From f21a130030c7d3f9b7803746863315756d5a87f3 Mon Sep 17 00:00:00 2001 From: Aaron Bockelie Date: Wed, 1 Jul 2026 23:11:16 -0500 Subject: [PATCH 1/4] =?UTF-8?q?fix(schema):=20repair=20warm=20migration=20?= =?UTF-8?q?058=20=E2=80=94=20it=20has=20never=20successfully=20applied?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Discovered while generating the v080 checkpoint baseline (see follow-up commit): replaying every migration into a pristine apache/age container with psql -v ON_ERROR_STOP=1 aborted at 058 with ERROR: syntax error at or near "CREATE" LINE 14: ... 'SELECT * FROM cypher(''knowledge_graph'', $$ CREATE VLA... Two stacked defects, both previously invisible: 1. Dollar-quote collision (parse-time, deterministic). The EXECUTE format string embedded `$$ CREATE VLABEL ... $$` inside a DO $$ block; the inner $$ terminates the outer block body, so the file has never parsed at all. Production never noticed because migrate-db.sh treats a failed migration as "skip, retry on next run" — it has failed silently on every platform start since the file was written. Confirmed on the live dev database: schema_migrations records versions 57 and 59 with no 58. 2. Even without the quoting bug, the statement was invalid: openCypher has no CREATE VLABEL statement. Label precreation is AGE's ag_catalog.create_vlabel()/create_elabel() API, whose parameters are cstring — a text variable needs an explicit ::cstring cast or function resolution fails. (This second failure mode surfaced during the first rewrite attempt, masked by the migration's own EXCEPTION handlers: "function ag_catalog.create_vlabel(unknown, text) does not exist".) Consequences of it never running: every fresh install has relied on lazy label auto-creation during ingestion — the exact concurrent-DDL race ('relation "Source" already exists') this migration was written to eliminate — and migration 078's MATCH on :Ontology assumed a precreated label that 058 never actually delivered. The rewrite keeps the IF NOT EXISTS guards and EXCEPTION handlers, and creates labels via PERFORM ag_catalog.create_vlabel('knowledge_graph', lbl::cstring) / create_elabel(...). Verified in the checkpoint replay: it creates all 45 missing labels on a fresh graph with zero failures, and it now shows as pending-warm on the dev database, so existing deployments finally apply it on their next platform start. Safe to modify in place — "never modify existing migrations" protects migrations that have been applied somewhere; this one never was, anywhere. Claude-Session: https://claude.ai/code/session_018VLKzWianGCKvY7CPbow8Z --- .../058_precreate_graph_labels.sql | 29 +++++++++---------- 1 file changed, 14 insertions(+), 15 deletions(-) diff --git a/schema/migrations-warm/058_precreate_graph_labels.sql b/schema/migrations-warm/058_precreate_graph_labels.sql index 2d1901efb..76c82cfb7 100644 --- a/schema/migrations-warm/058_precreate_graph_labels.sql +++ b/schema/migrations-warm/058_precreate_graph_labels.sql @@ -5,12 +5,19 @@ -- threads can race to create the same label, causing: -- "relation 'Source' already exists" -- --- Fix: explicitly create all known vertex and edge labels at schema init time. --- Uses DO blocks to check ag_catalog.ag_label before creating (AGE has no --- CREATE VLABEL IF NOT EXISTS syntax). +-- Fix: explicitly create all known vertex and edge labels at schema init time +-- via ag_catalog.create_vlabel()/create_elabel() (AGE's label API — cypher has +-- no CREATE VLABEL statement). Uses DO blocks to check ag_catalog.ag_label +-- before creating, since the functions error on existing labels. -- -- Note: Uses EXCEPTION handlers so label creation failures (e.g., AGE not fully -- initialized on cold start) are logged as warnings rather than aborting the migration. +-- +-- History: the original version of this file EXECUTEd +-- `cypher(..., $$ CREATE VLABEL ... $$)` — the inner $$ terminated the outer +-- DO $$ quoting, so the file never parsed and version 58 was never recorded +-- anywhere. Rewritten 2026-07-01; safe to modify in place because it never +-- successfully applied. LOAD 'age'; SET search_path = ag_catalog, "$user", public; @@ -31,10 +38,8 @@ BEGIN WHERE g.name = 'knowledge_graph' AND l.name = lbl AND l.kind = 'v' ) THEN BEGIN - EXECUTE format( - 'SELECT * FROM cypher(''knowledge_graph'', $$ CREATE VLABEL %I $$) as (a agtype)', - lbl - ); + -- create_vlabel takes cstring args; text needs an explicit cast + PERFORM ag_catalog.create_vlabel('knowledge_graph', lbl::cstring); RAISE NOTICE 'Created vertex label: %', lbl; EXCEPTION WHEN OTHERS THEN RAISE NOTICE 'Could not create vertex label % (will be created on first use): %', lbl, SQLERRM; @@ -63,10 +68,7 @@ BEGIN WHERE g.name = 'knowledge_graph' AND l.name = lbl AND l.kind = 'e' ) THEN BEGIN - EXECUTE format( - 'SELECT * FROM cypher(''knowledge_graph'', $$ CREATE ELABEL %I $$) as (a agtype)', - lbl - ); + PERFORM ag_catalog.create_elabel('knowledge_graph', lbl::cstring); RAISE NOTICE 'Created edge label: %', lbl; EXCEPTION WHEN OTHERS THEN RAISE NOTICE 'Could not create edge label % (will be created on first use): %', lbl, SQLERRM; @@ -110,10 +112,7 @@ BEGIN WHERE g.name = 'knowledge_graph' AND l.name = lbl AND l.kind = 'e' ) THEN BEGIN - EXECUTE format( - 'SELECT * FROM cypher(''knowledge_graph'', $$ CREATE ELABEL %I $$) as (a agtype)', - lbl - ); + PERFORM ag_catalog.create_elabel('knowledge_graph', lbl::cstring); RAISE NOTICE 'Created edge label: %', lbl; EXCEPTION WHEN OTHERS THEN RAISE NOTICE 'Could not create edge label % (will be created on first use): %', lbl, SQLERRM; From f221b4de24c65b388ae2c808333b6d53725aee7d Mon Sep 17 00:00:00 2001 From: Aaron Bockelie Date: Wed, 1 Jul 2026 23:11:16 -0500 Subject: [PATCH 2/4] feat(schema): consolidate migrations 001-080 into checkpoint baseline Executes ADR-210's 'periodically consolidate' plan. schema/00_baseline.sql is now a generated checkpoint of the full resting state at version 080: - Generated, not hand-merged: schema/scripts/checkpoint.sh replays the old baseline + all 78 migration files into a throwaway apache/age container via raw psql, then dumps relational DDL and seed data with pg_dump. Graph-seeding cypher migrations (014, 044, 058, 078) are carried into the baseline verbatim because AGE graphs cannot round-trip pg_dump. - Verified faithful: a second container built from the candidate alone matches the replayed original on full schema dump, AGE label catalog, per-table row counts, and normalized seed data. - Migrations 001-080 move to schema/migrations/archived/. The baseline records versions 1-80 in schema_migrations, so fresh installs skip them; migrate-db.sh scans archived/ first, so pre-checkpoint databases keep their incremental upgrade path. Next migration: 081. - Warm migration 058 stays in migrations-warm/ for pre-checkpoint deployments (none ever applied it; see previous commit). The kg-postgres image bakes 00_baseline.sql at build time and must be rebuilt/republished with the next release. Claude-Session: https://claude.ai/code/session_018VLKzWianGCKvY7CPbow8Z --- .claude/ways/kg/schema/way.md | 11 +- .../ADR-210-database-schema-migrations.md | 13 + operator/database/migrate-db.sh | 8 +- schema/00_baseline.sql | 9013 +++++++++++++++-- schema/migrations/README.md | 36 +- .../{ => archived}/001_baseline.sql | 0 .../003_add_embedding_config.sql | 0 .../004_add_ai_extraction_config.sql | 0 .../005_add_api_key_validation.sql | 0 .../006_add_embedding_config_protection.sql | 0 .../007_add_local_extraction_providers.sql | 0 .../008_add_nomic_embedding_preset.sql | 0 .../{ => archived}/009_add_thinking_mode.sql | 0 .../011_add_grounding_metrics.sql | 0 .../012_add_embedding_worker_support.sql | 0 .../013_add_schema_version_tracking.sql | 0 .../014_vocabulary_as_graph.sql | 0 ...robabilistic_vocabulary_categorization.sql | 0 ..._llm_determined_relationship_direction.sql | 0 .../017_vocabulary_configuration_system.sql | 0 .../018_add_rate_limiting_config.sql | 0 .../019_scheduled_jobs_system.sql | 0 .../020_track_authenticated_users_in_jobs.sql | 0 .../021_graph_provenance_tracking.sql | 0 .../022_oauth_client_management.sql | 0 .../023_image_storage_source_properties.sql | 0 .../024_add_concept_descriptions.sql | 0 .../025_graph_metrics_table.sql | 0 .../026_epistemic_remeasurement_job.sql | 0 .../027_source_text_embeddings.sql | 0 .../028_platform_admin_resources.sql | 0 .../{ => archived}/029_fix_role_hierarchy.sql | 0 .../030_users_create_permission.sql | 0 .../{ => archived}/031_platform_config.sql | 0 .../032_projection_scheduled_job.sql | 0 .../033_graph_change_triggers.sql | 0 .../034_user_scoping_groups.sql | 0 .../035_artifact_persistence.sql | 0 .../036_job_artifact_linking.sql | 0 .../037_admin_backup_permissions.sql | 0 .../038_admin_to_platform_admin.sql | 0 .../039_query_definition_metadata.sql | 0 .../040_graph_crud_permissions.sql | 0 .../{ => archived}/041_job_permissions.sql | 0 .../042_storage_admin_permissions.sql | 0 ...43_backfill_document_meta_content_type.sql | 0 .../044_ontology_graph_nodes.sql | 0 .../045_ontology_lifecycle_permissions.sql | 0 .../046_annealing_proposals.sql | 0 .../047_annealing_scheduled_job.sql | 0 .../{ => archived}/048_ontology_edges.sql | 0 .../{ => archived}/049_proposal_execution.sql | 0 .../050_exploration_definition_type.sql | 0 .../051_graph_accel_extension.sql | 0 .../052_program_definition_type.sql | 0 .../053_annealing_autonomous_default.sql | 0 .../054_sources_delete_permission.sql | 0 .../{ => archived}/055_embedding_profile.sql | 0 .../056_embedding_task_prefix.sql | 0 .../057_worker_lanes_and_job_dispatch.sql | 0 .../059_provider_model_catalog.sql | 0 .../060_remove_ollama_catalog_seeds.sql | 0 .../{ => archived}/061_llamacpp_provider.sql | 0 .../062_per_provider_config.sql | 0 .../{ => archived}/063_graph_epoch_events.sql | 0 .../064_graph_epoch_kinds_lookup.sql | 0 .../065_annealing_cadence_floors.sql | 0 .../066_ontology_tombstones.sql | 0 .../{ => archived}/067_annealing_vocab_v2.sql | 0 .../068_annealing_pressure_history.sql | 0 .../069_vocab_embedding_lifecycle.sql | 0 .../070_vocab_embedding_scheduled_job.sql | 0 .../071_registration_enabled_flag.sql | 0 .../072_admin_oauth_clients_write.sql | 0 .../{ => archived}/073_catalog_index.sql | 0 .../{ => archived}/074_ai_vision_config.sql | 0 .../075_decouple_image_embedding_space.sql | 0 .../{ => archived}/076_freshness_clock.sql | 0 .../{ => archived}/077_restore_epoch_kind.sql | 0 .../078_seed_primordial_ontology.sql | 0 .../079_search_default_threshold.sql | 0 .../080_search_config_permission.sql | 0 schema/scripts/checkpoint.sh | 381 + 83 files changed, 8609 insertions(+), 853 deletions(-) rename schema/migrations/{ => archived}/001_baseline.sql (100%) rename schema/migrations/{ => archived}/003_add_embedding_config.sql (100%) rename schema/migrations/{ => archived}/004_add_ai_extraction_config.sql (100%) rename schema/migrations/{ => archived}/005_add_api_key_validation.sql (100%) rename schema/migrations/{ => archived}/006_add_embedding_config_protection.sql (100%) rename schema/migrations/{ => archived}/007_add_local_extraction_providers.sql (100%) rename schema/migrations/{ => archived}/008_add_nomic_embedding_preset.sql (100%) rename schema/migrations/{ => archived}/009_add_thinking_mode.sql (100%) rename schema/migrations/{ => archived}/011_add_grounding_metrics.sql (100%) rename schema/migrations/{ => archived}/012_add_embedding_worker_support.sql (100%) rename schema/migrations/{ => archived}/013_add_schema_version_tracking.sql (100%) rename schema/migrations/{ => archived}/014_vocabulary_as_graph.sql (100%) rename schema/migrations/{ => archived}/015_probabilistic_vocabulary_categorization.sql (100%) rename schema/migrations/{ => archived}/016_llm_determined_relationship_direction.sql (100%) rename schema/migrations/{ => archived}/017_vocabulary_configuration_system.sql (100%) rename schema/migrations/{ => archived}/018_add_rate_limiting_config.sql (100%) rename schema/migrations/{ => archived}/019_scheduled_jobs_system.sql (100%) rename schema/migrations/{ => archived}/020_track_authenticated_users_in_jobs.sql (100%) rename schema/migrations/{ => archived}/021_graph_provenance_tracking.sql (100%) rename schema/migrations/{ => archived}/022_oauth_client_management.sql (100%) rename schema/migrations/{ => archived}/023_image_storage_source_properties.sql (100%) rename schema/migrations/{ => archived}/024_add_concept_descriptions.sql (100%) rename schema/migrations/{ => archived}/025_graph_metrics_table.sql (100%) rename schema/migrations/{ => archived}/026_epistemic_remeasurement_job.sql (100%) rename schema/migrations/{ => archived}/027_source_text_embeddings.sql (100%) rename schema/migrations/{ => archived}/028_platform_admin_resources.sql (100%) rename schema/migrations/{ => archived}/029_fix_role_hierarchy.sql (100%) rename schema/migrations/{ => archived}/030_users_create_permission.sql (100%) rename schema/migrations/{ => archived}/031_platform_config.sql (100%) rename schema/migrations/{ => archived}/032_projection_scheduled_job.sql (100%) rename schema/migrations/{ => archived}/033_graph_change_triggers.sql (100%) rename schema/migrations/{ => archived}/034_user_scoping_groups.sql (100%) rename schema/migrations/{ => archived}/035_artifact_persistence.sql (100%) rename schema/migrations/{ => archived}/036_job_artifact_linking.sql (100%) rename schema/migrations/{ => archived}/037_admin_backup_permissions.sql (100%) rename schema/migrations/{ => archived}/038_admin_to_platform_admin.sql (100%) rename schema/migrations/{ => archived}/039_query_definition_metadata.sql (100%) rename schema/migrations/{ => archived}/040_graph_crud_permissions.sql (100%) rename schema/migrations/{ => archived}/041_job_permissions.sql (100%) rename schema/migrations/{ => archived}/042_storage_admin_permissions.sql (100%) rename schema/migrations/{ => archived}/043_backfill_document_meta_content_type.sql (100%) rename schema/migrations/{ => archived}/044_ontology_graph_nodes.sql (100%) rename schema/migrations/{ => archived}/045_ontology_lifecycle_permissions.sql (100%) rename schema/migrations/{ => archived}/046_annealing_proposals.sql (100%) rename schema/migrations/{ => archived}/047_annealing_scheduled_job.sql (100%) rename schema/migrations/{ => archived}/048_ontology_edges.sql (100%) rename schema/migrations/{ => archived}/049_proposal_execution.sql (100%) rename schema/migrations/{ => archived}/050_exploration_definition_type.sql (100%) rename schema/migrations/{ => archived}/051_graph_accel_extension.sql (100%) rename schema/migrations/{ => archived}/052_program_definition_type.sql (100%) rename schema/migrations/{ => archived}/053_annealing_autonomous_default.sql (100%) rename schema/migrations/{ => archived}/054_sources_delete_permission.sql (100%) rename schema/migrations/{ => archived}/055_embedding_profile.sql (100%) rename schema/migrations/{ => archived}/056_embedding_task_prefix.sql (100%) rename schema/migrations/{ => archived}/057_worker_lanes_and_job_dispatch.sql (100%) rename schema/migrations/{ => archived}/059_provider_model_catalog.sql (100%) rename schema/migrations/{ => archived}/060_remove_ollama_catalog_seeds.sql (100%) rename schema/migrations/{ => archived}/061_llamacpp_provider.sql (100%) rename schema/migrations/{ => archived}/062_per_provider_config.sql (100%) rename schema/migrations/{ => archived}/063_graph_epoch_events.sql (100%) rename schema/migrations/{ => archived}/064_graph_epoch_kinds_lookup.sql (100%) rename schema/migrations/{ => archived}/065_annealing_cadence_floors.sql (100%) rename schema/migrations/{ => archived}/066_ontology_tombstones.sql (100%) rename schema/migrations/{ => archived}/067_annealing_vocab_v2.sql (100%) rename schema/migrations/{ => archived}/068_annealing_pressure_history.sql (100%) rename schema/migrations/{ => archived}/069_vocab_embedding_lifecycle.sql (100%) rename schema/migrations/{ => archived}/070_vocab_embedding_scheduled_job.sql (100%) rename schema/migrations/{ => archived}/071_registration_enabled_flag.sql (100%) rename schema/migrations/{ => archived}/072_admin_oauth_clients_write.sql (100%) rename schema/migrations/{ => archived}/073_catalog_index.sql (100%) rename schema/migrations/{ => archived}/074_ai_vision_config.sql (100%) rename schema/migrations/{ => archived}/075_decouple_image_embedding_space.sql (100%) rename schema/migrations/{ => archived}/076_freshness_clock.sql (100%) rename schema/migrations/{ => archived}/077_restore_epoch_kind.sql (100%) rename schema/migrations/{ => archived}/078_seed_primordial_ontology.sql (100%) rename schema/migrations/{ => archived}/079_search_default_threshold.sql (100%) rename schema/migrations/{ => archived}/080_search_config_permission.sql (100%) create mode 100755 schema/scripts/checkpoint.sh diff --git a/.claude/ways/kg/schema/way.md b/.claude/ways/kg/schema/way.md index f2976759f..ed2fc47c6 100644 --- a/.claude/ways/kg/schema/way.md +++ b/.claude/ways/kg/schema/way.md @@ -11,16 +11,21 @@ scope: agent, subagent ``` schema/ -├── 00_baseline.sql # Core schema (AGE, auth, API tables) -└── migrations/ # Sequential migrations (NNN_name.sql) +├── 00_baseline.sql # Checkpoint baseline (migrations 001-080 consolidated) +├── migrations/ # Post-checkpoint migrations (081+) +│ └── archived/ # 001-080, superseded by baseline — never edit +├── migrations-warm/ # Migrations needing a running graph engine +└── scripts/checkpoint.sh # Regenerate the checkpoint (replay + dump + verify) ``` ## Creating Migrations -1. Create `schema/migrations/NNN_descriptive_name.sql` +1. Create `schema/migrations/NNN_descriptive_name.sql` (next number after 080/latest) 2. Use next sequential number 3. Migrations auto-apply on `./operator.sh restart postgres` +`00_baseline.sql` is generated by `checkpoint.sh` — regenerate it, don't hand-edit. + ## Query Language **Apache AGE uses openCypher** (not Neo4j Cypher): diff --git a/docs/architecture/database-schema/ADR-210-database-schema-migrations.md b/docs/architecture/database-schema/ADR-210-database-schema-migrations.md index cc8595e15..2ac02c8df 100644 --- a/docs/architecture/database-schema/ADR-210-database-schema-migrations.md +++ b/docs/architecture/database-schema/ADR-210-database-schema-migrations.md @@ -275,6 +275,19 @@ COMMIT; - Merge all migrations into new baseline: `schema/migrations/stable_v2_baseline.sql` - Archive old migrations in `schema/migrations/archived/` +> **Executed 2026-07-01 (checkpoint v080):** migrations 001–080 were +> consolidated into `schema/00_baseline.sql` and moved to +> `schema/migrations/archived/`. The consolidation is generated, not +> hand-merged: `schema/scripts/checkpoint.sh` replays the old baseline plus +> every migration into a throwaway Apache AGE container via raw psql, dumps +> the resting state (relational DDL + seed data via pg_dump; graph-seeding +> cypher migrations carried verbatim, since AGE graphs cannot round-trip +> through pg_dump), and verifies the candidate by building a second container +> from it alone and diffing schema, AGE labels, row counts, and normalized +> data against the replayed original. The baseline records versions 1–80 in +> `schema_migrations`; the runner scans `archived/` before `migrations/`, so +> pre-checkpoint databases keep their incremental upgrade path. + --- ## Consequences diff --git a/operator/database/migrate-db.sh b/operator/database/migrate-db.sh index 4c88e98f6..988052dfe 100755 --- a/operator/database/migrate-db.sh +++ b/operator/database/migrate-db.sh @@ -177,8 +177,12 @@ if [ ! -d "$MIGRATIONS_DIR" ]; then exit 0 fi -# Scan for migration files -for migration_file in "$MIGRATIONS_DIR"/*.sql; do +# Scan for migration files. archived/ holds migrations consolidated into the +# checkpoint baseline (schema/00_baseline.sql): fresh databases have all of +# them recorded by the baseline and skip them; databases created before the +# checkpoint still find their pending versions there. Scanned first so +# archived versions sort ahead of post-checkpoint migrations. +for migration_file in "$MIGRATIONS_DIR"/archived/*.sql "$MIGRATIONS_DIR"/*.sql; do [ -f "$migration_file" ] || continue # Extract version from filename (001_baseline.sql → 001) diff --git a/schema/00_baseline.sql b/schema/00_baseline.sql index 0473012b4..8908038b4 100644 --- a/schema/00_baseline.sql +++ b/schema/00_baseline.sql @@ -1,754 +1,847 @@ -- ============================================================================ --- Knowledge Graph System - Consolidated Baseline Schema +-- Knowledge Graph System - Consolidated Baseline Schema (Checkpoint) -- ============================================================================ --- PostgreSQL + Apache AGE unified schema --- Date: 2025-10-16 --- Version: 2.0.0 (Baseline Consolidation) --- --- This script consolidates all schema definitions and migrations into a single --- baseline. It replaces the previous multi-file approach with a single source --- of truth for the database schema. --- --- Includes: --- - Apache AGE extension initialization --- - Multi-schema architecture (kg_api, kg_auth, kg_logs) --- - Dynamic RBAC system (ADR-028) --- - Vocabulary management (ADR-025, ADR-026, ADR-032) --- - All applied migrations +-- Generated by schema/scripts/checkpoint.sh — do not edit by hand. +-- Regenerate: schema/scripts/checkpoint.sh all +-- +-- Source commit: e0becd8c6 +-- Checkpoint: schema version 80 (migrations 001-080 consolidated) +-- Superseded migrations live in schema/migrations/archived/ +-- +-- Contents: +-- 1. AGE extension + knowledge_graph graph +-- 2. Relational DDL (pg_dump --schema-only of: ag_catalog graph_accel kg_api kg_auth kg_logs public ) +-- 3. Seed data (pg_dump --data-only, fresh fully-migrated database) +-- 4. Graph seed (idempotent cypher migrations carried verbatim) +-- 5. schema_migrations records for all consolidated versions -- ============================================================================ -- ---------------------------------------------------------------------------- --- STEP 1: Extensions and Configuration +-- 1. Extensions and graph -- ---------------------------------------------------------------------------- CREATE EXTENSION IF NOT EXISTS age; LOAD 'age'; SET search_path = ag_catalog, "$user", public; +DO $$ +BEGIN + IF NOT EXISTS (SELECT 1 FROM ag_catalog.ag_graph WHERE name = 'knowledge_graph') THEN + PERFORM ag_catalog.create_graph('knowledge_graph'); + END IF; +END $$; + +CREATE EXTENSION IF NOT EXISTS pg_trgm WITH SCHEMA public; + -- ---------------------------------------------------------------------------- --- STEP 2: Create Knowledge Graph +-- 2. Relational DDL -- ---------------------------------------------------------------------------- +-- +-- PostgreSQL database dump +-- -SELECT create_graph('knowledge_graph'); --- ============================================================================ --- STEP 3: Create Schemas --- ============================================================================ +-- Dumped from database version 18.1 (Debian 18.1-1.pgdg13+2) +-- Dumped by pg_dump version 18.1 (Debian 18.1-1.pgdg13+2) -CREATE SCHEMA IF NOT EXISTS kg_api; -- API operational state -CREATE SCHEMA IF NOT EXISTS kg_auth; -- Authentication & authorization -CREATE SCHEMA IF NOT EXISTS kg_logs; -- Audit logs & observability +SET statement_timeout = 0; +SET lock_timeout = 0; +SET idle_in_transaction_session_timeout = 0; +SET transaction_timeout = 0; +SET client_encoding = 'UTF8'; +SET standard_conforming_strings = on; +SELECT pg_catalog.set_config('search_path', '', false); +SET check_function_bodies = false; +SET xmloption = content; +SET client_min_messages = warning; +SET row_security = off; -COMMENT ON SCHEMA kg_api IS 'API operational state: jobs, sessions, vocabulary (ADR-024, ADR-025)'; -COMMENT ON SCHEMA kg_auth IS 'Security: authentication, authorization, RBAC (ADR-024, ADR-028)'; -COMMENT ON SCHEMA kg_logs IS 'Observability: audit trails, metrics, telemetry (ADR-024)'; +-- +-- Name: ag_catalog; Type: SCHEMA; Schema: -; Owner: - +-- --- ============================================================================ --- Migration Tracking (ADR-040) --- ============================================================================ +-- CREATE SCHEMA ag_catalog; (owned by age extension) -CREATE TABLE IF NOT EXISTS public.schema_migrations ( - version INTEGER PRIMARY KEY, - name TEXT NOT NULL, - applied_at TIMESTAMP NOT NULL DEFAULT NOW() -); -CREATE INDEX IF NOT EXISTS idx_schema_migrations_applied ON public.schema_migrations(applied_at DESC); +-- +-- Name: public; Type: SCHEMA; Schema: -; Owner: - +-- -COMMENT ON TABLE public.schema_migrations IS 'Tracks applied schema migrations for safe schema evolution - ADR-040'; -COMMENT ON COLUMN public.schema_migrations.version IS 'Sequential migration number (001, 002, 003, ...)'; -COMMENT ON COLUMN public.schema_migrations.name IS 'Descriptive migration name (e.g., baseline, add_embedding_config)'; -COMMENT ON COLUMN public.schema_migrations.applied_at IS 'Timestamp when migration was applied'; +-- CREATE SCHEMA public; (exists) --- ============================================================================ --- KG_API SCHEMA - Operational State --- ============================================================================ --- Job Queue (ADR-014, ADR-024, ADR-100) --- Note: table renamed from ingestion_jobs to jobs in migration 019 -CREATE TABLE kg_api.ingestion_jobs ( - job_id VARCHAR(50) PRIMARY KEY, - job_type VARCHAR(50) NOT NULL, - status VARCHAR(50) NOT NULL CHECK (status IN ( - 'pending', - 'awaiting_approval', - 'approved', - 'queued', - 'running', - 'completed', - 'failed', - 'cancelled' - )), - ontology VARCHAR(200) NOT NULL, - client_id VARCHAR(100), - content_hash VARCHAR(80), -- sha256: prefix (7) + 64 hex = 71 chars (fixed in migration 002) - job_data JSONB NOT NULL, - progress JSONB, - result JSONB, - analysis JSONB, -- Pre-ingestion cost estimates (ADR-014) - processing_mode VARCHAR(20) DEFAULT 'serial' CHECK (processing_mode IN ('serial', 'parallel')), - -- ADR-100: Database-driven job dispatch columns - priority INTEGER NOT NULL DEFAULT 0, -- Higher = claimed first - claimed_by TEXT, -- Worker ID that claimed this job - claimed_at TIMESTAMPTZ, -- When claimed - cancelled BOOLEAN NOT NULL DEFAULT FALSE, -- Cooperative cancellation flag - retries INTEGER NOT NULL DEFAULT 0, -- Times re-queued after stale timeout - max_retries INTEGER NOT NULL DEFAULT 3, -- Max retries before failing - created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), - started_at TIMESTAMPTZ, - completed_at TIMESTAMPTZ, - approved_at TIMESTAMPTZ, - approved_by VARCHAR(100), - expires_at TIMESTAMPTZ, - error_message TEXT -); - -CREATE INDEX idx_jobs_status ON kg_api.ingestion_jobs(status); -CREATE INDEX idx_jobs_ontology ON kg_api.ingestion_jobs(ontology); -CREATE INDEX idx_jobs_created_at ON kg_api.ingestion_jobs(created_at DESC); -CREATE INDEX idx_jobs_client_id ON kg_api.ingestion_jobs(client_id); -CREATE INDEX idx_jobs_content_hash ON kg_api.ingestion_jobs(content_hash); --- ADR-100: Claim query index (poll-and-claim dispatch) -CREATE INDEX idx_jobs_claim ON kg_api.ingestion_jobs (status, priority DESC, created_at ASC) - WHERE status = 'approved'; --- ADR-100: Stale job recovery index -CREATE INDEX idx_jobs_stale_recovery ON kg_api.ingestion_jobs (claimed_at) - WHERE status = 'running' AND claimed_at IS NOT NULL; - -COMMENT ON TABLE kg_api.ingestion_jobs IS 'Job queue (replaces SQLite jobs.db) - ADR-014, ADR-024, ADR-100'; - --- Worker Lanes (ADR-100) -CREATE TABLE kg_api.worker_lanes ( - name TEXT PRIMARY KEY, - job_types TEXT[] NOT NULL, - max_slots INTEGER NOT NULL DEFAULT 1, - poll_interval_ms INTEGER NOT NULL DEFAULT 5000, - stale_timeout_minutes INTEGER NOT NULL DEFAULT 30, - enabled BOOLEAN NOT NULL DEFAULT TRUE, - updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW() -); +-- +-- Name: SCHEMA public; Type: COMMENT; Schema: -; Owner: - +-- -COMMENT ON TABLE kg_api.worker_lanes IS 'Worker lane configuration for database-driven job dispatch (ADR-100)'; +COMMENT ON SCHEMA public IS 'standard public schema'; --- Active Sessions -CREATE TABLE kg_api.sessions ( - session_id VARCHAR(100) PRIMARY KEY, - user_id INTEGER, - created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), - expires_at TIMESTAMPTZ NOT NULL, - last_activity TIMESTAMPTZ NOT NULL DEFAULT NOW(), - metadata JSONB -); -CREATE INDEX idx_sessions_user_id ON kg_api.sessions(user_id); -CREATE INDEX idx_sessions_expires_at ON kg_api.sessions(expires_at); +-- +-- Name: kg_api; Type: SCHEMA; Schema: -; Owner: - +-- --- Rate Limiting -CREATE TABLE kg_api.rate_limits ( - client_id VARCHAR(100) NOT NULL, - endpoint VARCHAR(200) NOT NULL, - window_start TIMESTAMPTZ NOT NULL, - request_count INTEGER NOT NULL DEFAULT 0, - PRIMARY KEY (client_id, endpoint, window_start) -); +CREATE SCHEMA kg_api; -CREATE INDEX idx_rate_limits_window ON kg_api.rate_limits(window_start); --- Background Workers -CREATE TABLE kg_api.worker_status ( - worker_id VARCHAR(100) PRIMARY KEY, - last_heartbeat TIMESTAMPTZ NOT NULL DEFAULT NOW(), - current_job_id VARCHAR(50), - status VARCHAR(50) NOT NULL CHECK (status IN ('idle', 'running', 'error', 'stopped')), - metadata JSONB -); +-- +-- Name: SCHEMA kg_api; Type: COMMENT; Schema: -; Owner: - +-- -CREATE INDEX idx_worker_heartbeat ON kg_api.worker_status(last_heartbeat DESC); +COMMENT ON SCHEMA kg_api IS 'API operational state: jobs, sessions, vocabulary (ADR-024, ADR-025)'; --- ============================================================================ --- Relationship Vocabulary Management (ADR-025, ADR-032) --- ============================================================================ --- Relationship Vocabulary (canonical types with embeddings) -CREATE TABLE kg_api.relationship_vocabulary ( - relationship_type VARCHAR(100) PRIMARY KEY, - description TEXT, - category VARCHAR(50), - added_by VARCHAR(100), - added_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), - usage_count INTEGER DEFAULT 0, - is_active BOOLEAN DEFAULT TRUE, - is_builtin BOOLEAN DEFAULT FALSE, - synonyms VARCHAR(100)[], - deprecation_reason TEXT, - -- ADR-032 additions: - embedding JSONB, -- Cached vector as JSON array - embedding_model VARCHAR(100), -- Model used (e.g., text-embedding-ada-002) - embedding_generated_at TIMESTAMPTZ -); - -CREATE INDEX idx_vocab_category ON kg_api.relationship_vocabulary(category); -CREATE INDEX idx_vocab_active ON kg_api.relationship_vocabulary(is_active); -CREATE INDEX idx_vocab_usage ON kg_api.relationship_vocabulary(usage_count DESC); -CREATE INDEX idx_vocab_embedding_model ON kg_api.relationship_vocabulary(embedding_model) - WHERE embedding IS NOT NULL; +-- +-- Name: kg_auth; Type: SCHEMA; Schema: -; Owner: - +-- -COMMENT ON TABLE kg_api.relationship_vocabulary IS 'Canonical relationship types with embeddings - ADR-025, ADR-032'; -COMMENT ON COLUMN kg_api.relationship_vocabulary.embedding IS 'Cached embedding vector (JSONB array) for synonym detection (ADR-032)'; +CREATE SCHEMA kg_auth; --- Skipped Relationships (capture layer) -CREATE TABLE kg_api.skipped_relationships ( - id SERIAL PRIMARY KEY, - relationship_type VARCHAR(100) NOT NULL, - from_concept_label VARCHAR(500), - to_concept_label VARCHAR(500), - job_id VARCHAR(50), - ontology VARCHAR(200), - first_seen TIMESTAMPTZ NOT NULL DEFAULT NOW(), - last_seen TIMESTAMPTZ NOT NULL DEFAULT NOW(), - occurrence_count INTEGER DEFAULT 1, - sample_context JSONB, - UNIQUE(relationship_type, from_concept_label, to_concept_label) -); - -CREATE INDEX idx_skipped_type ON kg_api.skipped_relationships(relationship_type); -CREATE INDEX idx_skipped_count ON kg_api.skipped_relationships(occurrence_count DESC); -CREATE INDEX idx_skipped_first_seen ON kg_api.skipped_relationships(first_seen DESC); -CREATE INDEX idx_skipped_ontology ON kg_api.skipped_relationships(ontology); -COMMENT ON TABLE kg_api.skipped_relationships IS 'Capture layer for unmatched relationship types - ADR-025'; +-- +-- Name: SCHEMA kg_auth; Type: COMMENT; Schema: -; Owner: - +-- --- Vocabulary Audit Trail -CREATE TABLE kg_api.vocabulary_audit ( - id SERIAL PRIMARY KEY, - relationship_type VARCHAR(100), - action VARCHAR(50) NOT NULL, - performed_by VARCHAR(100), - performed_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), - details JSONB -); +COMMENT ON SCHEMA kg_auth IS 'Security: authentication, authorization, RBAC (ADR-024, ADR-028)'; -CREATE INDEX idx_vocab_audit_type ON kg_api.vocabulary_audit(relationship_type); -CREATE INDEX idx_vocab_audit_performed_at ON kg_api.vocabulary_audit(performed_at DESC); --- Vocabulary History (ADR-032 detailed tracking) -CREATE TABLE kg_api.vocabulary_history ( - id SERIAL PRIMARY KEY, - relationship_type VARCHAR(100) NOT NULL, - action VARCHAR(50) NOT NULL CHECK (action IN ('added', 'merged', 'pruned', 'deprecated', 'reactivated')), - performed_by VARCHAR(100) NOT NULL, - performed_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), - target_type VARCHAR(100), - reason TEXT, - metadata JSONB, - aggressiveness NUMERIC(4,3), - zone VARCHAR(20), - vocab_size_before INTEGER, - vocab_size_after INTEGER -); - -CREATE INDEX idx_vocab_history_type ON kg_api.vocabulary_history(relationship_type); -CREATE INDEX idx_vocab_history_action ON kg_api.vocabulary_history(action); -CREATE INDEX idx_vocab_history_performed_at ON kg_api.vocabulary_history(performed_at DESC); -CREATE INDEX idx_vocab_history_performed_by ON kg_api.vocabulary_history(performed_by); +-- +-- Name: kg_logs; Type: SCHEMA; Schema: -; Owner: - +-- -COMMENT ON TABLE kg_api.vocabulary_history IS 'Detailed vocabulary change tracking with context (ADR-032)'; +CREATE SCHEMA kg_logs; --- Pruning Recommendations (ADR-032) -CREATE TABLE kg_api.pruning_recommendations ( - id SERIAL PRIMARY KEY, - relationship_type VARCHAR(100) NOT NULL, - target_type VARCHAR(100), - action_type VARCHAR(50) NOT NULL CHECK (action_type IN ('merge', 'prune', 'deprecate', 'skip')), - review_level VARCHAR(20) NOT NULL CHECK (review_level IN ('none', 'ai', 'human')), - reasoning TEXT NOT NULL, - similarity NUMERIC(4,3), - value_score NUMERIC(10,2), - metadata JSONB, - status VARCHAR(50) NOT NULL DEFAULT 'pending' CHECK (status IN ('pending', 'approved', 'rejected', 'executed', 'expired')), - created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), - reviewed_at TIMESTAMPTZ, - reviewed_by VARCHAR(100), - reviewer_notes TEXT, - executed_at TIMESTAMPTZ, - expires_at TIMESTAMPTZ -); - -CREATE INDEX idx_pruning_recs_type ON kg_api.pruning_recommendations(relationship_type); -CREATE INDEX idx_pruning_recs_status ON kg_api.pruning_recommendations(status); -CREATE INDEX idx_pruning_recs_review_level ON kg_api.pruning_recommendations(review_level); -CREATE INDEX idx_pruning_recs_created_at ON kg_api.pruning_recommendations(created_at DESC); -CREATE INDEX idx_pruning_recs_expires_at ON kg_api.pruning_recommendations(expires_at) - WHERE expires_at IS NOT NULL; -COMMENT ON TABLE kg_api.pruning_recommendations IS 'Pending vocabulary management actions - ADR-032'; +-- +-- Name: SCHEMA kg_logs; Type: COMMENT; Schema: -; Owner: - +-- --- Vocabulary Configuration -CREATE TABLE kg_api.vocabulary_config ( - key VARCHAR(100) PRIMARY KEY, - value TEXT NOT NULL, - description TEXT, - updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), - updated_by VARCHAR(100) -); +COMMENT ON SCHEMA kg_logs IS 'Observability: audit trails, metrics, telemetry (ADR-024)'; -CREATE INDEX idx_vocab_config_updated_at ON kg_api.vocabulary_config(updated_at DESC); -COMMENT ON TABLE kg_api.vocabulary_config IS 'System configuration for automatic vocabulary management (ADR-032)'; --- ============================================================================ --- Performance Optimization (ADR-025) --- ============================================================================ +-- +-- Name: archive_old_jobs(integer); Type: FUNCTION; Schema: ag_catalog; Owner: - +-- --- Edge Usage Statistics -CREATE TABLE kg_api.edge_usage_stats ( - from_concept_id VARCHAR(100) NOT NULL, - to_concept_id VARCHAR(100) NOT NULL, - relationship_type VARCHAR(100) NOT NULL, - traversal_count INTEGER DEFAULT 0, - last_traversed TIMESTAMPTZ, - avg_query_time_ms NUMERIC(10,2), - PRIMARY KEY (from_concept_id, to_concept_id, relationship_type) -); +CREATE FUNCTION ag_catalog.archive_old_jobs(days_threshold integer DEFAULT 30) RETURNS integer + LANGUAGE plpgsql + AS $$ +DECLARE + archived_count INTEGER; +BEGIN + INSERT INTO kg_logs.job_events (job_id, timestamp, event_type, details) + SELECT + job_id, + completed_at, + 'archived', + jsonb_build_object( + 'status', status, + 'ontology', ontology, + 'result', result + ) + FROM kg_api.ingestion_jobs + WHERE completed_at < NOW() - (days_threshold || ' days')::INTERVAL + AND status IN ('completed', 'failed'); -CREATE INDEX idx_edge_usage_count ON kg_api.edge_usage_stats(traversal_count DESC); -CREATE INDEX idx_edge_usage_type ON kg_api.edge_usage_stats(relationship_type); -CREATE INDEX idx_edge_usage_from ON kg_api.edge_usage_stats(from_concept_id); + DELETE FROM kg_api.ingestion_jobs + WHERE completed_at < NOW() - (days_threshold || ' days')::INTERVAL + AND status IN ('completed', 'failed'); -COMMENT ON TABLE kg_api.edge_usage_stats IS 'Performance tracking for graph traversals - ADR-025'; + GET DIAGNOSTICS archived_count = ROW_COUNT; + RETURN archived_count; +END; +$$; --- Concept Access Statistics -CREATE TABLE kg_api.concept_access_stats ( - concept_id VARCHAR(100) PRIMARY KEY, - access_count INTEGER DEFAULT 0, - last_accessed TIMESTAMPTZ, - avg_query_time_ms NUMERIC(10,2), - queries_as_start INTEGER DEFAULT 0, - queries_as_result INTEGER DEFAULT 0 -); -CREATE INDEX idx_concept_access_count ON kg_api.concept_access_stats(access_count DESC); -CREATE INDEX idx_concept_access_last ON kg_api.concept_access_stats(last_accessed DESC); +-- +-- Name: cleanup_expired_sessions(); Type: FUNCTION; Schema: ag_catalog; Owner: - +-- -COMMENT ON TABLE kg_api.concept_access_stats IS 'Node-level access patterns for caching - ADR-025'; +CREATE FUNCTION ag_catalog.cleanup_expired_sessions() RETURNS integer + LANGUAGE plpgsql + AS $$ +DECLARE + deleted_count INTEGER; +BEGIN + DELETE FROM kg_api.sessions WHERE expires_at < NOW(); + GET DIAGNOSTICS deleted_count = ROW_COUNT; + RETURN deleted_count; +END; +$$; --- Hot Edges Materialized View -CREATE MATERIALIZED VIEW kg_api.hot_edges AS -SELECT - from_concept_id, - to_concept_id, - relationship_type, - traversal_count -FROM kg_api.edge_usage_stats -WHERE traversal_count > 100 -ORDER BY traversal_count DESC -LIMIT 1000; -CREATE INDEX idx_hot_edges_lookup ON kg_api.hot_edges(from_concept_id, to_concept_id); +-- +-- Name: expire_old_recommendations(integer); Type: FUNCTION; Schema: ag_catalog; Owner: - +-- --- Hot Concepts Materialized View -CREATE MATERIALIZED VIEW kg_api.hot_concepts AS -SELECT - concept_id, - access_count, - queries_as_start -FROM kg_api.concept_access_stats -WHERE access_count > 50 -ORDER BY access_count DESC -LIMIT 100; +CREATE FUNCTION ag_catalog.expire_old_recommendations(days_threshold integer DEFAULT 7) RETURNS integer + LANGUAGE plpgsql + AS $$ +DECLARE + expired_count INTEGER; +BEGIN + UPDATE kg_api.pruning_recommendations + SET status = 'expired' + WHERE status = 'pending' + AND created_at < NOW() - (days_threshold || ' days')::INTERVAL + AND expires_at IS NULL; -CREATE INDEX idx_hot_concepts_id ON kg_api.hot_concepts(concept_id); + GET DIAGNOSTICS expired_count = ROW_COUNT; + RETURN expired_count; +END; +$$; --- ============================================================================ --- Autonomous Vocabulary Management (ADR-026) --- ============================================================================ --- Ontology Version Registry -CREATE TABLE kg_api.ontology_versions ( - version_id SERIAL PRIMARY KEY, - version_number VARCHAR(20) NOT NULL UNIQUE, - created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), - created_by VARCHAR(100), - change_summary TEXT, - is_active BOOLEAN DEFAULT TRUE, - vocabulary_snapshot JSONB NOT NULL, - types_added TEXT[], - types_aliased JSONB, - types_deprecated TEXT[], - backward_compatible BOOLEAN DEFAULT TRUE, - migration_required BOOLEAN DEFAULT FALSE -); - -CREATE INDEX idx_ontology_versions_number ON kg_api.ontology_versions(version_number); -CREATE INDEX idx_ontology_versions_active ON kg_api.ontology_versions(is_active); +-- +-- Name: FUNCTION expire_old_recommendations(days_threshold integer); Type: COMMENT; Schema: ag_catalog; Owner: - +-- -COMMENT ON TABLE kg_api.ontology_versions IS 'Formal ontology versioning with immutable snapshots - ADR-026'; +COMMENT ON FUNCTION ag_catalog.expire_old_recommendations(days_threshold integer) IS 'Expire pending recommendations older than threshold (ADR-032)'; --- Concept Version Metadata -CREATE TABLE kg_api.concept_version_metadata ( - concept_id VARCHAR(100) PRIMARY KEY, - created_in_version INTEGER REFERENCES kg_api.ontology_versions(version_id), - last_modified_version INTEGER REFERENCES kg_api.ontology_versions(version_id) -); --- Vocabulary Suggestions (LLM-assisted curation) -CREATE TABLE kg_api.vocabulary_suggestions ( - id SERIAL PRIMARY KEY, - relationship_type VARCHAR(100) NOT NULL, - suggestion_type VARCHAR(50) NOT NULL CHECK (suggestion_type IN ('alias', 'new_type')), - confidence NUMERIC(3,2) NOT NULL CHECK (confidence BETWEEN 0 AND 1), - suggested_canonical_type VARCHAR(100), - suggested_category VARCHAR(50), - suggested_description TEXT, - similar_types JSONB, - reasoning TEXT, - created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), - reviewed BOOLEAN DEFAULT FALSE, - curator_decision VARCHAR(50), - curator_notes TEXT -); - -CREATE INDEX idx_vocab_suggestions_type ON kg_api.vocabulary_suggestions(relationship_type); -CREATE INDEX idx_vocab_suggestions_confidence ON kg_api.vocabulary_suggestions(confidence DESC); -CREATE INDEX idx_vocab_suggestions_reviewed ON kg_api.vocabulary_suggestions(reviewed); +-- +-- Name: refresh_hot_concepts(); Type: FUNCTION; Schema: ag_catalog; Owner: - +-- -COMMENT ON TABLE kg_api.vocabulary_suggestions IS 'LLM-assisted vocabulary curation suggestions - ADR-026'; --- Embedding Configuration - Moved to migration 003 (ADR-039) --- See: schema/migrations/003_add_embedding_config.sql +CREATE FUNCTION ag_catalog.refresh_hot_concepts() RETURNS void + LANGUAGE plpgsql + AS $$ +BEGIN + REFRESH MATERIALIZED VIEW CONCURRENTLY kg_api.hot_concepts; +END; +$$; --- ============================================================================ --- KG_AUTH SCHEMA - Security (ADR-028 Dynamic RBAC) --- ============================================================================ --- Resource Registry -CREATE TABLE kg_auth.resources ( - resource_type VARCHAR(100) PRIMARY KEY, - description TEXT, - parent_type VARCHAR(100) REFERENCES kg_auth.resources(resource_type), - available_actions VARCHAR(50)[] NOT NULL, - supports_scoping BOOLEAN DEFAULT FALSE, - metadata JSONB DEFAULT '{}', - registered_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), - registered_by VARCHAR(100) -); +-- +-- Name: refresh_hot_edges(); Type: FUNCTION; Schema: ag_catalog; Owner: - +-- -CREATE INDEX idx_resources_parent ON kg_auth.resources(parent_type); -COMMENT ON TABLE kg_auth.resources IS 'Dynamic resource type registry (ADR-028)'; +CREATE FUNCTION ag_catalog.refresh_hot_edges() RETURNS void + LANGUAGE plpgsql + AS $$ +BEGIN + REFRESH MATERIALIZED VIEW CONCURRENTLY kg_api.hot_edges; +END; +$$; --- Dynamic Roles -CREATE TABLE kg_auth.roles ( - role_name VARCHAR(50) PRIMARY KEY, - display_name VARCHAR(100) NOT NULL, - description TEXT, - is_builtin BOOLEAN DEFAULT FALSE, - is_active BOOLEAN DEFAULT TRUE, - parent_role VARCHAR(50) REFERENCES kg_auth.roles(role_name), - created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), - created_by INTEGER, -- Will reference users.id after users table is created - metadata JSONB DEFAULT '{}' -); - -CREATE INDEX idx_roles_parent ON kg_auth.roles(parent_role); -CREATE INDEX idx_roles_builtin ON kg_auth.roles(is_builtin); -CREATE INDEX idx_roles_active ON kg_auth.roles(is_active); -COMMENT ON TABLE kg_auth.roles IS 'Dynamic role definitions with inheritance (ADR-028)'; --- User Accounts -CREATE TABLE kg_auth.users ( - id SERIAL PRIMARY KEY, - username VARCHAR(100) UNIQUE NOT NULL, - password_hash VARCHAR(255) NOT NULL, - primary_role VARCHAR(50) NOT NULL REFERENCES kg_auth.roles(role_name), - created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), - last_login TIMESTAMPTZ, - disabled BOOLEAN DEFAULT FALSE -); +-- +-- Name: are_source_embeddings_stale(text, text); Type: FUNCTION; Schema: kg_api; Owner: - +-- -CREATE INDEX idx_users_username ON kg_auth.users(username); -CREATE INDEX idx_users_primary_role ON kg_auth.users(primary_role); +CREATE FUNCTION kg_api.are_source_embeddings_stale(p_source_id text, p_current_source_hash text) RETURNS boolean + LANGUAGE plpgsql STABLE + AS $$ +DECLARE + v_stored_hash TEXT; +BEGIN + -- Get stored hash from any embedding for this source + SELECT source_hash + INTO v_stored_hash + FROM kg_api.source_embeddings + WHERE source_id = p_source_id + LIMIT 1; -COMMENT ON COLUMN kg_auth.users.primary_role IS 'Primary role (backwards compatibility) - user can have additional roles in user_roles table'; + -- If no embeddings exist, they're not stale (they're missing) + IF v_stored_hash IS NULL THEN + RETURN FALSE; + END IF; --- Now that users table exists, add the foreign key to roles.created_by -ALTER TABLE kg_auth.roles ADD CONSTRAINT fk_roles_created_by - FOREIGN KEY (created_by) REFERENCES kg_auth.users(id); + -- Compare hashes + RETURN v_stored_hash != p_current_source_hash; +END; +$$; --- User Role Assignments (multiple roles per user) -CREATE TABLE kg_auth.user_roles ( - id SERIAL PRIMARY KEY, - user_id INTEGER NOT NULL REFERENCES kg_auth.users(id) ON DELETE CASCADE, - role_name VARCHAR(50) NOT NULL REFERENCES kg_auth.roles(role_name) ON DELETE CASCADE, - scope_type VARCHAR(50), - scope_id VARCHAR(200), - assigned_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), - assigned_by INTEGER REFERENCES kg_auth.users(id), - expires_at TIMESTAMPTZ, - UNIQUE(user_id, role_name, scope_type, scope_id) -); - -CREATE INDEX idx_user_roles_user ON kg_auth.user_roles(user_id); -CREATE INDEX idx_user_roles_role ON kg_auth.user_roles(role_name); -CREATE INDEX idx_user_roles_scope ON kg_auth.user_roles(scope_type, scope_id); -CREATE INDEX idx_user_roles_expires ON kg_auth.user_roles(expires_at) WHERE expires_at IS NOT NULL; -COMMENT ON TABLE kg_auth.user_roles IS 'User role assignments with optional scoping (ADR-028)'; +-- +-- Name: FUNCTION are_source_embeddings_stale(p_source_id text, p_current_source_hash text); Type: COMMENT; Schema: kg_api; Owner: - +-- --- Role Permissions (scoped permissions) -CREATE TABLE kg_auth.role_permissions ( - id SERIAL PRIMARY KEY, - role_name VARCHAR(50) NOT NULL REFERENCES kg_auth.roles(role_name) ON DELETE CASCADE, - resource_type VARCHAR(100) NOT NULL REFERENCES kg_auth.resources(resource_type), - action VARCHAR(50) NOT NULL, - scope_type VARCHAR(50), - scope_id VARCHAR(200), - scope_filter JSONB, - granted BOOLEAN NOT NULL DEFAULT TRUE, - inherited_from VARCHAR(50) REFERENCES kg_auth.roles(role_name), - created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), - created_by INTEGER REFERENCES kg_auth.users(id) -); - -CREATE UNIQUE INDEX idx_role_perms_unique_with_scope - ON kg_auth.role_permissions(role_name, resource_type, action, scope_type, scope_id) - WHERE scope_id IS NOT NULL; - -CREATE UNIQUE INDEX idx_role_perms_unique_without_scope - ON kg_auth.role_permissions(role_name, resource_type, action, scope_type) - WHERE scope_id IS NULL; - -CREATE INDEX idx_role_perms_role ON kg_auth.role_permissions(role_name); -CREATE INDEX idx_role_perms_resource ON kg_auth.role_permissions(resource_type, action); -CREATE INDEX idx_role_perms_scope ON kg_auth.role_permissions(scope_type, scope_id); -CREATE INDEX idx_role_perms_granted ON kg_auth.role_permissions(granted) WHERE granted = FALSE; +COMMENT ON FUNCTION kg_api.are_source_embeddings_stale(p_source_id text, p_current_source_hash text) IS 'ADR-068: Check if source embeddings are stale (source text changed)'; -COMMENT ON TABLE kg_auth.role_permissions IS 'Dynamic role permissions with scoping (ADR-028)'; --- API Keys -CREATE TABLE kg_auth.api_keys ( - id SERIAL PRIMARY KEY, - key_hash VARCHAR(255) UNIQUE NOT NULL, - user_id INTEGER REFERENCES kg_auth.users(id) ON DELETE CASCADE, - name VARCHAR(200), - scopes TEXT[], - created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), - last_used TIMESTAMPTZ, - expires_at TIMESTAMPTZ -); +-- +-- Name: auto_validate_vocabulary_embedding(); Type: FUNCTION; Schema: kg_api; Owner: - +-- -CREATE INDEX idx_api_keys_key_hash ON kg_auth.api_keys(key_hash); -CREATE INDEX idx_api_keys_user_id ON kg_auth.api_keys(user_id); +CREATE FUNCTION kg_api.auto_validate_vocabulary_embedding() RETURNS trigger + LANGUAGE plpgsql + AS $$ +BEGIN + -- Only validate if embedding was set/changed + IF NEW.embedding IS NOT NULL THEN + -- Get expected dimensions from active embedding profile + DECLARE + v_expected_dimensions INTEGER; + BEGIN + SELECT text_dimensions INTO v_expected_dimensions + FROM kg_api.embedding_profile + WHERE active = TRUE + LIMIT 1; --- OAuth Tokens (future) -CREATE TABLE kg_auth.oauth_tokens ( - token_hash VARCHAR(255) PRIMARY KEY, - user_id INTEGER REFERENCES kg_auth.users(id) ON DELETE CASCADE, - provider VARCHAR(50), - scopes TEXT[], - expires_at TIMESTAMPTZ NOT NULL -); + -- Validate embedding structure + IF kg_api.validate_embedding(NEW.embedding, v_expected_dimensions) THEN + NEW.embedding_validation_status := 'valid'; -CREATE INDEX idx_oauth_user_id ON kg_auth.oauth_tokens(user_id); -CREATE INDEX idx_oauth_expires ON kg_auth.oauth_tokens(expires_at); + -- Calculate quality score (basic: non-zero vector magnitude) + -- Full quality scoring implemented in Python EmbeddingWorker + NEW.embedding_quality_score := 1.0; + ELSE + NEW.embedding_validation_status := 'invalid'; + NEW.embedding_quality_score := 0.0; + END IF; + END; + END IF; --- ============================================================================ --- KG_LOGS SCHEMA - Observability --- ============================================================================ + RETURN NEW; +END; +$$; --- Audit Trail -CREATE TABLE kg_logs.audit_trail ( - id SERIAL PRIMARY KEY, - timestamp TIMESTAMPTZ NOT NULL DEFAULT NOW(), - user_id INTEGER, - action VARCHAR(100) NOT NULL, - resource_type VARCHAR(50) NOT NULL, - resource_id VARCHAR(200), - details JSONB, - ip_address INET, - user_agent TEXT, - outcome VARCHAR(50) NOT NULL CHECK (outcome IN ('success', 'denied', 'error')) -); - -CREATE INDEX idx_audit_timestamp ON kg_logs.audit_trail(timestamp DESC); -CREATE INDEX idx_audit_user_id ON kg_logs.audit_trail(user_id); -CREATE INDEX idx_audit_action ON kg_logs.audit_trail(action); -CREATE INDEX idx_audit_resource ON kg_logs.audit_trail(resource_type, resource_id); - --- API Performance Metrics -CREATE TABLE kg_logs.api_metrics ( - id SERIAL PRIMARY KEY, - timestamp TIMESTAMPTZ NOT NULL DEFAULT NOW(), - endpoint VARCHAR(200) NOT NULL, - method VARCHAR(10) NOT NULL, - status_code INTEGER NOT NULL, - duration_ms NUMERIC(10,2) NOT NULL, - client_id VARCHAR(100), - error_message TEXT -); -CREATE INDEX idx_api_metrics_timestamp ON kg_logs.api_metrics(timestamp DESC); -CREATE INDEX idx_api_metrics_endpoint ON kg_logs.api_metrics(endpoint); -CREATE INDEX idx_api_metrics_duration ON kg_logs.api_metrics(duration_ms DESC); +-- +-- Name: calculate_type_grounding_contribution(character varying); Type: FUNCTION; Schema: kg_api; Owner: - +-- --- Job Events -CREATE TABLE kg_logs.job_events ( - id SERIAL PRIMARY KEY, - job_id VARCHAR(50) NOT NULL, - timestamp TIMESTAMPTZ NOT NULL DEFAULT NOW(), - event_type VARCHAR(50) NOT NULL, - details JSONB -); +CREATE FUNCTION kg_api.calculate_type_grounding_contribution(p_relationship_type character varying) RETURNS double precision + LANGUAGE plpgsql + AS $$ +DECLARE + v_contribution FLOAT; +BEGIN + -- Placeholder implementation + -- Full implementation in Python (EmbeddingWorker.calculate_grounding_contribution) + -- This function is here for documentation and future SQL-based optimization + + -- For now, return NULL to indicate calculation needed + RETURN NULL; +END; +$$; -CREATE INDEX idx_job_events_job_id ON kg_logs.job_events(job_id); -CREATE INDEX idx_job_events_timestamp ON kg_logs.job_events(timestamp DESC); --- System Health Checks -CREATE TABLE kg_logs.health_checks ( - id SERIAL PRIMARY KEY, - timestamp TIMESTAMPTZ NOT NULL DEFAULT NOW(), - service VARCHAR(50) NOT NULL, - status VARCHAR(50) NOT NULL CHECK (status IN ('healthy', 'degraded', 'down')), - metrics JSONB -); +-- +-- Name: FUNCTION calculate_type_grounding_contribution(p_relationship_type character varying); Type: COMMENT; Schema: kg_api; Owner: - +-- -CREATE INDEX idx_health_timestamp ON kg_logs.health_checks(timestamp DESC); -CREATE INDEX idx_health_service ON kg_logs.health_checks(service); +COMMENT ON FUNCTION kg_api.calculate_type_grounding_contribution(p_relationship_type character varying) IS 'ADR-046: Calculates grounding contribution for a relationship type. Currently implemented in Python EmbeddingWorker.'; --- ============================================================================ --- MAINTENANCE FUNCTIONS --- ============================================================================ --- Refresh hot edges materialized view -CREATE OR REPLACE FUNCTION refresh_hot_edges() -RETURNS void AS $$ -BEGIN - REFRESH MATERIALIZED VIEW CONCURRENTLY kg_api.hot_edges; -END; -$$ LANGUAGE plpgsql; +-- +-- Name: complete_graph_epoch(bigint, text); Type: FUNCTION; Schema: kg_api; Owner: - +-- --- Refresh hot concepts materialized view -CREATE OR REPLACE FUNCTION refresh_hot_concepts() -RETURNS void AS $$ +CREATE FUNCTION kg_api.complete_graph_epoch(p_event_id bigint, p_status text DEFAULT 'completed'::text) RETURNS void + LANGUAGE plpgsql + AS $$ BEGIN - REFRESH MATERIALIZED VIEW CONCURRENTLY kg_api.hot_concepts; -END; -$$ LANGUAGE plpgsql; + IF p_status NOT IN ('completed', 'failed') THEN + RAISE EXCEPTION 'complete_graph_epoch: status must be completed or failed, got %', p_status; + END IF; --- Clean expired sessions -CREATE OR REPLACE FUNCTION cleanup_expired_sessions() -RETURNS INTEGER AS $$ -DECLARE - deleted_count INTEGER; -BEGIN - DELETE FROM kg_api.sessions WHERE expires_at < NOW(); - GET DIAGNOSTICS deleted_count = ROW_COUNT; - RETURN deleted_count; + UPDATE kg_api.graph_epochs + SET status = p_status + WHERE event_id = p_event_id; END; -$$ LANGUAGE plpgsql; +$$; --- Archive old jobs -CREATE OR REPLACE FUNCTION archive_old_jobs(days_threshold INTEGER DEFAULT 30) -RETURNS INTEGER AS $$ -DECLARE - archived_count INTEGER; -BEGIN - INSERT INTO kg_logs.job_events (job_id, timestamp, event_type, details) - SELECT - job_id, - completed_at, - 'archived', - jsonb_build_object( - 'status', status, - 'ontology', ontology, - 'result', result - ) - FROM kg_api.ingestion_jobs - WHERE completed_at < NOW() - (days_threshold || ' days')::INTERVAL - AND status IN ('completed', 'failed'); - DELETE FROM kg_api.ingestion_jobs - WHERE completed_at < NOW() - (days_threshold || ' days')::INTERVAL - AND status IN ('completed', 'failed'); +-- +-- Name: FUNCTION complete_graph_epoch(p_event_id bigint, p_status text); Type: COMMENT; Schema: kg_api; Owner: - +-- - GET DIAGNOSTICS archived_count = ROW_COUNT; - RETURN archived_count; -END; -$$ LANGUAGE plpgsql; +COMMENT ON FUNCTION kg_api.complete_graph_epoch(p_event_id bigint, p_status text) IS 'ADR-207/#384: resolve an epoch event recorded by record_graph_epoch(). Call with completed when the mutation transaction commits, failed when it aborts/rolls back. Until called, the event is in_progress and holds the committed watermark below its event_id.'; + + +-- +-- Name: delete_source_embeddings(text, text); Type: FUNCTION; Schema: kg_api; Owner: - +-- --- Expire old pruning recommendations (ADR-032) -CREATE OR REPLACE FUNCTION expire_old_recommendations(days_threshold INTEGER DEFAULT 7) -RETURNS INTEGER AS $$ +CREATE FUNCTION kg_api.delete_source_embeddings(p_source_id text, p_strategy text DEFAULT NULL::text) RETURNS integer + LANGUAGE plpgsql + AS $$ DECLARE - expired_count INTEGER; + v_deleted_count INTEGER; BEGIN - UPDATE kg_api.pruning_recommendations - SET status = 'expired' - WHERE status = 'pending' - AND created_at < NOW() - (days_threshold || ' days')::INTERVAL - AND expires_at IS NULL; + IF p_strategy IS NULL THEN + -- Delete all embeddings for source + DELETE FROM kg_api.source_embeddings + WHERE source_id = p_source_id; + ELSE + -- Delete embeddings for specific strategy + DELETE FROM kg_api.source_embeddings + WHERE source_id = p_source_id + AND chunk_strategy = p_strategy; + END IF; - GET DIAGNOSTICS expired_count = ROW_COUNT; - RETURN expired_count; + GET DIAGNOSTICS v_deleted_count = ROW_COUNT; + RETURN v_deleted_count; END; -$$ LANGUAGE plpgsql; +$$; -COMMENT ON FUNCTION expire_old_recommendations IS 'Expire pending recommendations older than threshold (ADR-032)'; --- Check if user has permission (ADR-028) -CREATE OR REPLACE FUNCTION kg_auth.has_permission( - p_user_id INTEGER, - p_resource_type VARCHAR, - p_action VARCHAR, - p_resource_id VARCHAR DEFAULT NULL -) -RETURNS BOOLEAN AS $$ -DECLARE - v_has_permission BOOLEAN := FALSE; -BEGIN - SELECT EXISTS ( - SELECT 1 - FROM kg_auth.user_roles ur - JOIN kg_auth.role_permissions rp ON ur.role_name = rp.role_name - WHERE ur.user_id = p_user_id - AND rp.resource_type = p_resource_type - AND rp.action = p_action - AND rp.scope_type = 'global' - AND rp.granted = TRUE - AND (ur.expires_at IS NULL OR ur.expires_at > NOW()) - ) INTO v_has_permission; +-- +-- Name: FUNCTION delete_source_embeddings(p_source_id text, p_strategy text); Type: COMMENT; Schema: kg_api; Owner: - +-- + +COMMENT ON FUNCTION kg_api.delete_source_embeddings(p_source_id text, p_strategy text) IS 'ADR-068: Delete source embeddings (optionally filtered by strategy) for regeneration'; - RETURN v_has_permission; -END; -$$ LANGUAGE plpgsql STABLE; -COMMENT ON FUNCTION kg_auth.has_permission IS 'Check if user has permission (simple global check - extend for scoped permissions)'; +-- +-- Name: get_aggressiveness_profile(character varying); Type: FUNCTION; Schema: kg_api; Owner: - +-- --- Get user's effective roles (ADR-028) -CREATE OR REPLACE FUNCTION kg_auth.get_user_roles(p_user_id INTEGER) -RETURNS TABLE ( - role_name VARCHAR, - scope_type VARCHAR, - scope_id VARCHAR -) AS $$ +CREATE FUNCTION kg_api.get_aggressiveness_profile(profile character varying) RETURNS TABLE(profile_name character varying, control_x1 double precision, control_y1 double precision, control_x2 double precision, control_y2 double precision, description text) + LANGUAGE plpgsql STABLE + AS $$ BEGIN RETURN QUERY SELECT - ur.role_name, - ur.scope_type, - ur.scope_id - FROM kg_auth.user_roles ur - WHERE ur.user_id = p_user_id - AND (ur.expires_at IS NULL OR ur.expires_at > NOW()); + p.profile_name, + p.control_x1, + p.control_y1, + p.control_x2, + p.control_y2, + p.description + FROM kg_api.aggressiveness_profiles p + WHERE p.profile_name = profile; END; -$$ LANGUAGE plpgsql STABLE; +$$; -COMMENT ON FUNCTION kg_auth.get_user_roles IS 'Get all active roles for a user'; --- ============================================================================ --- AUDIT TRIGGERS (ADR-028) --- ============================================================================ +-- +-- Name: FUNCTION get_aggressiveness_profile(profile character varying); Type: COMMENT; Schema: kg_api; Owner: - +-- + +COMMENT ON FUNCTION kg_api.get_aggressiveness_profile(profile character varying) IS 'Retrieve Bezier curve parameters for aggressiveness profile'; + + +-- +-- Name: get_committed_epoch(); Type: FUNCTION; Schema: kg_api; Owner: - +-- + +CREATE FUNCTION kg_api.get_committed_epoch() RETURNS bigint + LANGUAGE plpgsql STABLE + AS $$ +DECLARE + v_first_in_flight BIGINT; + v_max BIGINT; +BEGIN + -- Lowest event still in flight: every event below it is RESOLVED (whether + -- it committed or failed), and no event above it may be exposed yet (the + -- in-flight one is logically earlier and may still be committing). + SELECT MIN(event_id) INTO v_first_in_flight + FROM kg_api.graph_epochs + WHERE status = 'in_progress'; + + IF v_first_in_flight IS NOT NULL THEN + RETURN v_first_in_flight - 1; + END IF; + + -- Nothing in flight: the watermark is the highest event id recorded, + -- counting BOTH completed and failed events. Failed events count on + -- purpose: ingestion commits per-chunk, so a job that aborted or was + -- cancelled mid-run may have already committed partial graph changes. + -- Excluding it (MAX WHERE completed) would hide those commits and read + -- false-FRESH. A cleanly-rolled-back failed event that committed nothing + -- costs at most one benign no-op rebuild — the safe direction to err. + -- The completed/failed distinction is for analytics (#384: drop zero- + -- instance jobs from hot/stale signals), NOT for this watermark. + -- Monotonic: event_id is BIGSERIAL, so a new in-flight event always has a + -- higher id than the resolved prefix and the watermark never regresses. + SELECT MAX(event_id) INTO v_max FROM kg_api.graph_epochs; + RETURN COALESCE(v_max, 0); +END; +$$; + + +-- +-- Name: FUNCTION get_committed_epoch(); Type: COMMENT; Schema: kg_api; Owner: - +-- + +COMMENT ON FUNCTION kg_api.get_committed_epoch() IS 'ADR-207: canonical freshness clock. Committed-prefix watermark: MIN(in_flight)-1 if any job is in flight, else MAX(event_id) over all resolved events (completed AND failed both count — per-chunk commits mean a failed job may have mutated the graph). Monotonic high-water signal: a derivation whose stamp < this value is provably stale. Out-of-order completion safe.'; + + +-- +-- Name: get_graph_epoch(); Type: FUNCTION; Schema: kg_api; Owner: - +-- + +CREATE FUNCTION kg_api.get_graph_epoch() RETURNS bigint + LANGUAGE plpgsql STABLE + AS $$ +DECLARE + v_epoch BIGINT; +BEGIN + SELECT counter INTO v_epoch + FROM public.graph_metrics + WHERE metric_name = 'graph_change_counter'; + + RETURN COALESCE(v_epoch, 0); +END; +$$; + + +-- +-- Name: FUNCTION get_graph_epoch(); Type: COMMENT; Schema: kg_api; Owner: - +-- + +COMMENT ON FUNCTION kg_api.get_graph_epoch() IS 'ADR-079 graph_change_counter (composite count checksum). ADR-207: a ONE-DIRECTIONAL DIRTY HINT ONLY — counter != stamp reliably proves the graph changed, but counter == stamp does NOT prove freshness (the checksum is non-injective; ADR-203). Use kg_api.get_committed_epoch() for any freshness/staleness decision. Do not reintroduce this as a freshness signal.'; + + +-- +-- Name: get_platform_config(character varying); Type: FUNCTION; Schema: kg_api; Owner: - +-- + +CREATE FUNCTION kg_api.get_platform_config(p_key character varying) RETURNS text + LANGUAGE plpgsql + AS $$ +DECLARE + v_value TEXT; +BEGIN + SELECT value INTO v_value FROM kg_api.platform_config WHERE key = p_key; + RETURN v_value; +END; +$$; + + +-- +-- Name: get_source_embedding_count(text, text); Type: FUNCTION; Schema: kg_api; Owner: - +-- + +CREATE FUNCTION kg_api.get_source_embedding_count(p_source_id text, p_strategy text DEFAULT NULL::text) RETURNS integer + LANGUAGE plpgsql STABLE + AS $$ +DECLARE + v_count INTEGER; +BEGIN + IF p_strategy IS NULL THEN + SELECT COUNT(*) + INTO v_count + FROM kg_api.source_embeddings + WHERE source_id = p_source_id; + ELSE + SELECT COUNT(*) + INTO v_count + FROM kg_api.source_embeddings + WHERE source_id = p_source_id + AND chunk_strategy = p_strategy; + END IF; + + RETURN COALESCE(v_count, 0); +END; +$$; + + +-- +-- Name: FUNCTION get_source_embedding_count(p_source_id text, p_strategy text); Type: COMMENT; Schema: kg_api; Owner: - +-- + +COMMENT ON FUNCTION kg_api.get_source_embedding_count(p_source_id text, p_strategy text) IS 'ADR-068: Count embeddings for a source (optionally filtered by strategy)'; + + +-- +-- Name: get_vocab_config(character varying, text); Type: FUNCTION; Schema: kg_api; Owner: - +-- + +CREATE FUNCTION kg_api.get_vocab_config(lookup_key character varying, fallback text DEFAULT NULL::text) RETURNS text + LANGUAGE plpgsql STABLE + AS $$ +DECLARE + result TEXT; +BEGIN + SELECT value INTO result + FROM kg_api.vocabulary_config + WHERE key = lookup_key; + + IF result IS NULL THEN + RETURN fallback; + END IF; + + RETURN result; +END; +$$; + + +-- +-- Name: FUNCTION get_vocab_config(lookup_key character varying, fallback text); Type: COMMENT; Schema: kg_api; Owner: - +-- + +COMMENT ON FUNCTION kg_api.get_vocab_config(lookup_key character varying, fallback text) IS 'Retrieve vocabulary configuration value with optional fallback'; + + +-- +-- Name: is_artifact_fresh(integer); Type: FUNCTION; Schema: kg_api; Owner: - +-- + +CREATE FUNCTION kg_api.is_artifact_fresh(p_artifact_id integer) RETURNS boolean + LANGUAGE plpgsql STABLE + AS $$ +DECLARE + v_artifact_epoch INTEGER; + v_current_epoch INTEGER; +BEGIN + SELECT graph_epoch INTO v_artifact_epoch + FROM kg_api.artifacts + WHERE id = p_artifact_id; + + IF v_artifact_epoch IS NULL THEN + RETURN FALSE; -- Artifact not found + END IF; + + v_current_epoch := kg_api.get_graph_epoch(); + + RETURN v_artifact_epoch = v_current_epoch; +END; +$$; + + +-- +-- Name: FUNCTION is_artifact_fresh(p_artifact_id integer); Type: COMMENT; Schema: kg_api; Owner: - +-- + +COMMENT ON FUNCTION kg_api.is_artifact_fresh(p_artifact_id integer) IS 'Check if artifact is fresh (graph unchanged since creation)'; + + +-- +-- Name: link_job_to_artifact(text, integer); Type: FUNCTION; Schema: kg_api; Owner: - +-- + +CREATE FUNCTION kg_api.link_job_to_artifact(p_job_id text, p_artifact_id integer) RETURNS void + LANGUAGE plpgsql + AS $$ +BEGIN + UPDATE kg_api.jobs + SET artifact_id = p_artifact_id + WHERE job_id = p_job_id; +END; +$$; + + +-- +-- Name: FUNCTION link_job_to_artifact(p_job_id text, p_artifact_id integer); Type: COMMENT; Schema: kg_api; Owner: - +-- + +COMMENT ON FUNCTION kg_api.link_job_to_artifact(p_job_id text, p_artifact_id integer) IS 'Link a job to its created artifact (ADR-083)'; + + +-- +-- Name: mark_embeddings_stale_for_model(character varying); Type: FUNCTION; Schema: kg_api; Owner: - +-- + +CREATE FUNCTION kg_api.mark_embeddings_stale_for_model(p_old_model character varying) RETURNS integer + LANGUAGE plpgsql + AS $$ +DECLARE + v_updated_count INTEGER; +BEGIN + UPDATE kg_api.relationship_vocabulary + SET embedding_validation_status = 'stale' + WHERE embedding_model = p_old_model + AND embedding IS NOT NULL + AND is_active = TRUE; + + GET DIAGNOSTICS v_updated_count = ROW_COUNT; + + RETURN v_updated_count; +END; +$$; + + +-- +-- Name: FUNCTION mark_embeddings_stale_for_model(p_old_model character varying); Type: COMMENT; Schema: kg_api; Owner: - +-- + +COMMENT ON FUNCTION kg_api.mark_embeddings_stale_for_model(p_old_model character varying) IS 'ADR-045: Marks all embeddings for a model as stale when model is changed. Returns count of marked types.'; + + +-- +-- Name: prevent_builtin_profile_deletion(); Type: FUNCTION; Schema: kg_api; Owner: - +-- + +CREATE FUNCTION kg_api.prevent_builtin_profile_deletion() RETURNS trigger + LANGUAGE plpgsql + AS $$ +BEGIN + IF OLD.is_builtin = TRUE THEN + RAISE EXCEPTION 'Cannot delete builtin aggressiveness profile: %', OLD.profile_name; + END IF; + RETURN OLD; +END; +$$; + + +-- +-- Name: record_graph_epoch(text, text, jsonb); Type: FUNCTION; Schema: kg_api; Owner: - +-- + +CREATE FUNCTION kg_api.record_graph_epoch(p_kind text, p_actor text DEFAULT NULL::text, p_metadata jsonb DEFAULT '{}'::jsonb) RETURNS bigint + LANGUAGE plpgsql + AS $$ +DECLARE + v_event_id BIGINT; + v_counter BIGINT; +BEGIN + SELECT counter INTO v_counter + FROM public.graph_metrics + WHERE metric_name = 'graph_change_counter'; + + INSERT INTO kg_api.graph_epochs (kind, actor, counter_after, metadata) + VALUES (p_kind, p_actor, v_counter, p_metadata) + RETURNING event_id INTO v_event_id; + + RETURN v_event_id; +END; +$$; + + +-- +-- Name: FUNCTION record_graph_epoch(p_kind text, p_actor text, p_metadata jsonb); Type: COMMENT; Schema: kg_api; Owner: - +-- + +COMMENT ON FUNCTION kg_api.record_graph_epoch(p_kind text, p_actor text, p_metadata jsonb) IS 'ADR-203: Record a new graph-mutation event and return its event_id. Call at the start of an ingestion job (or other mutation transaction) so the returned id can tag nodes created during the unit of work.'; + + +-- +-- Name: set_platform_config(character varying, text, character varying); Type: FUNCTION; Schema: kg_api; Owner: - +-- + +CREATE FUNCTION kg_api.set_platform_config(p_key character varying, p_value text, p_updated_by character varying DEFAULT 'system'::character varying) RETURNS void + LANGUAGE plpgsql + AS $$ +BEGIN + INSERT INTO kg_api.platform_config (key, value, updated_at, updated_by) + VALUES (p_key, p_value, NOW(), p_updated_by) + ON CONFLICT (key) DO UPDATE SET + value = EXCLUDED.value, + updated_at = NOW(), + updated_by = EXCLUDED.updated_by; +END; +$$; + + +-- +-- Name: update_aggressiveness_profiles_timestamp(); Type: FUNCTION; Schema: kg_api; Owner: - +-- + +CREATE FUNCTION kg_api.update_aggressiveness_profiles_timestamp() RETURNS trigger + LANGUAGE plpgsql + AS $$ +BEGIN + NEW.updated_at = NOW(); + RETURN NEW; +END; +$$; + + +-- +-- Name: update_ai_extraction_config_timestamp(); Type: FUNCTION; Schema: kg_api; Owner: - +-- + +CREATE FUNCTION kg_api.update_ai_extraction_config_timestamp() RETURNS trigger + LANGUAGE plpgsql + AS $$ +BEGIN + NEW.updated_at = CURRENT_TIMESTAMP; + RETURN NEW; +END; +$$; + + +-- +-- Name: update_embedding_config_timestamp(); Type: FUNCTION; Schema: kg_api; Owner: - +-- + +CREATE FUNCTION kg_api.update_embedding_config_timestamp() RETURNS trigger + LANGUAGE plpgsql + AS $$ +BEGIN + NEW.updated_at = CURRENT_TIMESTAMP; + RETURN NEW; +END; +$$; + --- Audit role assignments -CREATE OR REPLACE FUNCTION kg_auth.audit_role_assignment() -RETURNS TRIGGER AS $$ +-- +-- Name: update_embedding_profile_timestamp(); Type: FUNCTION; Schema: kg_api; Owner: - +-- + +CREATE FUNCTION kg_api.update_embedding_profile_timestamp() RETURNS trigger + LANGUAGE plpgsql + AS $$ +BEGIN + NEW.updated_at = CURRENT_TIMESTAMP; + RETURN NEW; +END; +$$; + + +-- +-- Name: update_query_definition_timestamp(); Type: FUNCTION; Schema: kg_api; Owner: - +-- + +CREATE FUNCTION kg_api.update_query_definition_timestamp() RETURNS trigger + LANGUAGE plpgsql + AS $$ +BEGIN + NEW.updated_at = NOW(); + RETURN NEW; +END; +$$; + + +-- +-- Name: update_source_embedding_timestamp(); Type: FUNCTION; Schema: kg_api; Owner: - +-- + +CREATE FUNCTION kg_api.update_source_embedding_timestamp() RETURNS trigger + LANGUAGE plpgsql + AS $$ +BEGIN + NEW.updated_at = CURRENT_TIMESTAMP; + RETURN NEW; +END; +$$; + + +-- +-- Name: validate_embedding(jsonb, integer); Type: FUNCTION; Schema: kg_api; Owner: - +-- + +CREATE FUNCTION kg_api.validate_embedding(p_embedding jsonb, p_expected_dimensions integer DEFAULT 768) RETURNS boolean + LANGUAGE plpgsql IMMUTABLE + AS $$ +DECLARE + v_array_length INTEGER; +BEGIN + -- Check if embedding is a JSON array + IF jsonb_typeof(p_embedding) != 'array' THEN + RETURN FALSE; + END IF; + + -- Check dimensionality + v_array_length := jsonb_array_length(p_embedding); + IF v_array_length != p_expected_dimensions THEN + RETURN FALSE; + END IF; + + -- Basic validation passed + RETURN TRUE; +END; +$$; + + +-- +-- Name: FUNCTION validate_embedding(p_embedding jsonb, p_expected_dimensions integer); Type: COMMENT; Schema: kg_api; Owner: - +-- + +COMMENT ON FUNCTION kg_api.validate_embedding(p_embedding jsonb, p_expected_dimensions integer) IS 'ADR-045: Validates embedding structure (JSON array of expected dimensions)'; + + +-- +-- Name: audit_role_assignment(); Type: FUNCTION; Schema: kg_auth; Owner: - +-- + +CREATE FUNCTION kg_auth.audit_role_assignment() RETURNS trigger + LANGUAGE plpgsql + AS $$ BEGIN IF TG_OP = 'INSERT' THEN INSERT INTO kg_logs.audit_trail (user_id, action, resource_type, resource_id, details, outcome) @@ -781,215 +874,7449 @@ BEGIN END IF; RETURN NULL; END; -$$ LANGUAGE plpgsql; - -CREATE TRIGGER audit_user_roles - AFTER INSERT OR DELETE ON kg_auth.user_roles - FOR EACH ROW EXECUTE FUNCTION kg_auth.audit_role_assignment(); - --- ============================================================================ --- SEED DATA --- ============================================================================ +$$; --- Seed builtin resources (ADR-028) -INSERT INTO kg_auth.resources (resource_type, description, available_actions, supports_scoping, registered_by) -VALUES - ('concepts', 'Knowledge graph concepts and nodes', ARRAY['read', 'write', 'delete'], FALSE, 'system'), - ('vocabulary', 'Relationship vocabulary management', ARRAY['read', 'write', 'approve', 'delete'], FALSE, 'system'), - ('jobs', 'Ingestion job management', ARRAY['read', 'write', 'approve', 'delete'], FALSE, 'system'), - ('users', 'User account management', ARRAY['read', 'write', 'delete'], FALSE, 'system'), - ('roles', 'Role and permission management', ARRAY['read', 'write', 'delete'], FALSE, 'system'), - ('resources', 'Resource type registration', ARRAY['read', 'write', 'delete'], FALSE, 'system'), - ('workers', 'Worker lane management and job control (ADR-100)', ARRAY['view', 'manage'], FALSE, 'system') -ON CONFLICT (resource_type) DO NOTHING; - --- Seed builtin roles (ADR-028) -INSERT INTO kg_auth.roles (role_name, display_name, description, is_builtin, is_active) -VALUES - ('read_only', 'Read Only', 'Read access to public resources', TRUE, TRUE), - ('contributor', 'Contributor', 'Can create and modify content', TRUE, TRUE), - ('curator', 'Curator', 'Can approve and manage content', TRUE, TRUE), - ('admin', 'Administrator', 'Full system access', TRUE, TRUE) -ON CONFLICT (role_name) DO NOTHING; - --- Seed role permissions (ADR-028) -INSERT INTO kg_auth.role_permissions (role_name, resource_type, action, scope_type, granted) -VALUES - -- read_only role - ('read_only', 'concepts', 'read', 'global', TRUE), - ('read_only', 'vocabulary', 'read', 'global', TRUE), - ('read_only', 'jobs', 'read', 'global', TRUE), - -- contributor role - ('contributor', 'concepts', 'read', 'global', TRUE), - ('contributor', 'concepts', 'write', 'global', TRUE), - ('contributor', 'vocabulary', 'read', 'global', TRUE), - ('contributor', 'jobs', 'read', 'global', TRUE), - ('contributor', 'jobs', 'write', 'global', TRUE), - -- curator role - ('curator', 'concepts', 'read', 'global', TRUE), - ('curator', 'concepts', 'write', 'global', TRUE), - ('curator', 'vocabulary', 'read', 'global', TRUE), - ('curator', 'vocabulary', 'write', 'global', TRUE), - ('curator', 'vocabulary', 'approve', 'global', TRUE), - ('curator', 'jobs', 'read', 'global', TRUE), - ('curator', 'jobs', 'approve', 'global', TRUE), - ('curator', 'roles', 'read', 'global', TRUE), - ('curator', 'resources', 'read', 'global', TRUE), - -- admin role - ('admin', 'concepts', 'read', 'global', TRUE), - ('admin', 'concepts', 'write', 'global', TRUE), - ('admin', 'concepts', 'delete', 'global', TRUE), - ('admin', 'vocabulary', 'read', 'global', TRUE), - ('admin', 'vocabulary', 'write', 'global', TRUE), - ('admin', 'vocabulary', 'approve', 'global', TRUE), - ('admin', 'vocabulary', 'delete', 'global', TRUE), - ('admin', 'jobs', 'read', 'global', TRUE), - ('admin', 'jobs', 'write', 'global', TRUE), - ('admin', 'jobs', 'approve', 'global', TRUE), - ('admin', 'jobs', 'delete', 'global', TRUE), - ('admin', 'users', 'read', 'global', TRUE), - ('admin', 'users', 'write', 'global', TRUE), - ('admin', 'users', 'delete', 'global', TRUE), - ('admin', 'roles', 'read', 'global', TRUE), - ('admin', 'roles', 'write', 'global', TRUE), - ('admin', 'roles', 'delete', 'global', TRUE), - ('admin', 'resources', 'read', 'global', TRUE), - ('admin', 'resources', 'write', 'global', TRUE), - ('admin', 'resources', 'delete', 'global', TRUE), - -- admin: worker management (ADR-100) - ('admin', 'workers', 'view', 'global', TRUE), - ('admin', 'workers', 'manage', 'global', TRUE) -ON CONFLICT DO NOTHING; - --- Seed builtin relationship vocabulary (ADR-025) -INSERT INTO kg_api.relationship_vocabulary (relationship_type, description, category, is_builtin, is_active) -VALUES - ('IMPLIES', 'One concept logically implies another', 'logical', TRUE, TRUE), - ('SUPPORTS', 'One concept provides evidence for another', 'evidential', TRUE, TRUE), - ('CONTRADICTS', 'One concept contradicts another', 'logical', TRUE, TRUE), - ('RESULTS_FROM', 'One concept is a consequence of another', 'causation', TRUE, TRUE), - ('ENABLES', 'One concept makes another possible', 'causation', TRUE, TRUE), - ('REQUIRES', 'One concept needs another to exist or function', 'dependency', TRUE, TRUE), - ('INFLUENCES', 'One concept affects another without direct causation', 'causation', TRUE, TRUE), - ('COMPLEMENTS', 'One concept works together with another', 'composition', TRUE, TRUE), - ('OVERLAPS', 'Two concepts share common elements', 'semantic', TRUE, TRUE), - ('EXTENDS', 'One concept builds upon another', 'composition', TRUE, TRUE), - ('SPECIALIZES', 'One concept is a specific instance of another', 'hierarchical', TRUE, TRUE), - ('GENERALIZES', 'One concept is a broader category of another', 'hierarchical', TRUE, TRUE), - ('PART_OF', 'One concept is a component of another', 'composition', TRUE, TRUE), - ('HAS_PART', 'One concept contains another as a component', 'composition', TRUE, TRUE), - ('PRECEDES', 'One concept comes before another in sequence', 'temporal', TRUE, TRUE), - ('FOLLOWS', 'One concept comes after another in sequence', 'temporal', TRUE, TRUE), - ('CAUSES', 'One concept directly causes another', 'causation', TRUE, TRUE), - ('PREVENTS', 'One concept stops another from occurring', 'causation', TRUE, TRUE), - ('RELATED_TO', 'Generic relationship when specific type unclear', 'semantic', TRUE, TRUE), - ('OPPOSITE_OF', 'One concept is the inverse or contrary of another', 'semantic', TRUE, TRUE), - ('ANALOGOUS_TO', 'One concept is similar in structure or function to another', 'semantic', TRUE, TRUE), - ('DEPENDS_ON', 'One concept relies on another for functionality', 'dependency', TRUE, TRUE), - ('INSTANTIATES', 'One concept is a concrete example of another', 'hierarchical', TRUE, TRUE), - ('DEFINES', 'One concept provides the definition or meaning of another', 'semantic', TRUE, TRUE), - ('IMPLEMENTED_BY', 'One concept is realized through another', 'implementation', TRUE, TRUE), - ('IMPLEMENTS', 'One concept realizes or executes another', 'implementation', TRUE, TRUE), - ('COMPOSED_OF', 'One concept is made up of multiple instances of another', 'composition', TRUE, TRUE), - ('DERIVED_FROM', 'One concept originates from another', 'derivation', TRUE, TRUE), - ('PRODUCES', 'One concept creates or generates another', 'causation', TRUE, TRUE), - ('CONSUMES', 'One concept uses or depletes another', 'dependency', TRUE, TRUE) -ON CONFLICT (relationship_type) DO NOTHING; - --- Seed vocabulary configuration (ADR-032) -INSERT INTO kg_api.vocabulary_config (key, value, description, updated_by) -VALUES - ('vocab_min', '30', 'Minimum vocabulary size (protected core types)', 'system'), - ('vocab_max', '90', 'Maximum vocabulary size (soft limit)', 'system'), - ('vocab_emergency', '200', 'Emergency threshold (aggressive pruning)', 'system'), - ('aggressiveness_profile', 'aggressive', 'Bezier curve profile for pruning aggressiveness', 'system'), - ('pruning_mode', 'hitl', 'Decision mode: naive, hitl, aitl', 'system'), - ('auto_expand_enabled', 'false', 'Enable automatic vocabulary expansion', 'system'), - ('synonym_threshold_strong', '0.90', 'Strong synonym threshold (auto-merge)', 'system'), - ('synonym_threshold_moderate', '0.70', 'Moderate synonym threshold (review)', 'system'), - ('low_value_threshold', '1.0', 'Value score threshold for pruning consideration', 'system') -ON CONFLICT (key) DO NOTHING; - --- Seed default worker lanes (ADR-100) -INSERT INTO kg_api.worker_lanes (name, job_types, max_slots, poll_interval_ms, stale_timeout_minutes) -VALUES - ('interactive', - ARRAY['ingestion', 'ingest_image', 'polarity'], - 2, 2000, 30), - ('maintenance', - ARRAY['projection', 'vocab_refresh', 'epistemic_remeasurement', 'ontology_annealing', 'proposal_execution'], - 1, 15000, 60), - ('system', - ARRAY['restore', 'vocab_consolidate', 'artifact_cleanup', 'source_embedding'], - 1, 30000, 120) -ON CONFLICT (name) DO NOTHING; - --- Seed initial ontology version (ADR-026) -INSERT INTO kg_api.ontology_versions ( - version_number, - created_by, - change_summary, - vocabulary_snapshot, - types_added, - backward_compatible -) -SELECT - '2.0.0', - 'system', - 'Baseline schema consolidation: 30 builtin relationship types', - jsonb_agg(row_to_json(rv)), - ARRAY(SELECT relationship_type FROM kg_api.relationship_vocabulary WHERE is_builtin = TRUE), - TRUE -FROM kg_api.relationship_vocabulary rv -WHERE is_builtin = TRUE -ON CONFLICT (version_number) DO NOTHING; --- ============================================================================ --- Verification --- ============================================================================ +-- +-- Name: get_user_groups(integer); Type: FUNCTION; Schema: kg_auth; Owner: - +-- -DO $$ -DECLARE - schema_count INTEGER; - table_count INTEGER; - vocab_count INTEGER; +CREATE FUNCTION kg_auth.get_user_groups(p_user_id integer) RETURNS TABLE(group_id integer, group_name character varying) + LANGUAGE plpgsql STABLE + AS $$ BEGIN - SELECT COUNT(*) INTO schema_count - FROM information_schema.schemata - WHERE schema_name IN ('kg_api', 'kg_auth', 'kg_logs'); + -- Return explicit group memberships + RETURN QUERY + SELECT g.id, g.group_name + FROM kg_auth.groups g + JOIN kg_auth.user_groups ug ON g.id = ug.group_id + WHERE ug.user_id = p_user_id - SELECT COUNT(*) INTO table_count - FROM information_schema.tables - WHERE table_schema IN ('kg_api', 'kg_auth', 'kg_logs'); + UNION - SELECT COUNT(*) INTO vocab_count - FROM kg_api.relationship_vocabulary - WHERE is_builtin = TRUE; + -- Always include public group for all authenticated users + SELECT 1, 'public'::VARCHAR; +END; +$$; - RAISE NOTICE '========================================'; - RAISE NOTICE 'Baseline Schema Initialization Complete'; - RAISE NOTICE '========================================'; - RAISE NOTICE 'Version: 2.0.0 (Consolidated Baseline)'; - RAISE NOTICE 'Date: 2025-10-16'; - RAISE NOTICE ''; - RAISE NOTICE 'Schemas created: %', schema_count; - RAISE NOTICE 'Tables created: %', table_count; - RAISE NOTICE 'Builtin vocabulary types: %', vocab_count; - RAISE NOTICE 'Initial ontology version: 2.0.0'; - RAISE NOTICE ''; - RAISE NOTICE 'Includes:'; - RAISE NOTICE ' - Apache AGE graph database'; - RAISE NOTICE ' - Multi-schema architecture (ADR-024)'; - RAISE NOTICE ' - Dynamic RBAC system (ADR-028)'; - RAISE NOTICE ' - Vocabulary management (ADR-025, ADR-026, ADR-032)'; - RAISE NOTICE ' - Migration tracking system (ADR-040)'; - RAISE NOTICE '========================================'; -END $$; --- ============================================================================ --- Record Baseline Migration (ADR-040) --- ============================================================================ +-- +-- Name: FUNCTION get_user_groups(p_user_id integer); Type: COMMENT; Schema: kg_auth; Owner: - +-- + +COMMENT ON FUNCTION kg_auth.get_user_groups(p_user_id integer) IS 'Get all groups for user including implicit public membership'; + + +-- +-- Name: get_user_roles(integer); Type: FUNCTION; Schema: kg_auth; Owner: - +-- + +CREATE FUNCTION kg_auth.get_user_roles(p_user_id integer) RETURNS TABLE(role_name character varying, scope_type character varying, scope_id character varying) + LANGUAGE plpgsql STABLE + AS $$ +BEGIN + RETURN QUERY + SELECT + ur.role_name, + ur.scope_type, + ur.scope_id + FROM kg_auth.user_roles ur + WHERE ur.user_id = p_user_id + AND (ur.expires_at IS NULL OR ur.expires_at > NOW()); +END; +$$; + + +-- +-- Name: FUNCTION get_user_roles(p_user_id integer); Type: COMMENT; Schema: kg_auth; Owner: - +-- + +COMMENT ON FUNCTION kg_auth.get_user_roles(p_user_id integer) IS 'Get all active roles for a user'; + + +-- +-- Name: has_access(integer, character varying, character varying, character varying); Type: FUNCTION; Schema: kg_auth; Owner: - +-- + +CREATE FUNCTION kg_auth.has_access(p_user_id integer, p_resource_type character varying, p_resource_id character varying, p_permission character varying DEFAULT 'read'::character varying) RETURNS boolean + LANGUAGE plpgsql STABLE + AS $$ +DECLARE + v_has_access BOOLEAN := FALSE; +BEGIN + -- Check 1: Is user the owner? (requires owner_id on resource - checked by caller) + -- This function doesn't know about ownership, caller should check first + + -- Check 2: Does user have a direct grant? + SELECT EXISTS ( + SELECT 1 FROM kg_auth.resource_grants + WHERE resource_type = p_resource_type + AND resource_id = p_resource_id + AND principal_type = 'user' + AND principal_id = p_user_id + AND permission IN (p_permission, 'admin') -- admin implies all permissions + ) INTO v_has_access; + + IF v_has_access THEN + RETURN TRUE; + END IF; + + -- Check 3: Is user in a group with a grant? + SELECT EXISTS ( + SELECT 1 FROM kg_auth.user_groups ug + JOIN kg_auth.resource_grants rg + ON rg.principal_type = 'group' AND rg.principal_id = ug.group_id + WHERE ug.user_id = p_user_id + AND rg.resource_type = p_resource_type + AND rg.resource_id = p_resource_id + AND rg.permission IN (p_permission, 'admin') + ) INTO v_has_access; + + IF v_has_access THEN + RETURN TRUE; + END IF; + + -- Check 4: Is 'public' group granted access? (all authenticated users) + SELECT EXISTS ( + SELECT 1 FROM kg_auth.resource_grants + WHERE resource_type = p_resource_type + AND resource_id = p_resource_id + AND principal_type = 'group' + AND principal_id = 1 -- public group ID + AND permission IN (p_permission, 'admin') + ) INTO v_has_access; + + RETURN v_has_access; +END; +$$; + + +-- +-- Name: FUNCTION has_access(p_user_id integer, p_resource_type character varying, p_resource_id character varying, p_permission character varying); Type: COMMENT; Schema: kg_auth; Owner: - +-- + +COMMENT ON FUNCTION kg_auth.has_access(p_user_id integer, p_resource_type character varying, p_resource_id character varying, p_permission character varying) IS 'Check if user has access to specific resource via grants (ADR-082)'; + + +-- +-- Name: has_permission(integer, character varying, character varying, character varying); Type: FUNCTION; Schema: kg_auth; Owner: - +-- + +CREATE FUNCTION kg_auth.has_permission(p_user_id integer, p_resource_type character varying, p_action character varying, p_resource_id character varying DEFAULT NULL::character varying) RETURNS boolean + LANGUAGE plpgsql STABLE + AS $$ +DECLARE + v_has_permission BOOLEAN := FALSE; +BEGIN + SELECT EXISTS ( + SELECT 1 + FROM kg_auth.user_roles ur + JOIN kg_auth.role_permissions rp ON ur.role_name = rp.role_name + WHERE ur.user_id = p_user_id + AND rp.resource_type = p_resource_type + AND rp.action = p_action + AND rp.scope_type = 'global' + AND rp.granted = TRUE + AND (ur.expires_at IS NULL OR ur.expires_at > NOW()) + ) INTO v_has_permission; + + RETURN v_has_permission; +END; +$$; + + +-- +-- Name: FUNCTION has_permission(p_user_id integer, p_resource_type character varying, p_action character varying, p_resource_id character varying); Type: COMMENT; Schema: kg_auth; Owner: - +-- + +COMMENT ON FUNCTION kg_auth.has_permission(p_user_id integer, p_resource_type character varying, p_action character varying, p_resource_id character varying) IS 'Check if user has permission (simple global check - extend for scoped permissions)'; + + +-- +-- Name: get_counter_delta(character varying); Type: FUNCTION; Schema: public; Owner: - +-- + +CREATE FUNCTION public.get_counter_delta(p_metric_name character varying) RETURNS bigint + LANGUAGE plpgsql + AS $$ +DECLARE + v_delta BIGINT; +BEGIN + SELECT (counter - last_measured_counter) INTO v_delta + FROM graph_metrics + WHERE metric_name = p_metric_name; + + RETURN COALESCE(v_delta, 0); +END; +$$; + + +-- +-- Name: FUNCTION get_counter_delta(p_metric_name character varying); Type: COMMENT; Schema: public; Owner: - +-- + +COMMENT ON FUNCTION public.get_counter_delta(p_metric_name character varying) IS 'Get delta between current counter and last measured counter (staleness)'; + + +-- +-- Name: get_graph_snapshot(); Type: FUNCTION; Schema: public; Owner: - +-- + +CREATE FUNCTION public.get_graph_snapshot() RETURNS TABLE(concepts bigint, edges bigint, sources bigint, vocab_types bigint, total_objects bigint) + LANGUAGE plpgsql + AS $$ +BEGIN + RETURN QUERY SELECT + (SELECT COUNT(*) FROM knowledge_graph."Concept"), + (SELECT COUNT(*) FROM knowledge_graph._ag_label_edge), + (SELECT COUNT(*) FROM knowledge_graph."Source"), + (SELECT COUNT(*) FROM knowledge_graph."VocabType"), + (SELECT COUNT(*) FROM knowledge_graph."Concept") + + (SELECT COUNT(*) FROM knowledge_graph._ag_label_edge) + + (SELECT COUNT(*) FROM knowledge_graph."Source") + + (SELECT COUNT(*) FROM knowledge_graph."VocabType") + + (SELECT COUNT(*) FROM knowledge_graph."VocabCategory") + + (SELECT COUNT(*) FROM knowledge_graph."Instance") + + (SELECT COUNT(*) FROM knowledge_graph."DocumentMeta"); +END; +$$; + + +-- +-- Name: FUNCTION get_graph_snapshot(); Type: COMMENT; Schema: public; Owner: - +-- + +COMMENT ON FUNCTION public.get_graph_snapshot() IS 'Returns current graph object counts. Use total_objects to compare against stored +projection artifact metadata for cache invalidation. Fast (~5ms).'; + + +-- +-- Name: increment_counter(character varying); Type: FUNCTION; Schema: public; Owner: - +-- + +CREATE FUNCTION public.increment_counter(p_metric_name character varying) RETURNS void + LANGUAGE plpgsql + AS $$ +BEGIN + UPDATE graph_metrics + SET counter = counter + 1, + updated_at = CURRENT_TIMESTAMP + WHERE metric_name = p_metric_name; + + -- Create metric if it doesn't exist + IF NOT FOUND THEN + INSERT INTO graph_metrics (metric_name, counter, last_measured_counter, updated_at) + VALUES (p_metric_name, 1, 0, CURRENT_TIMESTAMP); + END IF; +END; +$$; + + +-- +-- Name: FUNCTION increment_counter(p_metric_name character varying); Type: COMMENT; Schema: public; Owner: - +-- + +COMMENT ON FUNCTION public.increment_counter(p_metric_name character varying) IS 'Increment any metric counter and update timestamp'; + + +-- +-- Name: mark_measurement_complete(character varying); Type: FUNCTION; Schema: public; Owner: - +-- + +CREATE FUNCTION public.mark_measurement_complete(p_metric_name character varying) RETURNS void + LANGUAGE plpgsql + AS $$ +BEGIN + UPDATE graph_metrics + SET last_measured_counter = counter, + last_measured_at = CURRENT_TIMESTAMP + WHERE metric_name = p_metric_name; +END; +$$; + + +-- +-- Name: FUNCTION mark_measurement_complete(p_metric_name character varying); Type: COMMENT; Schema: public; Owner: - +-- + +COMMENT ON FUNCTION public.mark_measurement_complete(p_metric_name character varying) IS 'Mark epistemic status measurement complete - resets delta to 0'; + + +-- +-- Name: refresh_graph_metrics(); Type: FUNCTION; Schema: public; Owner: - +-- + +CREATE FUNCTION public.refresh_graph_metrics() RETURNS TABLE(metric_name text, old_value bigint, new_value bigint, changed boolean) + LANGUAGE plpgsql + AS $$ +DECLARE + v_concept_count BIGINT; + v_edge_count BIGINT; + v_vocab_type_count BIGINT; + v_vocab_category_count BIGINT; + v_source_count BIGINT; + v_instance_count BIGINT; + v_document_count BIGINT; + v_graph_change_counter BIGINT; + v_old_value BIGINT; +BEGIN + -- Get current counts (fast parallel-safe queries) + SELECT COUNT(*) INTO v_concept_count FROM knowledge_graph."Concept"; + SELECT COUNT(*) INTO v_edge_count FROM knowledge_graph._ag_label_edge; + SELECT COUNT(*) INTO v_vocab_type_count FROM knowledge_graph."VocabType"; + SELECT COUNT(*) INTO v_vocab_category_count FROM knowledge_graph."VocabCategory"; + SELECT COUNT(*) INTO v_source_count FROM knowledge_graph."Source"; + SELECT COUNT(*) INTO v_instance_count FROM knowledge_graph."Instance"; + SELECT COUNT(*) INTO v_document_count FROM knowledge_graph."DocumentMeta"; + + -- Compute unified graph change counter (sum of all objects) + v_graph_change_counter := v_concept_count + v_edge_count + v_vocab_type_count + + v_vocab_category_count + v_source_count + v_instance_count + + v_document_count; + + -- Update concept_count + SELECT counter INTO v_old_value FROM public.graph_metrics WHERE public.graph_metrics.metric_name = 'concept_count'; + UPDATE public.graph_metrics SET counter = v_concept_count, updated_at = CURRENT_TIMESTAMP + WHERE public.graph_metrics.metric_name = 'concept_count'; + metric_name := 'concept_count'; old_value := v_old_value; new_value := v_concept_count; + changed := (v_old_value IS DISTINCT FROM v_concept_count); RETURN NEXT; + + -- Update total_edges + SELECT counter INTO v_old_value FROM public.graph_metrics WHERE public.graph_metrics.metric_name = 'total_edges'; + UPDATE public.graph_metrics SET counter = v_edge_count, updated_at = CURRENT_TIMESTAMP + WHERE public.graph_metrics.metric_name = 'total_edges'; + metric_name := 'total_edges'; old_value := v_old_value; new_value := v_edge_count; + changed := (v_old_value IS DISTINCT FROM v_edge_count); RETURN NEXT; + + -- Update vocabulary_type_count + SELECT counter INTO v_old_value FROM public.graph_metrics WHERE public.graph_metrics.metric_name = 'vocabulary_type_count'; + UPDATE public.graph_metrics SET counter = v_vocab_type_count, updated_at = CURRENT_TIMESTAMP + WHERE public.graph_metrics.metric_name = 'vocabulary_type_count'; + metric_name := 'vocabulary_type_count'; old_value := v_old_value; new_value := v_vocab_type_count; + changed := (v_old_value IS DISTINCT FROM v_vocab_type_count); RETURN NEXT; + + -- Update source_count + SELECT counter INTO v_old_value FROM public.graph_metrics WHERE public.graph_metrics.metric_name = 'source_count'; + UPDATE public.graph_metrics SET counter = v_source_count, updated_at = CURRENT_TIMESTAMP + WHERE public.graph_metrics.metric_name = 'source_count'; + metric_name := 'source_count'; old_value := v_old_value; new_value := v_source_count; + changed := (v_old_value IS DISTINCT FROM v_source_count); RETURN NEXT; + + -- Update instance_count + SELECT counter INTO v_old_value FROM public.graph_metrics WHERE public.graph_metrics.metric_name = 'instance_count'; + UPDATE public.graph_metrics SET counter = v_instance_count, updated_at = CURRENT_TIMESTAMP + WHERE public.graph_metrics.metric_name = 'instance_count'; + metric_name := 'instance_count'; old_value := v_old_value; new_value := v_instance_count; + changed := (v_old_value IS DISTINCT FROM v_instance_count); RETURN NEXT; + + -- Update graph_change_counter (unified counter for cache invalidation) + SELECT counter INTO v_old_value FROM public.graph_metrics WHERE public.graph_metrics.metric_name = 'graph_change_counter'; + UPDATE public.graph_metrics SET counter = v_graph_change_counter, updated_at = CURRENT_TIMESTAMP + WHERE public.graph_metrics.metric_name = 'graph_change_counter'; + metric_name := 'graph_change_counter'; old_value := v_old_value; new_value := v_graph_change_counter; + changed := (v_old_value IS DISTINCT FROM v_graph_change_counter); RETURN NEXT; + + -- Also update the legacy creation counters to match current state + -- (These were meant to be trigger-incremented but AGE bypasses triggers) + UPDATE public.graph_metrics SET counter = v_concept_count, updated_at = CURRENT_TIMESTAMP + WHERE public.graph_metrics.metric_name = 'concept_creation_counter'; + + UPDATE public.graph_metrics SET counter = v_edge_count, updated_at = CURRENT_TIMESTAMP + WHERE public.graph_metrics.metric_name = 'relationship_creation_counter'; + + UPDATE public.graph_metrics SET counter = v_vocab_type_count, updated_at = CURRENT_TIMESTAMP + WHERE public.graph_metrics.metric_name = 'vocabulary_creation_counter'; + + UPDATE public.graph_metrics SET counter = v_vocab_type_count + v_vocab_category_count, updated_at = CURRENT_TIMESTAMP + WHERE public.graph_metrics.metric_name = 'vocabulary_change_counter'; +END; +$$; + + +-- +-- Name: FUNCTION refresh_graph_metrics(); Type: COMMENT; Schema: public; Owner: - +-- + +COMMENT ON FUNCTION public.refresh_graph_metrics() IS 'Refreshes all graph metrics from current counts. Call after ingestion or periodically. +Returns table showing which metrics changed. Safe to call repeatedly (idempotent). +Used for ADR-079 projection cache invalidation.'; + + +-- +-- Name: reset_all_counters(); Type: FUNCTION; Schema: public; Owner: - +-- + +CREATE FUNCTION public.reset_all_counters() RETURNS void + LANGUAGE plpgsql + AS $$ +BEGIN + UPDATE graph_metrics + SET counter = 0, + last_measured_counter = 0, + last_measured_at = NULL, + updated_at = CURRENT_TIMESTAMP; +END; +$$; + + +-- +-- Name: FUNCTION reset_all_counters(); Type: COMMENT; Schema: public; Owner: - +-- + +COMMENT ON FUNCTION public.reset_all_counters() IS 'Reset ALL counters to 0 (operator maintenance task - use with caution)'; + + +-- +-- Name: reset_counter(character varying); Type: FUNCTION; Schema: public; Owner: - +-- + +CREATE FUNCTION public.reset_counter(p_metric_name character varying) RETURNS void + LANGUAGE plpgsql + AS $$ +BEGIN + UPDATE graph_metrics + SET counter = 0, + last_measured_counter = 0, + last_measured_at = NULL, + updated_at = CURRENT_TIMESTAMP + WHERE metric_name = p_metric_name; +END; +$$; + + +-- +-- Name: FUNCTION reset_counter(p_metric_name character varying); Type: COMMENT; Schema: public; Owner: - +-- + +COMMENT ON FUNCTION public.reset_counter(p_metric_name character varying) IS 'Reset counter to 0 (operator maintenance task)'; + + +-- +-- Name: update_graph_snapshot_totals(); Type: FUNCTION; Schema: public; Owner: - +-- + +CREATE FUNCTION public.update_graph_snapshot_totals() RETURNS void + LANGUAGE plpgsql + AS $$ +BEGIN + -- Just call the new comprehensive function + PERFORM public.refresh_graph_metrics(); +END; +$$; + + +-- +-- Name: FUNCTION update_graph_snapshot_totals(); Type: COMMENT; Schema: public; Owner: - +-- + +COMMENT ON FUNCTION public.update_graph_snapshot_totals() IS 'DEPRECATED: Use refresh_graph_metrics() instead. Kept for backwards compatibility.'; + + +-- +-- Name: update_snapshot_totals(); Type: FUNCTION; Schema: public; Owner: - +-- + +CREATE FUNCTION public.update_snapshot_totals() RETURNS void + LANGUAGE plpgsql + AS $$ +BEGIN + -- Update concept count + UPDATE graph_metrics + SET counter = (SELECT COUNT(*) FROM ag_catalog.ag_vertex WHERE label = 'Concept'), + updated_at = CURRENT_TIMESTAMP + WHERE metric_name = 'concept_count'; + + -- Update vocabulary type count + UPDATE graph_metrics + SET counter = (SELECT COUNT(*) FROM ag_catalog.ag_vertex WHERE label = 'VocabType'), + updated_at = CURRENT_TIMESTAMP + WHERE metric_name = 'vocabulary_type_count'; + + -- Note: total_edges, source_count, instance_count would require similar queries + -- but are omitted here to avoid cross-namespace queries +END; +$$; + + +-- +-- Name: FUNCTION update_snapshot_totals(); Type: COMMENT; Schema: public; Owner: - +-- + +COMMENT ON FUNCTION public.update_snapshot_totals() IS 'Update snapshot total counters from current graph state'; + + +SET default_tablespace = ''; + +SET default_table_access_method = heap; + +-- +-- Name: aggressiveness_profiles; Type: TABLE; Schema: kg_api; Owner: - +-- + +CREATE TABLE kg_api.aggressiveness_profiles ( + profile_name character varying(50) NOT NULL, + control_x1 double precision NOT NULL, + control_y1 double precision NOT NULL, + control_x2 double precision NOT NULL, + control_y2 double precision NOT NULL, + description text, + is_builtin boolean DEFAULT false, + created_at timestamp without time zone DEFAULT now(), + updated_at timestamp without time zone DEFAULT now(), + CONSTRAINT aggressiveness_profiles_control_x1_check CHECK (((control_x1 >= (0.0)::double precision) AND (control_x1 <= (1.0)::double precision))), + CONSTRAINT aggressiveness_profiles_control_x2_check CHECK (((control_x2 >= (0.0)::double precision) AND (control_x2 <= (1.0)::double precision))) +); + + +-- +-- Name: ai_extraction_config; Type: TABLE; Schema: kg_api; Owner: - +-- + +CREATE TABLE kg_api.ai_extraction_config ( + id integer NOT NULL, + provider character varying(50) NOT NULL, + model_name character varying(200) NOT NULL, + supports_vision boolean DEFAULT false, + supports_json_mode boolean DEFAULT true, + max_tokens integer, + created_at timestamp with time zone DEFAULT CURRENT_TIMESTAMP, + updated_at timestamp with time zone DEFAULT CURRENT_TIMESTAMP, + updated_by character varying(100), + active boolean DEFAULT true, + base_url character varying(255), + temperature double precision DEFAULT 0.1, + top_p double precision DEFAULT 0.9, + gpu_layers integer DEFAULT '-1'::integer, + num_threads integer DEFAULT 4, + thinking_mode character varying(20) DEFAULT 'off'::character varying, + max_concurrent_requests integer DEFAULT 4, + max_retries integer DEFAULT 8, + CONSTRAINT ai_extraction_config_max_concurrent_requests_check CHECK (((max_concurrent_requests >= 1) AND (max_concurrent_requests <= 100))), + CONSTRAINT ai_extraction_config_max_retries_check CHECK (((max_retries >= 0) AND (max_retries <= 20))), + CONSTRAINT ai_extraction_config_provider_check CHECK (((provider)::text = ANY ((ARRAY['openai'::character varying, 'anthropic'::character varying, 'ollama'::character varying, 'openrouter'::character varying, 'llamacpp'::character varying])::text[]))), + CONSTRAINT ai_extraction_config_temperature_check CHECK (((temperature >= (0.0)::double precision) AND (temperature <= (1.0)::double precision))), + CONSTRAINT ai_extraction_config_thinking_mode_check CHECK (((thinking_mode)::text = ANY ((ARRAY['off'::character varying, 'low'::character varying, 'medium'::character varying, 'high'::character varying])::text[]))), + CONSTRAINT ai_extraction_config_top_p_check CHECK (((top_p >= (0.0)::double precision) AND (top_p <= (1.0)::double precision))) +); + + +-- +-- Name: TABLE ai_extraction_config; Type: COMMENT; Schema: kg_api; Owner: - +-- + +COMMENT ON TABLE kg_api.ai_extraction_config IS 'AI extraction provider configuration for runtime-switchable models - ADR-041'; + + +-- +-- Name: COLUMN ai_extraction_config.provider; Type: COMMENT; Schema: kg_api; Owner: - +-- + +COMMENT ON COLUMN kg_api.ai_extraction_config.provider IS 'AI provider: openai, anthropic, ollama, or vllm'; + + +-- +-- Name: COLUMN ai_extraction_config.model_name; Type: COMMENT; Schema: kg_api; Owner: - +-- + +COMMENT ON COLUMN kg_api.ai_extraction_config.model_name IS 'Model identifier (e.g., gpt-4o, claude-sonnet-4-20250514)'; + + +-- +-- Name: COLUMN ai_extraction_config.supports_vision; Type: COMMENT; Schema: kg_api; Owner: - +-- + +COMMENT ON COLUMN kg_api.ai_extraction_config.supports_vision IS 'Whether the model supports vision/image inputs'; + + +-- +-- Name: COLUMN ai_extraction_config.supports_json_mode; Type: COMMENT; Schema: kg_api; Owner: - +-- + +COMMENT ON COLUMN kg_api.ai_extraction_config.supports_json_mode IS 'Whether the model supports JSON mode for structured outputs'; + + +-- +-- Name: COLUMN ai_extraction_config.max_tokens; Type: COMMENT; Schema: kg_api; Owner: - +-- + +COMMENT ON COLUMN kg_api.ai_extraction_config.max_tokens IS 'Maximum token limit for the model'; + + +-- +-- Name: COLUMN ai_extraction_config.active; Type: COMMENT; Schema: kg_api; Owner: - +-- + +COMMENT ON COLUMN kg_api.ai_extraction_config.active IS 'Only one config can be active at a time (enforced by unique index)'; + + +-- +-- Name: COLUMN ai_extraction_config.base_url; Type: COMMENT; Schema: kg_api; Owner: - +-- + +COMMENT ON COLUMN kg_api.ai_extraction_config.base_url IS 'Base URL for local providers (e.g., http://localhost:11434 for Ollama)'; + + +-- +-- Name: COLUMN ai_extraction_config.temperature; Type: COMMENT; Schema: kg_api; Owner: - +-- + +COMMENT ON COLUMN kg_api.ai_extraction_config.temperature IS 'Sampling temperature (0.0-1.0, lower = more consistent). Used by local providers.'; + + +-- +-- Name: COLUMN ai_extraction_config.top_p; Type: COMMENT; Schema: kg_api; Owner: - +-- + +COMMENT ON COLUMN kg_api.ai_extraction_config.top_p IS 'Nucleus sampling threshold (0.0-1.0). Used by local providers.'; + + +-- +-- Name: COLUMN ai_extraction_config.gpu_layers; Type: COMMENT; Schema: kg_api; Owner: - +-- + +COMMENT ON COLUMN kg_api.ai_extraction_config.gpu_layers IS 'GPU layers for inference: -1 = auto, 0 = CPU only, >0 = specific layer count (llama.cpp)'; + + +-- +-- Name: COLUMN ai_extraction_config.num_threads; Type: COMMENT; Schema: kg_api; Owner: - +-- + +COMMENT ON COLUMN kg_api.ai_extraction_config.num_threads IS 'CPU threads for inference (used by local CPU-based providers)'; + + +-- +-- Name: COLUMN ai_extraction_config.thinking_mode; Type: COMMENT; Schema: kg_api; Owner: - +-- + +COMMENT ON COLUMN kg_api.ai_extraction_config.thinking_mode IS 'Thinking mode for reasoning models (Ollama 0.12.x+): off, low, medium, high. +GPT-OSS: off=low, others pass through. Standard models: off=disabled, low/medium/high=enabled.'; + + +-- +-- Name: COLUMN ai_extraction_config.max_concurrent_requests; Type: COMMENT; Schema: kg_api; Owner: - +-- + +COMMENT ON COLUMN kg_api.ai_extraction_config.max_concurrent_requests IS 'Maximum number of concurrent API requests allowed for this provider. +Limits parallelism to prevent rate limit errors and resource thrashing. +Recommended: OpenAI=8, Anthropic=4, Ollama=1'; + + +-- +-- Name: COLUMN ai_extraction_config.max_retries; Type: COMMENT; Schema: kg_api; Owner: - +-- + +COMMENT ON COLUMN kg_api.ai_extraction_config.max_retries IS 'Maximum number of retry attempts for rate-limited requests (429 errors). +Uses exponential backoff with jitter: 1s, 2s, 4s, 8s, 16s, 32s, 64s, ... +Higher values provide more resilience with multiple workers. +Recommended: 8 for cloud providers, 3 for local'; + + +-- +-- Name: ai_extraction_config_id_seq; Type: SEQUENCE; Schema: kg_api; Owner: - +-- + +CREATE SEQUENCE kg_api.ai_extraction_config_id_seq + AS integer + START WITH 1 + INCREMENT BY 1 + NO MINVALUE + NO MAXVALUE + CACHE 1; + + +-- +-- Name: ai_extraction_config_id_seq; Type: SEQUENCE OWNED BY; Schema: kg_api; Owner: - +-- + +ALTER SEQUENCE kg_api.ai_extraction_config_id_seq OWNED BY kg_api.ai_extraction_config.id; + + +-- +-- Name: ai_vision_config; Type: TABLE; Schema: kg_api; Owner: - +-- + +CREATE TABLE kg_api.ai_vision_config ( + id integer NOT NULL, + provider character varying(50) NOT NULL, + model_name character varying(200) DEFAULT ''::character varying NOT NULL, + max_tokens integer, + temperature double precision, + created_at timestamp with time zone DEFAULT CURRENT_TIMESTAMP, + updated_at timestamp with time zone DEFAULT CURRENT_TIMESTAMP, + updated_by character varying(100), + active boolean DEFAULT true, + CONSTRAINT ai_vision_config_provider_check CHECK (((provider)::text = ANY ((ARRAY['openai'::character varying, 'anthropic'::character varying, 'ollama'::character varying, 'openrouter'::character varying, 'llamacpp'::character varying])::text[]))), + CONSTRAINT ai_vision_config_temperature_check CHECK (((temperature IS NULL) OR ((temperature >= (0.0)::double precision) AND (temperature <= (1.0)::double precision)))) +); + + +-- +-- Name: TABLE ai_vision_config; Type: COMMENT; Schema: kg_api; Owner: - +-- + +COMMENT ON TABLE kg_api.ai_vision_config IS 'Active vision (image->prose) provider selection — ADR-802 / #378. Selection-only; connectivity reused from per-provider config.'; + + +-- +-- Name: COLUMN ai_vision_config.provider; Type: COMMENT; Schema: kg_api; Owner: - +-- + +COMMENT ON COLUMN kg_api.ai_vision_config.provider IS 'Provider performing image->prose description'; + + +-- +-- Name: COLUMN ai_vision_config.model_name; Type: COMMENT; Schema: kg_api; Owner: - +-- + +COMMENT ON COLUMN kg_api.ai_vision_config.model_name IS 'Vision model id; '''' resolves from the catalog supports_vision rows'; + + +-- +-- Name: COLUMN ai_vision_config.active; Type: COMMENT; Schema: kg_api; Owner: - +-- + +COMMENT ON COLUMN kg_api.ai_vision_config.active IS 'Only one vision config active at a time (enforced by partial unique index)'; + + +-- +-- Name: ai_vision_config_id_seq; Type: SEQUENCE; Schema: kg_api; Owner: - +-- + +CREATE SEQUENCE kg_api.ai_vision_config_id_seq + AS integer + START WITH 1 + INCREMENT BY 1 + NO MINVALUE + NO MAXVALUE + CACHE 1; + + +-- +-- Name: ai_vision_config_id_seq; Type: SEQUENCE OWNED BY; Schema: kg_api; Owner: - +-- + +ALTER SEQUENCE kg_api.ai_vision_config_id_seq OWNED BY kg_api.ai_vision_config.id; + + +-- +-- Name: annealing_options; Type: TABLE; Schema: kg_api; Owner: - +-- + +CREATE TABLE kg_api.annealing_options ( + key character varying(100) NOT NULL, + value text NOT NULL, + description text, + updated_at timestamp with time zone DEFAULT now() +); + + +-- +-- Name: TABLE annealing_options; Type: COMMENT; Schema: kg_api; Owner: - +-- + +COMMENT ON TABLE kg_api.annealing_options IS 'Tunable parameters for ontology annealing cycles (ADR-200 Phase 3b). Code defaults apply when a key is absent; database values override.'; + + +-- +-- Name: annealing_pressure_history; Type: TABLE; Schema: kg_api; Owner: - +-- + +CREATE TABLE kg_api.annealing_pressure_history ( + id integer NOT NULL, + epoch integer NOT NULL, + total_ontologies integer NOT NULL, + total_concepts integer NOT NULL, + avg_concepts_per_ontology double precision NOT NULL, + pressure_score double precision NOT NULL, + pressure_zone character varying(20) NOT NULL, + pressure_recommendation jsonb DEFAULT '{}'::jsonb NOT NULL, + recorded_at timestamp with time zone DEFAULT now() NOT NULL, + CONSTRAINT annealing_pressure_history_pressure_score_check CHECK (((pressure_score >= (0.0)::double precision) AND (pressure_score <= (1.0)::double precision))) +); + + +-- +-- Name: TABLE annealing_pressure_history; Type: COMMENT; Schema: kg_api; Owner: - +-- + +COMMENT ON TABLE kg_api.annealing_pressure_history IS 'One row per annealing cycle: ecological snapshot + Bezier pressure read-out (#249, ADR-206 §Phase 3). Drives the web admin "pressure" panel and the future trend chart.'; + + +-- +-- Name: annealing_pressure_history_id_seq; Type: SEQUENCE; Schema: kg_api; Owner: - +-- + +CREATE SEQUENCE kg_api.annealing_pressure_history_id_seq + AS integer + START WITH 1 + INCREMENT BY 1 + NO MINVALUE + NO MAXVALUE + CACHE 1; + + +-- +-- Name: annealing_pressure_history_id_seq; Type: SEQUENCE OWNED BY; Schema: kg_api; Owner: - +-- + +ALTER SEQUENCE kg_api.annealing_pressure_history_id_seq OWNED BY kg_api.annealing_pressure_history.id; + + +-- +-- Name: annealing_proposals; Type: TABLE; Schema: kg_api; Owner: - +-- + +CREATE TABLE kg_api.annealing_proposals ( + id integer NOT NULL, + proposal_type character varying(20) NOT NULL, + ontology_name character varying(200) NOT NULL, + anchor_concept_id character varying(100), + target_ontology character varying(200), + reasoning text NOT NULL, + mass_score numeric(10,4), + coherence_score numeric(10,4), + protection_score numeric(10,4), + status character varying(20) DEFAULT 'pending'::character varying NOT NULL, + created_at timestamp with time zone DEFAULT now() NOT NULL, + created_at_epoch integer DEFAULT 0 NOT NULL, + reviewed_at timestamp with time zone, + reviewed_by character varying(100), + reviewer_notes text, + expires_at timestamp with time zone DEFAULT (now() + '7 days'::interval), + executed_at timestamp with time zone, + execution_result jsonb, + suggested_name character varying(200), + suggested_description text, + proposal_kind character varying(20) DEFAULT 'ontology'::character varying NOT NULL, + params jsonb, + CONSTRAINT annealing_proposals_proposal_kind_check CHECK (((proposal_kind)::text = ANY ((ARRAY['ontology'::character varying, 'control'::character varying])::text[]))), + CONSTRAINT annealing_proposals_proposal_type_check CHECK (((proposal_type)::text = ANY ((ARRAY['CLEAVE'::character varying, 'DISSOLVE'::character varying, 'MERGE'::character varying, 'RENAME'::character varying, 'NO_ACTION'::character varying, 'ESCALATE'::character varying, 'ADJUST_CONTROL'::character varying, 'promotion'::character varying, 'demotion'::character varying])::text[]))), + CONSTRAINT annealing_proposals_status_check CHECK (((status)::text = ANY ((ARRAY['pending'::character varying, 'approved'::character varying, 'rejected'::character varying, 'expired'::character varying, 'executing'::character varying, 'executed'::character varying, 'failed'::character varying])::text[]))) +); + + +-- +-- Name: annealing_proposals_id_seq; Type: SEQUENCE; Schema: kg_api; Owner: - +-- + +CREATE SEQUENCE kg_api.annealing_proposals_id_seq + AS integer + START WITH 1 + INCREMENT BY 1 + NO MINVALUE + NO MAXVALUE + CACHE 1; + + +-- +-- Name: annealing_proposals_id_seq; Type: SEQUENCE OWNED BY; Schema: kg_api; Owner: - +-- + +ALTER SEQUENCE kg_api.annealing_proposals_id_seq OWNED BY kg_api.annealing_proposals.id; + + +-- +-- Name: artifacts; Type: TABLE; Schema: kg_api; Owner: - +-- + +CREATE TABLE kg_api.artifacts ( + id integer NOT NULL, + artifact_type character varying(50) NOT NULL, + representation character varying(50) NOT NULL, + name character varying(200), + owner_id integer, + graph_epoch bigint NOT NULL, + created_at timestamp with time zone DEFAULT now() NOT NULL, + expires_at timestamp with time zone, + parameters jsonb NOT NULL, + metadata jsonb, + inline_result jsonb, + garage_key character varying(200), + query_definition_id integer, + ontology character varying(200), + concept_ids text[], + CONSTRAINT has_content CHECK (((inline_result IS NOT NULL) OR (garage_key IS NOT NULL))), + CONSTRAINT valid_artifact_type CHECK (((artifact_type)::text = ANY ((ARRAY['polarity_analysis'::character varying, 'projection'::character varying, 'query_result'::character varying, 'graph_subgraph'::character varying, 'vocabulary_analysis'::character varying, 'epistemic_measurement'::character varying, 'consolidation_result'::character varying, 'search_result'::character varying, 'connection_path'::character varying, 'report'::character varying, 'stats_snapshot'::character varying])::text[]))), + CONSTRAINT valid_representation CHECK (((representation)::text = ANY ((ARRAY['polarity_explorer'::character varying, 'embedding_landscape'::character varying, 'block_builder'::character varying, 'edge_explorer'::character varying, 'vocabulary_chord'::character varying, 'force_graph_2d'::character varying, 'force_graph_3d'::character varying, 'report_workspace'::character varying, 'cli'::character varying, 'mcp_server'::character varying, 'api_direct'::character varying])::text[]))) +); + + +-- +-- Name: TABLE artifacts; Type: COMMENT; Schema: kg_api; Owner: - +-- + +COMMENT ON TABLE kg_api.artifacts IS 'Computed artifact metadata with Garage blob pointers (ADR-083)'; + + +-- +-- Name: COLUMN artifacts.artifact_type; Type: COMMENT; Schema: kg_api; Owner: - +-- + +COMMENT ON COLUMN kg_api.artifacts.artifact_type IS 'Type of computation: polarity_analysis, projection, etc.'; + + +-- +-- Name: COLUMN artifacts.representation; Type: COMMENT; Schema: kg_api; Owner: - +-- + +COMMENT ON COLUMN kg_api.artifacts.representation IS 'Source UI/tool: polarity_explorer, cli, mcp_server, etc.'; + + +-- +-- Name: COLUMN artifacts.graph_epoch; Type: COMMENT; Schema: kg_api; Owner: - +-- + +COMMENT ON COLUMN kg_api.artifacts.graph_epoch IS 'graph_change_counter at creation for freshness validation'; + + +-- +-- Name: COLUMN artifacts.inline_result; Type: COMMENT; Schema: kg_api; Owner: - +-- + +COMMENT ON COLUMN kg_api.artifacts.inline_result IS 'Small results (<10KB) stored inline'; + + +-- +-- Name: COLUMN artifacts.garage_key; Type: COMMENT; Schema: kg_api; Owner: - +-- + +COMMENT ON COLUMN kg_api.artifacts.garage_key IS 'Pointer to Garage blob for large results'; + + +-- +-- Name: COLUMN artifacts.concept_ids; Type: COMMENT; Schema: kg_api; Owner: - +-- + +COMMENT ON COLUMN kg_api.artifacts.concept_ids IS 'Concept IDs involved in this artifact'; + + +-- +-- Name: artifacts_id_seq; Type: SEQUENCE; Schema: kg_api; Owner: - +-- + +CREATE SEQUENCE kg_api.artifacts_id_seq + AS integer + START WITH 1 + INCREMENT BY 1 + NO MINVALUE + NO MAXVALUE + CACHE 1; + + +-- +-- Name: artifacts_id_seq; Type: SEQUENCE OWNED BY; Schema: kg_api; Owner: - +-- + +ALTER SEQUENCE kg_api.artifacts_id_seq OWNED BY kg_api.artifacts.id; + + +-- +-- Name: catalog_edge; Type: TABLE; Schema: kg_api; Owner: - +-- + +CREATE TABLE kg_api.catalog_edge ( + parent_kind character varying(16) NOT NULL, + parent_id text NOT NULL, + child_kind character varying(16) NOT NULL, + child_id text NOT NULL, + graph_epoch bigint NOT NULL +); + + +-- +-- Name: TABLE catalog_edge; Type: COMMENT; Schema: kg_api; Owner: - +-- + +COMMENT ON TABLE kg_api.catalog_edge IS 'ADR-501: parent->child membership edges projecting canonical :SCOPED_BY (ontology<-document) and :HAS_SOURCE/:APPEARS (document<-concept). A concept may have many parent documents (DAG).'; + + +-- +-- Name: catalog_node; Type: TABLE; Schema: kg_api; Owner: - +-- + +CREATE TABLE kg_api.catalog_node ( + kind character varying(16) NOT NULL, + node_id text NOT NULL, + name text NOT NULL, + name_lower text NOT NULL, + child_count integer DEFAULT 0 NOT NULL, + content_type character varying(32), + properties jsonb DEFAULT '{}'::jsonb NOT NULL, + graph_epoch bigint NOT NULL, + indexed_at timestamp with time zone DEFAULT now() NOT NULL +); + + +-- +-- Name: TABLE catalog_node; Type: COMMENT; Schema: kg_api; Owner: - +-- + +COMMENT ON TABLE kg_api.catalog_node IS 'ADR-501: materialized identity/metadata for catalog nodes (ontology/document/concept). Source of truth is the AGE graph; rebuilt on graph epoch advance.'; + + +-- +-- Name: COLUMN catalog_node.child_count; Type: COMMENT; Schema: kg_api; Owner: - +-- + +COMMENT ON COLUMN kg_api.catalog_node.child_count IS 'Number of direct children (documents-in-ontology, concepts-in-document); 0 for leaf concepts.'; + + +-- +-- Name: COLUMN catalog_node.graph_epoch; Type: COMMENT; Schema: kg_api; Owner: - +-- + +COMMENT ON COLUMN kg_api.catalog_node.graph_epoch IS 'graph_change_counter at build time; compared to kg_api.get_graph_epoch() for staleness.'; + + +-- +-- Name: concept_access_stats; Type: TABLE; Schema: kg_api; Owner: - +-- + +CREATE TABLE kg_api.concept_access_stats ( + concept_id character varying(100) NOT NULL, + access_count integer DEFAULT 0, + last_accessed timestamp with time zone, + avg_query_time_ms numeric(10,2), + queries_as_start integer DEFAULT 0, + queries_as_result integer DEFAULT 0 +); + + +-- +-- Name: TABLE concept_access_stats; Type: COMMENT; Schema: kg_api; Owner: - +-- + +COMMENT ON TABLE kg_api.concept_access_stats IS 'Node-level access patterns for caching - ADR-025'; + + +-- +-- Name: concept_version_metadata; Type: TABLE; Schema: kg_api; Owner: - +-- + +CREATE TABLE kg_api.concept_version_metadata ( + concept_id character varying(100) NOT NULL, + created_in_version integer, + last_modified_version integer +); + + +-- +-- Name: edge_usage_stats; Type: TABLE; Schema: kg_api; Owner: - +-- + +CREATE TABLE kg_api.edge_usage_stats ( + from_concept_id character varying(100) NOT NULL, + to_concept_id character varying(100) NOT NULL, + relationship_type character varying(100) NOT NULL, + traversal_count integer DEFAULT 0, + last_traversed timestamp with time zone, + avg_query_time_ms numeric(10,2) +); + + +-- +-- Name: TABLE edge_usage_stats; Type: COMMENT; Schema: kg_api; Owner: - +-- + +COMMENT ON TABLE kg_api.edge_usage_stats IS 'Performance tracking for graph traversals - ADR-025'; + + +-- +-- Name: embedding_config_legacy; Type: TABLE; Schema: kg_api; Owner: - +-- + +CREATE TABLE kg_api.embedding_config_legacy ( + id integer CONSTRAINT embedding_config_id_not_null NOT NULL, + provider character varying(50) CONSTRAINT embedding_config_provider_not_null NOT NULL, + model_name character varying(200) CONSTRAINT embedding_config_model_name_not_null NOT NULL, + embedding_dimensions integer CONSTRAINT embedding_config_embedding_dimensions_not_null NOT NULL, + "precision" character varying(20) CONSTRAINT embedding_config_precision_not_null NOT NULL, + max_memory_mb integer, + num_threads integer, + device character varying(20), + batch_size integer DEFAULT 8, + max_seq_length integer, + normalize_embeddings boolean DEFAULT true, + created_at timestamp with time zone DEFAULT CURRENT_TIMESTAMP, + updated_at timestamp with time zone DEFAULT CURRENT_TIMESTAMP, + updated_by character varying(100), + active boolean DEFAULT true, + delete_protected boolean DEFAULT false, + change_protected boolean DEFAULT false, + CONSTRAINT embedding_config_device_check CHECK (((device)::text = ANY ((ARRAY['cpu'::character varying, 'cuda'::character varying, 'mps'::character varying])::text[]))), + CONSTRAINT embedding_config_precision_check CHECK ((("precision")::text = ANY ((ARRAY['float16'::character varying, 'float32'::character varying])::text[]))), + CONSTRAINT embedding_config_provider_check CHECK (((provider)::text = ANY ((ARRAY['local'::character varying, 'openai'::character varying])::text[]))) +); + + +-- +-- Name: TABLE embedding_config_legacy; Type: COMMENT; Schema: kg_api; Owner: - +-- + +COMMENT ON TABLE kg_api.embedding_config_legacy IS 'Resource-aware embedding configuration for local and remote models - ADR-039. Includes preset for nomic-embed-text-v1.5.'; + + +-- +-- Name: COLUMN embedding_config_legacy.provider; Type: COMMENT; Schema: kg_api; Owner: - +-- + +COMMENT ON COLUMN kg_api.embedding_config_legacy.provider IS 'Embedding provider: local (sentence-transformers) or openai'; + + +-- +-- Name: COLUMN embedding_config_legacy.model_name; Type: COMMENT; Schema: kg_api; Owner: - +-- + +COMMENT ON COLUMN kg_api.embedding_config_legacy.model_name IS 'Model identifier (HuggingFace ID for local, OpenAI model name for remote)'; + + +-- +-- Name: COLUMN embedding_config_legacy.max_memory_mb; Type: COMMENT; Schema: kg_api; Owner: - +-- + +COMMENT ON COLUMN kg_api.embedding_config_legacy.max_memory_mb IS 'Maximum RAM allocation for local model (local provider only)'; + + +-- +-- Name: COLUMN embedding_config_legacy.num_threads; Type: COMMENT; Schema: kg_api; Owner: - +-- + +COMMENT ON COLUMN kg_api.embedding_config_legacy.num_threads IS 'CPU threads for inference (local provider only)'; + + +-- +-- Name: COLUMN embedding_config_legacy.device; Type: COMMENT; Schema: kg_api; Owner: - +-- + +COMMENT ON COLUMN kg_api.embedding_config_legacy.device IS 'Compute device: cpu, cuda, or mps (local provider only)'; + + +-- +-- Name: COLUMN embedding_config_legacy.batch_size; Type: COMMENT; Schema: kg_api; Owner: - +-- + +COMMENT ON COLUMN kg_api.embedding_config_legacy.batch_size IS 'Batch size for embedding generation'; + + +-- +-- Name: COLUMN embedding_config_legacy.active; Type: COMMENT; Schema: kg_api; Owner: - +-- + +COMMENT ON COLUMN kg_api.embedding_config_legacy.active IS 'Only one config can be active at a time (enforced by unique constraint)'; + + +-- +-- Name: COLUMN embedding_config_legacy.delete_protected; Type: COMMENT; Schema: kg_api; Owner: - +-- + +COMMENT ON COLUMN kg_api.embedding_config_legacy.delete_protected IS 'Prevents deletion without first removing protection (default configs)'; + + +-- +-- Name: COLUMN embedding_config_legacy.change_protected; Type: COMMENT; Schema: kg_api; Owner: - +-- + +COMMENT ON COLUMN kg_api.embedding_config_legacy.change_protected IS 'Prevents changing provider/dimensions without explicit unlock (safety)'; + + +-- +-- Name: embedding_config_id_seq; Type: SEQUENCE; Schema: kg_api; Owner: - +-- + +CREATE SEQUENCE kg_api.embedding_config_id_seq + AS integer + START WITH 1 + INCREMENT BY 1 + NO MINVALUE + NO MAXVALUE + CACHE 1; + + +-- +-- Name: embedding_config_id_seq; Type: SEQUENCE OWNED BY; Schema: kg_api; Owner: - +-- + +ALTER SEQUENCE kg_api.embedding_config_id_seq OWNED BY kg_api.embedding_config_legacy.id; + + +-- +-- Name: embedding_generation_jobs; Type: TABLE; Schema: kg_api; Owner: - +-- + +CREATE TABLE kg_api.embedding_generation_jobs ( + job_id uuid DEFAULT gen_random_uuid() NOT NULL, + job_type character varying(50) NOT NULL, + target_types character varying(100)[], + target_count integer, + status character varying(20) DEFAULT 'pending'::character varying NOT NULL, + processed_count integer DEFAULT 0, + failed_count integer DEFAULT 0, + embedding_model character varying(100), + embedding_provider character varying(50), + created_at timestamp with time zone DEFAULT CURRENT_TIMESTAMP, + started_at timestamp with time zone, + completed_at timestamp with time zone, + duration_ms integer, + result_summary jsonb, + error_message text, + CONSTRAINT embedding_generation_jobs_job_type_check CHECK (((job_type)::text = ANY ((ARRAY['cold_start'::character varying, 'vocabulary_update'::character varying, 'model_migration'::character varying, 'batch_regeneration'::character varying])::text[]))), + CONSTRAINT embedding_generation_jobs_status_check CHECK (((status)::text = ANY ((ARRAY['pending'::character varying, 'running'::character varying, 'completed'::character varying, 'failed'::character varying, 'cancelled'::character varying])::text[]))) +); + + +-- +-- Name: TABLE embedding_generation_jobs; Type: COMMENT; Schema: kg_api; Owner: - +-- + +COMMENT ON TABLE kg_api.embedding_generation_jobs IS 'ADR-045: Tracks embedding generation jobs for audit trail and progress monitoring'; + + +-- +-- Name: embedding_profile; Type: TABLE; Schema: kg_api; Owner: - +-- + +CREATE TABLE kg_api.embedding_profile ( + id integer NOT NULL, + name character varying(200) NOT NULL, + vector_space character varying(100) NOT NULL, + multimodal boolean DEFAULT false, + text_provider character varying(50) NOT NULL, + text_model_name character varying(200) NOT NULL, + text_loader character varying(50) NOT NULL, + text_revision character varying(200), + text_dimensions integer NOT NULL, + text_precision character varying(20) DEFAULT 'float16'::character varying, + text_trust_remote_code boolean DEFAULT false, + image_provider character varying(50), + image_model_name character varying(200), + image_loader character varying(50), + image_revision character varying(200), + image_dimensions integer, + image_precision character varying(20) DEFAULT 'float16'::character varying, + image_trust_remote_code boolean DEFAULT false, + device character varying(20) DEFAULT 'cpu'::character varying, + max_memory_mb integer, + num_threads integer, + batch_size integer DEFAULT 8, + max_seq_length integer, + normalize_embeddings boolean DEFAULT true, + active boolean DEFAULT false, + delete_protected boolean DEFAULT false, + change_protected boolean DEFAULT false, + created_at timestamp with time zone DEFAULT CURRENT_TIMESTAMP, + updated_at timestamp with time zone DEFAULT CURRENT_TIMESTAMP, + updated_by character varying(100), + text_query_prefix character varying(200), + text_document_prefix character varying(200), + image_vector_space character varying(100), + CONSTRAINT chk_image_loader CHECK ((((image_loader)::text = ANY ((ARRAY['sentence-transformers'::character varying, 'transformers'::character varying, 'api'::character varying])::text[])) OR (image_loader IS NULL))), + CONSTRAINT chk_multimodal_no_image CHECK (((NOT multimodal) OR ((image_provider IS NULL) AND (image_model_name IS NULL) AND (image_loader IS NULL) AND (image_dimensions IS NULL)))), + CONSTRAINT chk_text_loader CHECK (((text_loader)::text = ANY ((ARRAY['sentence-transformers'::character varying, 'transformers'::character varying, 'api'::character varying])::text[]))) +); + + +-- +-- Name: TABLE embedding_profile; Type: COMMENT; Schema: kg_api; Owner: - +-- + +COMMENT ON TABLE kg_api.embedding_profile IS 'Unified embedding profile with text + image model slots. Replaces embedding_config.'; + + +-- +-- Name: COLUMN embedding_profile.vector_space; Type: COMMENT; Schema: kg_api; Owner: - +-- + +COMMENT ON COLUMN kg_api.embedding_profile.vector_space IS 'Compatibility key for the universal TEXT/prose space (concepts, edges, docs, image-prose). Profiles with the same text vector_space produce comparable text embeddings. Image embeddings are independent — see image_vector_space (ADR-803).'; + + +-- +-- Name: COLUMN embedding_profile.multimodal; Type: COMMENT; Schema: kg_api; Owner: - +-- + +COMMENT ON COLUMN kg_api.embedding_profile.multimodal IS 'When true, the text model also handles image embeddings (e.g. SigLIP 2)'; + + +-- +-- Name: COLUMN embedding_profile.text_loader; Type: COMMENT; Schema: kg_api; Owner: - +-- + +COMMENT ON COLUMN kg_api.embedding_profile.text_loader IS 'How to load text model: sentence-transformers, transformers (AutoModel), or api'; + + +-- +-- Name: COLUMN embedding_profile.image_loader; Type: COMMENT; Schema: kg_api; Owner: - +-- + +COMMENT ON COLUMN kg_api.embedding_profile.image_loader IS 'How to load image model: sentence-transformers, transformers (AutoModel), or api'; + + +-- +-- Name: COLUMN embedding_profile.text_query_prefix; Type: COMMENT; Schema: kg_api; Owner: - +-- + +COMMENT ON COLUMN kg_api.embedding_profile.text_query_prefix IS 'Prefix prepended for search queries (e.g. search_query: )'; + + +-- +-- Name: COLUMN embedding_profile.text_document_prefix; Type: COMMENT; Schema: kg_api; Owner: - +-- + +COMMENT ON COLUMN kg_api.embedding_profile.text_document_prefix IS 'Prefix prepended for stored documents (e.g. search_document: )'; + + +-- +-- Name: COLUMN embedding_profile.image_vector_space; Type: COMMENT; Schema: kg_api; Owner: - +-- + +COMMENT ON COLUMN kg_api.embedding_profile.image_vector_space IS 'Independent vector_space of the image (modality) embedding index (ADR-803). NULL for text-only / multimodal profiles. Never compared to text vector_space.'; + + +-- +-- Name: embedding_profile_id_seq; Type: SEQUENCE; Schema: kg_api; Owner: - +-- + +CREATE SEQUENCE kg_api.embedding_profile_id_seq + AS integer + START WITH 1 + INCREMENT BY 1 + NO MINVALUE + NO MAXVALUE + CACHE 1; + + +-- +-- Name: embedding_profile_id_seq; Type: SEQUENCE OWNED BY; Schema: kg_api; Owner: - +-- + +ALTER SEQUENCE kg_api.embedding_profile_id_seq OWNED BY kg_api.embedding_profile.id; + + +-- +-- Name: graph_epoch_kinds; Type: TABLE; Schema: kg_api; Owner: - +-- + +CREATE TABLE kg_api.graph_epoch_kinds ( + kind text NOT NULL, + semantic_wallclock boolean NOT NULL, + description text +); + + +-- +-- Name: TABLE graph_epoch_kinds; Type: COMMENT; Schema: kg_api; Owner: - +-- + +COMMENT ON TABLE kg_api.graph_epoch_kinds IS 'ADR-203: Discriminator for graph_epochs.kind. semantic_wallclock distinguishes events whose occurred_at is semantically primary (ingestion, edit) from those where it is forensic-only (reasoning, annealing).'; + + +-- +-- Name: COLUMN graph_epoch_kinds.semantic_wallclock; Type: COMMENT; Schema: kg_api; Owner: - +-- + +COMMENT ON COLUMN kg_api.graph_epoch_kinds.semantic_wallclock IS 'When TRUE, occurred_at is the meaningful timestamp for downstream consumers. When FALSE, occurred_at is recorded for audit/forensics but should not drive time-based queries on the resulting graph state.'; + + +-- +-- Name: graph_epochs; Type: TABLE; Schema: kg_api; Owner: - +-- + +CREATE TABLE kg_api.graph_epochs ( + event_id bigint NOT NULL, + occurred_at timestamp with time zone DEFAULT now() NOT NULL, + kind text NOT NULL, + actor text, + counter_after bigint, + metadata jsonb DEFAULT '{}'::jsonb NOT NULL, + status text DEFAULT 'in_progress'::text NOT NULL, + CONSTRAINT graph_epochs_status_check CHECK ((status = ANY (ARRAY['in_progress'::text, 'completed'::text, 'failed'::text]))) +); + + +-- +-- Name: TABLE graph_epochs; Type: COMMENT; Schema: kg_api; Owner: - +-- + +COMMENT ON TABLE kg_api.graph_epochs IS 'ADR-203: Monotonic event log of graph mutations. Distinct from graph_change_counter (ADR-079) which is a composite cache-invalidation checksum.'; + + +-- +-- Name: COLUMN graph_epochs.event_id; Type: COMMENT; Schema: kg_api; Owner: - +-- + +COMMENT ON COLUMN kg_api.graph_epochs.event_id IS 'Monotonic logical-time id. Foreign-keyed by Instance.created_at_event_id.'; + + +-- +-- Name: COLUMN graph_epochs.kind; Type: COMMENT; Schema: kg_api; Owner: - +-- + +COMMENT ON COLUMN kg_api.graph_epochs.kind IS 'ingestion | reasoning | annealing | edit. Determines whether occurred_at is semantically meaningful for the rows attributable to this event.'; + + +-- +-- Name: COLUMN graph_epochs.status; Type: COMMENT; Schema: kg_api; Owner: - +-- + +COMMENT ON COLUMN kg_api.graph_epochs.status IS 'ADR-207/#384: in_progress (set at record_graph_epoch) | completed | failed. Only in_progress blocks the committed watermark — both completed and failed count toward it (per-chunk commits mean a failed job may have mutated the graph). The completed/failed split is for analytics (drop zero-instance jobs from hot/stale signals), not for freshness.'; + + +-- +-- Name: graph_epochs_event_id_seq; Type: SEQUENCE; Schema: kg_api; Owner: - +-- + +CREATE SEQUENCE kg_api.graph_epochs_event_id_seq + START WITH 1 + INCREMENT BY 1 + NO MINVALUE + NO MAXVALUE + CACHE 1; + + +-- +-- Name: graph_epochs_event_id_seq; Type: SEQUENCE OWNED BY; Schema: kg_api; Owner: - +-- + +ALTER SEQUENCE kg_api.graph_epochs_event_id_seq OWNED BY kg_api.graph_epochs.event_id; + + +-- +-- Name: hot_concepts; Type: MATERIALIZED VIEW; Schema: kg_api; Owner: - +-- + +CREATE MATERIALIZED VIEW kg_api.hot_concepts AS + SELECT concept_id, + access_count, + queries_as_start + FROM kg_api.concept_access_stats + WHERE (access_count > 50) + ORDER BY access_count DESC + LIMIT 100 + WITH NO DATA; + + +-- +-- Name: hot_edges; Type: MATERIALIZED VIEW; Schema: kg_api; Owner: - +-- + +CREATE MATERIALIZED VIEW kg_api.hot_edges AS + SELECT from_concept_id, + to_concept_id, + relationship_type, + traversal_count + FROM kg_api.edge_usage_stats + WHERE (traversal_count > 100) + ORDER BY traversal_count DESC + LIMIT 1000 + WITH NO DATA; + + +-- +-- Name: jobs; Type: TABLE; Schema: kg_api; Owner: - +-- + +CREATE TABLE kg_api.jobs ( + job_id text NOT NULL, + job_type text NOT NULL, + content_hash text, + ontology text, + status text NOT NULL, + progress text, + result text, + error text, + created_at timestamp without time zone DEFAULT now() NOT NULL, + started_at timestamp without time zone, + completed_at timestamp without time zone, + job_data jsonb NOT NULL, + analysis text, + approved_at timestamp without time zone, + approved_by text, + expires_at timestamp without time zone, + processing_mode text DEFAULT 'serial'::text, + job_source character varying(50) DEFAULT 'user_cli'::character varying, + created_by character varying(100) DEFAULT 'unknown'::character varying, + is_system_job boolean DEFAULT false, + user_id integer NOT NULL, + source_filename text, + source_type text, + source_path text, + source_hostname text, + artifact_id integer, + priority integer DEFAULT 0 NOT NULL, + claimed_by text, + claimed_at timestamp with time zone, + cancelled boolean DEFAULT false NOT NULL, + retries integer DEFAULT 0 NOT NULL, + max_retries integer DEFAULT 3 NOT NULL, + CONSTRAINT chk_source_type CHECK (((source_type IS NULL) OR (source_type = ANY (ARRAY['file'::text, 'stdin'::text, 'mcp'::text, 'api'::text])))) +); + + +-- +-- Name: TABLE jobs; Type: COMMENT; Schema: kg_api; Owner: - +-- + +COMMENT ON TABLE kg_api.jobs IS 'Unified job queue for all background tasks (ingestion, backup, vocab, scheduled)'; + + +-- +-- Name: COLUMN jobs.job_id; Type: COMMENT; Schema: kg_api; Owner: - +-- + +COMMENT ON COLUMN kg_api.jobs.job_id IS 'Unique job identifier (UUID)'; + + +-- +-- Name: COLUMN jobs.job_type; Type: COMMENT; Schema: kg_api; Owner: - +-- + +COMMENT ON COLUMN kg_api.jobs.job_type IS 'Type of job: ingestion, restore, backup, vocab_refresh, vocab_consolidate'; + + +-- +-- Name: COLUMN jobs.content_hash; Type: COMMENT; Schema: kg_api; Owner: - +-- + +COMMENT ON COLUMN kg_api.jobs.content_hash IS 'SHA256 hash for deduplication (used with ontology to detect duplicates)'; + + +-- +-- Name: COLUMN jobs.ontology; Type: COMMENT; Schema: kg_api; Owner: - +-- + +COMMENT ON COLUMN kg_api.jobs.ontology IS 'Target ontology for the job'; + + +-- +-- Name: COLUMN jobs.status; Type: COMMENT; Schema: kg_api; Owner: - +-- + +COMMENT ON COLUMN kg_api.jobs.status IS 'Job status: pending_approval, approved, running, completed, failed, cancelled'; + + +-- +-- Name: COLUMN jobs.progress; Type: COMMENT; Schema: kg_api; Owner: - +-- + +COMMENT ON COLUMN kg_api.jobs.progress IS 'Progress message for UI display'; + + +-- +-- Name: COLUMN jobs.result; Type: COMMENT; Schema: kg_api; Owner: - +-- + +COMMENT ON COLUMN kg_api.jobs.result IS 'Final result data (JSON)'; + + +-- +-- Name: COLUMN jobs.error; Type: COMMENT; Schema: kg_api; Owner: - +-- + +COMMENT ON COLUMN kg_api.jobs.error IS 'Error message if failed'; + + +-- +-- Name: COLUMN jobs.job_data; Type: COMMENT; Schema: kg_api; Owner: - +-- + +COMMENT ON COLUMN kg_api.jobs.job_data IS 'Job-specific parameters (JSON)'; + + +-- +-- Name: COLUMN jobs.analysis; Type: COMMENT; Schema: kg_api; Owner: - +-- + +COMMENT ON COLUMN kg_api.jobs.analysis IS 'Pre-approval analysis (cost/time estimates)'; + + +-- +-- Name: COLUMN jobs.approved_at; Type: COMMENT; Schema: kg_api; Owner: - +-- + +COMMENT ON COLUMN kg_api.jobs.approved_at IS 'When job was approved by user'; + + +-- +-- Name: COLUMN jobs.approved_by; Type: COMMENT; Schema: kg_api; Owner: - +-- + +COMMENT ON COLUMN kg_api.jobs.approved_by IS 'Who approved the job'; + + +-- +-- Name: COLUMN jobs.expires_at; Type: COMMENT; Schema: kg_api; Owner: - +-- + +COMMENT ON COLUMN kg_api.jobs.expires_at IS 'When pending approval expires'; + + +-- +-- Name: COLUMN jobs.processing_mode; Type: COMMENT; Schema: kg_api; Owner: - +-- + +COMMENT ON COLUMN kg_api.jobs.processing_mode IS 'Execution mode: serial or parallel'; + + +-- +-- Name: COLUMN jobs.job_source; Type: COMMENT; Schema: kg_api; Owner: - +-- + +COMMENT ON COLUMN kg_api.jobs.job_source IS 'Source of job creation: user_cli, user_api, scheduled_task, system'; + + +-- +-- Name: COLUMN jobs.created_by; Type: COMMENT; Schema: kg_api; Owner: - +-- + +COMMENT ON COLUMN kg_api.jobs.created_by IS 'User or system identifier that created the job'; + + +-- +-- Name: COLUMN jobs.is_system_job; Type: COMMENT; Schema: kg_api; Owner: - +-- + +COMMENT ON COLUMN kg_api.jobs.is_system_job IS 'True for system-scheduled jobs (cannot be deleted by users)'; + + +-- +-- Name: COLUMN jobs.user_id; Type: COMMENT; Schema: kg_api; Owner: - +-- + +COMMENT ON COLUMN kg_api.jobs.user_id IS 'User who submitted the job (FK to kg_auth.users.id)'; + + +-- +-- Name: COLUMN jobs.source_filename; Type: COMMENT; Schema: kg_api; Owner: - +-- + +COMMENT ON COLUMN kg_api.jobs.source_filename IS 'Display name for source: filename, "stdin", or MCP session ID (best-effort metadata)'; + + +-- +-- Name: COLUMN jobs.source_type; Type: COMMENT; Schema: kg_api; Owner: - +-- + +COMMENT ON COLUMN kg_api.jobs.source_type IS 'Ingestion method: file (CLI file), stdin (pipe), mcp (Claude), api (direct) - enables source-aware queries'; + + +-- +-- Name: COLUMN jobs.source_path; Type: COMMENT; Schema: kg_api; Owner: - +-- + +COMMENT ON COLUMN kg_api.jobs.source_path IS 'Full filesystem path for file ingestion (null for stdin/mcp/api) - helps identify exact source file'; + + +-- +-- Name: COLUMN jobs.source_hostname; Type: COMMENT; Schema: kg_api; Owner: - +-- + +COMMENT ON COLUMN kg_api.jobs.source_hostname IS 'Hostname where ingestion initiated (CLI only, null for MCP/API) - useful for distributed deployments'; + + +-- +-- Name: COLUMN jobs.artifact_id; Type: COMMENT; Schema: kg_api; Owner: - +-- + +COMMENT ON COLUMN kg_api.jobs.artifact_id IS 'Artifact created by this job (ADR-083). NULL for jobs that do not produce artifacts.'; + + +-- +-- Name: ontology_tombstones; Type: TABLE; Schema: kg_api; Owner: - +-- + +CREATE TABLE kg_api.ontology_tombstones ( + name character varying(200) NOT NULL, + removed_at timestamp with time zone DEFAULT now() NOT NULL, + removed_by character varying(100), + reason text +); + + +-- +-- Name: TABLE ontology_tombstones; Type: COMMENT; Schema: kg_api; Owner: - +-- + +COMMENT ON TABLE kg_api.ontology_tombstones IS 'Positive operator-intent signal that an ontology was deliberately removed and must not be silently recreated by a subsequent ingest (#402 Defect B2). Operator-initiated delete writes a row; annealing dissolution does not.'; + + +-- +-- Name: ontology_versions; Type: TABLE; Schema: kg_api; Owner: - +-- + +CREATE TABLE kg_api.ontology_versions ( + version_id integer NOT NULL, + version_number character varying(20) NOT NULL, + created_at timestamp with time zone DEFAULT now() NOT NULL, + created_by character varying(100), + change_summary text, + is_active boolean DEFAULT true, + vocabulary_snapshot jsonb NOT NULL, + types_added text[], + types_aliased jsonb, + types_deprecated text[], + backward_compatible boolean DEFAULT true, + migration_required boolean DEFAULT false +); + + +-- +-- Name: TABLE ontology_versions; Type: COMMENT; Schema: kg_api; Owner: - +-- + +COMMENT ON TABLE kg_api.ontology_versions IS 'Formal ontology versioning with immutable snapshots - ADR-026'; + + +-- +-- Name: ontology_versions_version_id_seq; Type: SEQUENCE; Schema: kg_api; Owner: - +-- + +CREATE SEQUENCE kg_api.ontology_versions_version_id_seq + AS integer + START WITH 1 + INCREMENT BY 1 + NO MINVALUE + NO MAXVALUE + CACHE 1; + + +-- +-- Name: ontology_versions_version_id_seq; Type: SEQUENCE OWNED BY; Schema: kg_api; Owner: - +-- + +ALTER SEQUENCE kg_api.ontology_versions_version_id_seq OWNED BY kg_api.ontology_versions.version_id; + + +-- +-- Name: platform_config; Type: TABLE; Schema: kg_api; Owner: - +-- + +CREATE TABLE kg_api.platform_config ( + key character varying(100) NOT NULL, + value text NOT NULL, + description text, + updated_at timestamp with time zone DEFAULT now(), + updated_by character varying(100) DEFAULT 'system'::character varying +); + + +-- +-- Name: TABLE platform_config; Type: COMMENT; Schema: kg_api; Owner: - +-- + +COMMENT ON TABLE kg_api.platform_config IS 'Platform lifecycle configuration for operator control plane (ADR-061)'; + + +-- +-- Name: provider_model_catalog; Type: TABLE; Schema: kg_api; Owner: - +-- + +CREATE TABLE kg_api.provider_model_catalog ( + id integer NOT NULL, + provider character varying(50) NOT NULL, + model_id character varying(300) NOT NULL, + display_name character varying(300), + category character varying(50) NOT NULL, + context_length integer, + max_completion_tokens integer, + supports_vision boolean DEFAULT false, + supports_json_mode boolean DEFAULT false, + supports_tool_use boolean DEFAULT false, + supports_streaming boolean DEFAULT true, + price_prompt_per_m numeric, + price_completion_per_m numeric, + price_cache_read_per_m numeric, + enabled boolean DEFAULT false, + is_default boolean DEFAULT false, + sort_order integer DEFAULT 0, + upstream_provider character varying(100), + raw_metadata jsonb, + fetched_at timestamp with time zone, + created_at timestamp with time zone DEFAULT now(), + updated_at timestamp with time zone DEFAULT now() +); + + +-- +-- Name: TABLE provider_model_catalog; Type: COMMENT; Schema: kg_api; Owner: - +-- + +COMMENT ON TABLE kg_api.provider_model_catalog IS 'Cached model catalog per AI provider with curation and pricing (ADR-800)'; + + +-- +-- Name: provider_model_catalog_id_seq; Type: SEQUENCE; Schema: kg_api; Owner: - +-- + +CREATE SEQUENCE kg_api.provider_model_catalog_id_seq + AS integer + START WITH 1 + INCREMENT BY 1 + NO MINVALUE + NO MAXVALUE + CACHE 1; + + +-- +-- Name: provider_model_catalog_id_seq; Type: SEQUENCE OWNED BY; Schema: kg_api; Owner: - +-- + +ALTER SEQUENCE kg_api.provider_model_catalog_id_seq OWNED BY kg_api.provider_model_catalog.id; + + +-- +-- Name: pruning_recommendations; Type: TABLE; Schema: kg_api; Owner: - +-- + +CREATE TABLE kg_api.pruning_recommendations ( + id integer NOT NULL, + relationship_type character varying(100) NOT NULL, + target_type character varying(100), + action_type character varying(50) NOT NULL, + review_level character varying(20) NOT NULL, + reasoning text NOT NULL, + similarity numeric(4,3), + value_score numeric(10,2), + metadata jsonb, + status character varying(50) DEFAULT 'pending'::character varying NOT NULL, + created_at timestamp with time zone DEFAULT now() NOT NULL, + reviewed_at timestamp with time zone, + reviewed_by character varying(100), + reviewer_notes text, + executed_at timestamp with time zone, + expires_at timestamp with time zone, + CONSTRAINT pruning_recommendations_action_type_check CHECK (((action_type)::text = ANY ((ARRAY['merge'::character varying, 'prune'::character varying, 'deprecate'::character varying, 'skip'::character varying])::text[]))), + CONSTRAINT pruning_recommendations_review_level_check CHECK (((review_level)::text = ANY ((ARRAY['none'::character varying, 'ai'::character varying, 'human'::character varying])::text[]))), + CONSTRAINT pruning_recommendations_status_check CHECK (((status)::text = ANY ((ARRAY['pending'::character varying, 'approved'::character varying, 'rejected'::character varying, 'executed'::character varying, 'expired'::character varying])::text[]))) +); + + +-- +-- Name: TABLE pruning_recommendations; Type: COMMENT; Schema: kg_api; Owner: - +-- + +COMMENT ON TABLE kg_api.pruning_recommendations IS 'Pending vocabulary management actions - ADR-032'; + + +-- +-- Name: pruning_recommendations_id_seq; Type: SEQUENCE; Schema: kg_api; Owner: - +-- + +CREATE SEQUENCE kg_api.pruning_recommendations_id_seq + AS integer + START WITH 1 + INCREMENT BY 1 + NO MINVALUE + NO MAXVALUE + CACHE 1; + + +-- +-- Name: pruning_recommendations_id_seq; Type: SEQUENCE OWNED BY; Schema: kg_api; Owner: - +-- + +ALTER SEQUENCE kg_api.pruning_recommendations_id_seq OWNED BY kg_api.pruning_recommendations.id; + + +-- +-- Name: query_definitions; Type: TABLE; Schema: kg_api; Owner: - +-- + +CREATE TABLE kg_api.query_definitions ( + id integer NOT NULL, + name character varying(200) NOT NULL, + definition_type character varying(50) NOT NULL, + definition jsonb NOT NULL, + owner_id integer, + created_at timestamp with time zone DEFAULT now() NOT NULL, + updated_at timestamp with time zone DEFAULT now() NOT NULL, + metadata jsonb DEFAULT '{}'::jsonb, + CONSTRAINT valid_definition_type CHECK (((definition_type)::text = ANY ((ARRAY['block_diagram'::character varying, 'cypher'::character varying, 'search'::character varying, 'polarity'::character varying, 'connection'::character varying, 'exploration'::character varying, 'program'::character varying])::text[]))) +); + + +-- +-- Name: TABLE query_definitions; Type: COMMENT; Schema: kg_api; Owner: - +-- + +COMMENT ON TABLE kg_api.query_definitions IS 'Saved query recipes that can be re-executed (ADR-083)'; + + +-- +-- Name: COLUMN query_definitions.definition_type; Type: COMMENT; Schema: kg_api; Owner: - +-- + +COMMENT ON COLUMN kg_api.query_definitions.definition_type IS 'Type of query: block_diagram, cypher, search, polarity, connection, exploration, program'; + + +-- +-- Name: COLUMN query_definitions.definition; Type: COMMENT; Schema: kg_api; Owner: - +-- + +COMMENT ON COLUMN kg_api.query_definitions.definition IS 'Query parameters/structure as JSON'; + + +-- +-- Name: COLUMN query_definitions.metadata; Type: COMMENT; Schema: kg_api; Owner: - +-- + +COMMENT ON COLUMN kg_api.query_definitions.metadata IS 'Optional metadata (nodeCount, edgeCount, description, etc.)'; + + +-- +-- Name: query_definitions_id_seq; Type: SEQUENCE; Schema: kg_api; Owner: - +-- + +CREATE SEQUENCE kg_api.query_definitions_id_seq + AS integer + START WITH 1 + INCREMENT BY 1 + NO MINVALUE + NO MAXVALUE + CACHE 1; + + +-- +-- Name: query_definitions_id_seq; Type: SEQUENCE OWNED BY; Schema: kg_api; Owner: - +-- + +ALTER SEQUENCE kg_api.query_definitions_id_seq OWNED BY kg_api.query_definitions.id; + + +-- +-- Name: rate_limits; Type: TABLE; Schema: kg_api; Owner: - +-- + +CREATE TABLE kg_api.rate_limits ( + client_id character varying(100) NOT NULL, + endpoint character varying(200) NOT NULL, + window_start timestamp with time zone NOT NULL, + request_count integer DEFAULT 0 NOT NULL +); + + +-- +-- Name: relationship_vocabulary; Type: TABLE; Schema: kg_api; Owner: - +-- + +CREATE TABLE kg_api.relationship_vocabulary ( + relationship_type character varying(100) NOT NULL, + description text, + category character varying(50), + added_by character varying(100), + added_at timestamp with time zone DEFAULT now() NOT NULL, + usage_count integer DEFAULT 0, + is_active boolean DEFAULT true, + is_builtin boolean DEFAULT false, + synonyms character varying(100)[], + deprecation_reason text, + embedding jsonb, + embedding_model character varying(100), + embedding_generated_at timestamp with time zone, + grounding_contribution double precision, + last_grounding_calculated timestamp with time zone, + avg_confidence double precision, + semantic_diversity double precision, + embedding_quality_score double precision, + embedding_validation_status character varying(20), + category_source character varying(20) DEFAULT 'builtin'::character varying, + category_confidence double precision, + category_scores jsonb, + category_ambiguous boolean DEFAULT false, + direction_semantics character varying(20) DEFAULT NULL::character varying, + CONSTRAINT relationship_vocabulary_avg_confidence_check CHECK (((avg_confidence >= (0.0)::double precision) AND (avg_confidence <= (1.0)::double precision))), + CONSTRAINT relationship_vocabulary_embedding_quality_score_check CHECK (((embedding_quality_score >= (0.0)::double precision) AND (embedding_quality_score <= (1.0)::double precision))), + CONSTRAINT relationship_vocabulary_embedding_validation_status_check CHECK (((embedding_validation_status)::text = ANY ((ARRAY['pending'::character varying, 'valid'::character varying, 'invalid'::character varying, 'stale'::character varying])::text[]))), + CONSTRAINT relationship_vocabulary_grounding_contribution_check CHECK (((grounding_contribution >= (0.0)::double precision) AND (grounding_contribution <= (1.0)::double precision))), + CONSTRAINT relationship_vocabulary_semantic_diversity_check CHECK (((semantic_diversity >= (0.0)::double precision) AND (semantic_diversity <= (1.0)::double precision))) +); + + +-- +-- Name: TABLE relationship_vocabulary; Type: COMMENT; Schema: kg_api; Owner: - +-- + +COMMENT ON TABLE kg_api.relationship_vocabulary IS 'Canonical relationship types with embeddings - ADR-025, ADR-032'; + + +-- +-- Name: COLUMN relationship_vocabulary.embedding; Type: COMMENT; Schema: kg_api; Owner: - +-- + +COMMENT ON COLUMN kg_api.relationship_vocabulary.embedding IS 'Cached embedding vector (JSONB array) for synonym detection (ADR-032)'; + + +-- +-- Name: COLUMN relationship_vocabulary.grounding_contribution; Type: COMMENT; Schema: kg_api; Owner: - +-- + +COMMENT ON COLUMN kg_api.relationship_vocabulary.grounding_contribution IS 'ADR-046: Measures impact on concept grounding strength (0.0-1.0). Higher values indicate this edge type significantly affects truth convergence.'; + + +-- +-- Name: COLUMN relationship_vocabulary.last_grounding_calculated; Type: COMMENT; Schema: kg_api; Owner: - +-- + +COMMENT ON COLUMN kg_api.relationship_vocabulary.last_grounding_calculated IS 'ADR-046: Timestamp when grounding metrics were last recalculated. Enables staleness detection.'; + + +-- +-- Name: COLUMN relationship_vocabulary.avg_confidence; Type: COMMENT; Schema: kg_api; Owner: - +-- + +COMMENT ON COLUMN kg_api.relationship_vocabulary.avg_confidence IS 'ADR-046: Average confidence score across all edges of this type. Helps identify low-quality edge types.'; + + +-- +-- Name: COLUMN relationship_vocabulary.semantic_diversity; Type: COMMENT; Schema: kg_api; Owner: - +-- + +COMMENT ON COLUMN kg_api.relationship_vocabulary.semantic_diversity IS 'ADR-046: Semantic diversity score (0.0-1.0). High diversity may indicate overly broad type; low diversity may indicate well-defined type.'; + + +-- +-- Name: COLUMN relationship_vocabulary.embedding_quality_score; Type: COMMENT; Schema: kg_api; Owner: - +-- + +COMMENT ON COLUMN kg_api.relationship_vocabulary.embedding_quality_score IS 'ADR-045: Quality score for embedding (based on validation checks like magnitude, dimensionality)'; + + +-- +-- Name: COLUMN relationship_vocabulary.embedding_validation_status; Type: COMMENT; Schema: kg_api; Owner: - +-- + +COMMENT ON COLUMN kg_api.relationship_vocabulary.embedding_validation_status IS 'ADR-045: Validation status - stale indicates model changed since generation'; + + +-- +-- Name: COLUMN relationship_vocabulary.category_source; Type: COMMENT; Schema: kg_api; Owner: - +-- + +COMMENT ON COLUMN kg_api.relationship_vocabulary.category_source IS 'Source of category assignment: builtin (hand-assigned) or computed (ADR-047)'; + + +-- +-- Name: COLUMN relationship_vocabulary.category_confidence; Type: COMMENT; Schema: kg_api; Owner: - +-- + +COMMENT ON COLUMN kg_api.relationship_vocabulary.category_confidence IS 'Confidence score (0.0-1.0) for computed categories based on max similarity to seed types'; + + +-- +-- Name: COLUMN relationship_vocabulary.category_scores; Type: COMMENT; Schema: kg_api; Owner: - +-- + +COMMENT ON COLUMN kg_api.relationship_vocabulary.category_scores IS 'Full category similarity breakdown as JSON: {"causation": 0.85, "composition": 0.45, ...}'; + + +-- +-- Name: COLUMN relationship_vocabulary.category_ambiguous; Type: COMMENT; Schema: kg_api; Owner: - +-- + +COMMENT ON COLUMN kg_api.relationship_vocabulary.category_ambiguous IS 'True if runner-up category score > 0.70 (potential multi-category type)'; + + +-- +-- Name: COLUMN relationship_vocabulary.direction_semantics; Type: COMMENT; Schema: kg_api; Owner: - +-- + +COMMENT ON COLUMN kg_api.relationship_vocabulary.direction_semantics IS 'LLM-determined direction: outward (from→to), inward (from←to), bidirectional (symmetric). NULL = not yet determined by LLM.'; + + +-- +-- Name: scheduled_jobs; Type: TABLE; Schema: kg_api; Owner: - +-- + +CREATE TABLE kg_api.scheduled_jobs ( + id integer NOT NULL, + name character varying(100) NOT NULL, + launcher_class character varying(255) NOT NULL, + schedule_cron character varying(100) NOT NULL, + enabled boolean DEFAULT true, + max_retries integer DEFAULT 5, + retry_count integer DEFAULT 0, + last_run timestamp without time zone, + last_success timestamp without time zone, + last_failure timestamp without time zone, + next_run timestamp without time zone, + created_at timestamp without time zone DEFAULT now(), + updated_at timestamp without time zone DEFAULT now() +); + + +-- +-- Name: TABLE scheduled_jobs; Type: COMMENT; Schema: kg_api; Owner: - +-- + +COMMENT ON TABLE kg_api.scheduled_jobs IS 'Scheduled background jobs: +- category_refresh: Re-integrate LLM-generated vocabulary categories (every 6 hours) +- vocab_consolidation: Auto-consolidate vocabulary based on hysteresis thresholds (every 12 hours)'; + + +-- +-- Name: COLUMN scheduled_jobs.name; Type: COMMENT; Schema: kg_api; Owner: - +-- + +COMMENT ON COLUMN kg_api.scheduled_jobs.name IS 'Unique identifier for the scheduled job'; + + +-- +-- Name: COLUMN scheduled_jobs.launcher_class; Type: COMMENT; Schema: kg_api; Owner: - +-- + +COMMENT ON COLUMN kg_api.scheduled_jobs.launcher_class IS 'Python class name in launcher registry (e.g., CategoryRefreshLauncher)'; + + +-- +-- Name: COLUMN scheduled_jobs.schedule_cron; Type: COMMENT; Schema: kg_api; Owner: - +-- + +COMMENT ON COLUMN kg_api.scheduled_jobs.schedule_cron IS 'Cron expression for schedule (e.g., "0 */6 * * *" = every 6 hours)'; + + +-- +-- Name: COLUMN scheduled_jobs.enabled; Type: COMMENT; Schema: kg_api; Owner: - +-- + +COMMENT ON COLUMN kg_api.scheduled_jobs.enabled IS 'Whether this schedule is active (can be disabled on failure)'; + + +-- +-- Name: COLUMN scheduled_jobs.max_retries; Type: COMMENT; Schema: kg_api; Owner: - +-- + +COMMENT ON COLUMN kg_api.scheduled_jobs.max_retries IS 'Max consecutive failures before auto-disabling schedule'; + + +-- +-- Name: COLUMN scheduled_jobs.retry_count; Type: COMMENT; Schema: kg_api; Owner: - +-- + +COMMENT ON COLUMN kg_api.scheduled_jobs.retry_count IS 'Current consecutive failure count (reset on success or skip)'; + + +-- +-- Name: COLUMN scheduled_jobs.last_run; Type: COMMENT; Schema: kg_api; Owner: - +-- + +COMMENT ON COLUMN kg_api.scheduled_jobs.last_run IS 'Last time the schedule was checked (success, skip, or failure)'; + + +-- +-- Name: COLUMN scheduled_jobs.last_success; Type: COMMENT; Schema: kg_api; Owner: - +-- + +COMMENT ON COLUMN kg_api.scheduled_jobs.last_success IS 'Last time a job was successfully enqueued'; + + +-- +-- Name: COLUMN scheduled_jobs.last_failure; Type: COMMENT; Schema: kg_api; Owner: - +-- + +COMMENT ON COLUMN kg_api.scheduled_jobs.last_failure IS 'Last time the launcher failed with an exception'; + + +-- +-- Name: COLUMN scheduled_jobs.next_run; Type: COMMENT; Schema: kg_api; Owner: - +-- + +COMMENT ON COLUMN kg_api.scheduled_jobs.next_run IS 'Calculated next run time (from cron expression or backoff)'; + + +-- +-- Name: scheduled_jobs_id_seq; Type: SEQUENCE; Schema: kg_api; Owner: - +-- + +CREATE SEQUENCE kg_api.scheduled_jobs_id_seq + AS integer + START WITH 1 + INCREMENT BY 1 + NO MINVALUE + NO MAXVALUE + CACHE 1; + + +-- +-- Name: scheduled_jobs_id_seq; Type: SEQUENCE OWNED BY; Schema: kg_api; Owner: - +-- + +ALTER SEQUENCE kg_api.scheduled_jobs_id_seq OWNED BY kg_api.scheduled_jobs.id; + + +-- +-- Name: schema_migrations; Type: TABLE; Schema: kg_api; Owner: - +-- + +CREATE TABLE kg_api.schema_migrations ( + version integer NOT NULL, + description text NOT NULL, + applied_at timestamp without time zone DEFAULT now() NOT NULL +); + + +-- +-- Name: TABLE schema_migrations; Type: COMMENT; Schema: kg_api; Owner: - +-- + +COMMENT ON TABLE kg_api.schema_migrations IS 'Tracks applied database migrations for backup/restore compatibility. +Schema version is included in backups to ensure restore compatibility +when database schema evolves. See ADR-015 for details.'; + + +-- +-- Name: COLUMN schema_migrations.version; Type: COMMENT; Schema: kg_api; Owner: - +-- + +COMMENT ON COLUMN kg_api.schema_migrations.version IS 'Migration number matching schema/migrations/NNN_*.sql files'; + + +-- +-- Name: COLUMN schema_migrations.description; Type: COMMENT; Schema: kg_api; Owner: - +-- + +COMMENT ON COLUMN kg_api.schema_migrations.description IS 'Human-readable description of what this migration does'; + + +-- +-- Name: COLUMN schema_migrations.applied_at; Type: COMMENT; Schema: kg_api; Owner: - +-- + +COMMENT ON COLUMN kg_api.schema_migrations.applied_at IS 'When this migration was applied to the database'; + + +-- +-- Name: sessions; Type: TABLE; Schema: kg_api; Owner: - +-- + +CREATE TABLE kg_api.sessions ( + session_id character varying(100) NOT NULL, + user_id integer, + created_at timestamp with time zone DEFAULT now() NOT NULL, + expires_at timestamp with time zone NOT NULL, + last_activity timestamp with time zone DEFAULT now() NOT NULL, + metadata jsonb +); + + +-- +-- Name: skipped_relationships; Type: TABLE; Schema: kg_api; Owner: - +-- + +CREATE TABLE kg_api.skipped_relationships ( + id integer NOT NULL, + relationship_type character varying(100) NOT NULL, + from_concept_label character varying(500), + to_concept_label character varying(500), + job_id character varying(50), + ontology character varying(200), + first_seen timestamp with time zone DEFAULT now() NOT NULL, + last_seen timestamp with time zone DEFAULT now() NOT NULL, + occurrence_count integer DEFAULT 1, + sample_context jsonb +); + + +-- +-- Name: TABLE skipped_relationships; Type: COMMENT; Schema: kg_api; Owner: - +-- + +COMMENT ON TABLE kg_api.skipped_relationships IS 'Capture layer for unmatched relationship types - ADR-025'; + + +-- +-- Name: skipped_relationships_id_seq; Type: SEQUENCE; Schema: kg_api; Owner: - +-- + +CREATE SEQUENCE kg_api.skipped_relationships_id_seq + AS integer + START WITH 1 + INCREMENT BY 1 + NO MINVALUE + NO MAXVALUE + CACHE 1; + + +-- +-- Name: skipped_relationships_id_seq; Type: SEQUENCE OWNED BY; Schema: kg_api; Owner: - +-- + +ALTER SEQUENCE kg_api.skipped_relationships_id_seq OWNED BY kg_api.skipped_relationships.id; + + +-- +-- Name: source_embeddings; Type: TABLE; Schema: kg_api; Owner: - +-- + +CREATE TABLE kg_api.source_embeddings ( + embedding_id integer NOT NULL, + source_id text NOT NULL, + chunk_index integer NOT NULL, + chunk_strategy text NOT NULL, + start_offset integer NOT NULL, + end_offset integer NOT NULL, + chunk_text text NOT NULL, + chunk_hash text NOT NULL, + source_hash text NOT NULL, + embedding bytea NOT NULL, + embedding_model text NOT NULL, + embedding_dimension integer NOT NULL, + embedding_provider text, + created_at timestamp with time zone DEFAULT CURRENT_TIMESTAMP, + updated_at timestamp with time zone DEFAULT CURRENT_TIMESTAMP, + CONSTRAINT source_embeddings_check CHECK ((end_offset > start_offset)), + CONSTRAINT source_embeddings_chunk_index_check CHECK ((chunk_index >= 0)), + CONSTRAINT source_embeddings_chunk_strategy_check CHECK ((chunk_strategy = ANY (ARRAY['sentence'::text, 'paragraph'::text, 'semantic'::text, 'count'::text]))), + CONSTRAINT source_embeddings_embedding_dimension_check CHECK ((embedding_dimension > 0)), + CONSTRAINT source_embeddings_start_offset_check CHECK ((start_offset >= 0)) +); + + +-- +-- Name: TABLE source_embeddings; Type: COMMENT; Schema: kg_api; Owner: - +-- + +COMMENT ON TABLE kg_api.source_embeddings IS 'ADR-068: Embeddings for source text chunks with offset tracking and hash verification'; + + +-- +-- Name: COLUMN source_embeddings.source_id; Type: COMMENT; Schema: kg_api; Owner: - +-- + +COMMENT ON COLUMN kg_api.source_embeddings.source_id IS 'Reference to Source node in Apache AGE graph'; + + +-- +-- Name: COLUMN source_embeddings.chunk_index; Type: COMMENT; Schema: kg_api; Owner: - +-- + +COMMENT ON COLUMN kg_api.source_embeddings.chunk_index IS '0-based chunk number within source (e.g., 0, 1, 2...)'; + + +-- +-- Name: COLUMN source_embeddings.chunk_strategy; Type: COMMENT; Schema: kg_api; Owner: - +-- + +COMMENT ON COLUMN kg_api.source_embeddings.chunk_strategy IS 'Chunking strategy used: sentence, paragraph, semantic, or count'; + + +-- +-- Name: COLUMN source_embeddings.start_offset; Type: COMMENT; Schema: kg_api; Owner: - +-- + +COMMENT ON COLUMN kg_api.source_embeddings.start_offset IS 'Character offset in Source.full_text where chunk starts (0-based)'; + + +-- +-- Name: COLUMN source_embeddings.end_offset; Type: COMMENT; Schema: kg_api; Owner: - +-- + +COMMENT ON COLUMN kg_api.source_embeddings.end_offset IS 'Character offset in Source.full_text where chunk ends (exclusive)'; + + +-- +-- Name: COLUMN source_embeddings.chunk_text; Type: COMMENT; Schema: kg_api; Owner: - +-- + +COMMENT ON COLUMN kg_api.source_embeddings.chunk_text IS 'Actual chunk content stored for verification (should match Source.full_text[start_offset:end_offset])'; + + +-- +-- Name: COLUMN source_embeddings.chunk_hash; Type: COMMENT; Schema: kg_api; Owner: - +-- + +COMMENT ON COLUMN kg_api.source_embeddings.chunk_hash IS 'SHA256 hash of chunk_text - verifies chunk integrity'; + + +-- +-- Name: COLUMN source_embeddings.source_hash; Type: COMMENT; Schema: kg_api; Owner: - +-- + +COMMENT ON COLUMN kg_api.source_embeddings.source_hash IS 'SHA256 hash of Source.full_text - detects when source text changes (stale embedding indicator)'; + + +-- +-- Name: COLUMN source_embeddings.embedding; Type: COMMENT; Schema: kg_api; Owner: - +-- + +COMMENT ON COLUMN kg_api.source_embeddings.embedding IS 'Vector embedding bytes (float16 or float32 array, packed as bytea)'; + + +-- +-- Name: COLUMN source_embeddings.embedding_model; Type: COMMENT; Schema: kg_api; Owner: - +-- + +COMMENT ON COLUMN kg_api.source_embeddings.embedding_model IS 'Embedding model name (e.g., "nomic-ai/nomic-embed-text-v1.5", "text-embedding-3-small")'; + + +-- +-- Name: COLUMN source_embeddings.embedding_dimension; Type: COMMENT; Schema: kg_api; Owner: - +-- + +COMMENT ON COLUMN kg_api.source_embeddings.embedding_dimension IS 'Embedding vector dimension (must match system embedding_config for cosine similarity)'; + + +-- +-- Name: source_embeddings_embedding_id_seq; Type: SEQUENCE; Schema: kg_api; Owner: - +-- + +CREATE SEQUENCE kg_api.source_embeddings_embedding_id_seq + AS integer + START WITH 1 + INCREMENT BY 1 + NO MINVALUE + NO MAXVALUE + CACHE 1; + + +-- +-- Name: source_embeddings_embedding_id_seq; Type: SEQUENCE OWNED BY; Schema: kg_api; Owner: - +-- + +ALTER SEQUENCE kg_api.source_embeddings_embedding_id_seq OWNED BY kg_api.source_embeddings.embedding_id; + + +-- +-- Name: synonym_clusters; Type: TABLE; Schema: kg_api; Owner: - +-- + +CREATE TABLE kg_api.synonym_clusters ( + cluster_id uuid DEFAULT gen_random_uuid() NOT NULL, + representative_type character varying(100), + member_types character varying(100)[], + avg_similarity double precision, + cluster_size integer, + total_usage_count integer, + detected_at timestamp with time zone DEFAULT CURRENT_TIMESTAMP, + detection_method character varying(50) DEFAULT 'embedding_similarity'::character varying, + is_active boolean DEFAULT true, + merge_recommended boolean DEFAULT false, + merge_completed_at timestamp with time zone, + CONSTRAINT synonym_clusters_avg_similarity_check CHECK (((avg_similarity >= (0.0)::double precision) AND (avg_similarity <= (1.0)::double precision))) +); + + +-- +-- Name: TABLE synonym_clusters; Type: COMMENT; Schema: kg_api; Owner: - +-- + +COMMENT ON TABLE kg_api.synonym_clusters IS 'ADR-046: Tracks groups of synonymous edge types discovered through embedding-based semantic similarity (threshold > 0.85)'; + + +-- +-- Name: COLUMN synonym_clusters.representative_type; Type: COMMENT; Schema: kg_api; Owner: - +-- + +COMMENT ON COLUMN kg_api.synonym_clusters.representative_type IS 'The canonical type to use when merging cluster members. Usually has highest usage_count or is builtin.'; + + +-- +-- Name: COLUMN synonym_clusters.avg_similarity; Type: COMMENT; Schema: kg_api; Owner: - +-- + +COMMENT ON COLUMN kg_api.synonym_clusters.avg_similarity IS 'Average cosine similarity between all pairs of member embeddings. Higher values indicate stronger synonym relationship.'; + + +-- +-- Name: system_api_keys; Type: TABLE; Schema: kg_api; Owner: - +-- + +CREATE TABLE kg_api.system_api_keys ( + provider character varying(50) NOT NULL, + encrypted_key bytea NOT NULL, + updated_at timestamp with time zone DEFAULT now(), + validation_status character varying(20) DEFAULT 'untested'::character varying, + last_validated_at timestamp with time zone, + validation_error text, + CONSTRAINT system_api_keys_validation_status_check CHECK (((validation_status)::text = ANY ((ARRAY['valid'::character varying, 'invalid'::character varying, 'untested'::character varying])::text[]))) +); + + +-- +-- Name: TABLE system_api_keys; Type: COMMENT; Schema: kg_api; Owner: - +-- + +COMMENT ON TABLE kg_api.system_api_keys IS 'Encrypted system API keys for LLM providers (ADR-031, ADR-041)'; + + +-- +-- Name: COLUMN system_api_keys.provider; Type: COMMENT; Schema: kg_api; Owner: - +-- + +COMMENT ON COLUMN kg_api.system_api_keys.provider IS 'Provider name: openai, anthropic'; + + +-- +-- Name: COLUMN system_api_keys.encrypted_key; Type: COMMENT; Schema: kg_api; Owner: - +-- + +COMMENT ON COLUMN kg_api.system_api_keys.encrypted_key IS 'Fernet-encrypted API key (AES-128-CBC + HMAC-SHA256)'; + + +-- +-- Name: COLUMN system_api_keys.updated_at; Type: COMMENT; Schema: kg_api; Owner: - +-- + +COMMENT ON COLUMN kg_api.system_api_keys.updated_at IS 'Last time key was updated'; + + +-- +-- Name: COLUMN system_api_keys.validation_status; Type: COMMENT; Schema: kg_api; Owner: - +-- + +COMMENT ON COLUMN kg_api.system_api_keys.validation_status IS 'API key validation state: valid, invalid, or untested'; + + +-- +-- Name: COLUMN system_api_keys.last_validated_at; Type: COMMENT; Schema: kg_api; Owner: - +-- + +COMMENT ON COLUMN kg_api.system_api_keys.last_validated_at IS 'Timestamp of last validation check (typically at API startup)'; + + +-- +-- Name: COLUMN system_api_keys.validation_error; Type: COMMENT; Schema: kg_api; Owner: - +-- + +COMMENT ON COLUMN kg_api.system_api_keys.validation_error IS 'Error message from last failed validation attempt'; + + +-- +-- Name: system_initialization_status; Type: TABLE; Schema: kg_api; Owner: - +-- + +CREATE TABLE kg_api.system_initialization_status ( + component character varying(50) NOT NULL, + initialized boolean DEFAULT false, + initialized_at timestamp with time zone, + initialization_job_id uuid, + version character varying(20), + metadata jsonb, + last_processed_vocab_change_counter bigint DEFAULT 0 CONSTRAINT system_initialization_statu_last_processed_vocab_chang_not_null NOT NULL +); + + +-- +-- Name: TABLE system_initialization_status; Type: COMMENT; Schema: kg_api; Owner: - +-- + +COMMENT ON TABLE kg_api.system_initialization_status IS 'ADR-045: Tracks completion of system initialization tasks like cold start embedding generation'; + + +-- +-- Name: COLUMN system_initialization_status.last_processed_vocab_change_counter; Type: COMMENT; Schema: kg_api; Owner: - +-- + +COMMENT ON COLUMN kg_api.system_initialization_status.last_processed_vocab_change_counter IS 'Snapshot of vocabulary_change_counter at the time this initialization component last completed embedding work. The cold-start and VocabEmbeddingLauncher paths compare current counter vs. this value to detect new work since the last successful run. Replaces the binary `initialized` flag for embedding components (the flag stays for non-counter-driven components). Default 0 means "no work has completed yet" — correct initial state.'; + + +-- +-- Name: v_builtin_types_missing_embeddings; Type: VIEW; Schema: kg_api; Owner: - +-- + +CREATE VIEW kg_api.v_builtin_types_missing_embeddings AS + SELECT relationship_type, + description, + category, + usage_count, + added_at + FROM kg_api.relationship_vocabulary + WHERE ((is_builtin = true) AND ((embedding IS NULL) OR ((embedding_validation_status)::text = 'stale'::text)) AND (is_active = true)) + ORDER BY usage_count DESC, relationship_type; + + +-- +-- Name: VIEW v_builtin_types_missing_embeddings; Type: COMMENT; Schema: kg_api; Owner: - +-- + +COMMENT ON VIEW kg_api.v_builtin_types_missing_embeddings IS 'ADR-045: Builtin vocabulary types requiring embedding generation (cold start initialization)'; + + +-- +-- Name: v_sources_missing_embeddings; Type: VIEW; Schema: kg_api; Owner: - +-- + +CREATE VIEW kg_api.v_sources_missing_embeddings AS + SELECT source_id, + document, + paragraph, + length(full_text) AS text_length, + created_at + FROM ( SELECT cypher.source_id, + cypher.document, + cypher.paragraph, + cypher.full_text, + cypher.content_hash, + cypher.created_at + FROM ( SELECT (_.source_id)::text AS source_id, + (_.document)::text AS document, + (_.paragraph)::integer AS paragraph, + (_.full_text)::text AS full_text, + (_.content_hash)::text AS content_hash, + (_.created_at)::text AS created_at + FROM ( SELECT ag_catalog.agtype_access_operator(VARIADIC ARRAY[_age_default_alias_previous_cypher_clause.s, '"source_id"'::ag_catalog.agtype]) AS source_id, + ag_catalog.agtype_access_operator(VARIADIC ARRAY[_age_default_alias_previous_cypher_clause.s, '"document"'::ag_catalog.agtype]) AS document, + ag_catalog.agtype_access_operator(VARIADIC ARRAY[_age_default_alias_previous_cypher_clause.s, '"paragraph"'::ag_catalog.agtype]) AS paragraph, + ag_catalog.agtype_access_operator(VARIADIC ARRAY[_age_default_alias_previous_cypher_clause.s, '"full_text"'::ag_catalog.agtype]) AS full_text, + ag_catalog.agtype_access_operator(VARIADIC ARRAY[_age_default_alias_previous_cypher_clause.s, '"content_hash"'::ag_catalog.agtype]) AS content_hash, + ag_catalog.agtype_access_operator(VARIADIC ARRAY[_age_default_alias_previous_cypher_clause.s, '"created_at"'::ag_catalog.agtype]) AS created_at + FROM ( SELECT NULL::ag_catalog.agtype AS s + FROM knowledge_graph._ag_label_vertex s_1 + WHERE ('true'::ag_catalog.agtype OPERATOR(ag_catalog.=) 'false'::ag_catalog.agtype)) _age_default_alias_previous_cypher_clause) _) cypher) s + WHERE (content_hash IS NULL) + ORDER BY created_at DESC; + + +-- +-- Name: VIEW v_sources_missing_embeddings; Type: COMMENT; Schema: kg_api; Owner: - +-- + +COMMENT ON VIEW kg_api.v_sources_missing_embeddings IS 'ADR-068: Source nodes that need embedding generation (content_hash is NULL)'; + + +-- +-- Name: v_stale_source_embeddings; Type: VIEW; Schema: kg_api; Owner: - +-- + +CREATE VIEW kg_api.v_stale_source_embeddings AS + SELECT se.source_id, + se.chunk_index, + se.chunk_strategy, + se.source_hash AS stored_hash, + s.content_hash AS current_hash, + se.created_at AS embedding_created_at, + se.embedding_model + FROM (kg_api.source_embeddings se + JOIN ( SELECT cypher.source_id, + cypher.content_hash + FROM ( SELECT (_.source_id)::text AS source_id, + (_.content_hash)::text AS content_hash + FROM ( SELECT ag_catalog.agtype_access_operator(VARIADIC ARRAY[_age_default_alias_previous_cypher_clause.s, '"source_id"'::ag_catalog.agtype]) AS source_id, + ag_catalog.agtype_access_operator(VARIADIC ARRAY[_age_default_alias_previous_cypher_clause.s, '"content_hash"'::ag_catalog.agtype]) AS content_hash + FROM ( SELECT NULL::ag_catalog.agtype AS s + FROM knowledge_graph._ag_label_vertex s_1 + WHERE ('true'::ag_catalog.agtype OPERATOR(ag_catalog.=) 'false'::ag_catalog.agtype)) _age_default_alias_previous_cypher_clause) _) cypher) s ON ((se.source_id = s.source_id))) + WHERE (se.source_hash <> s.content_hash) + ORDER BY se.created_at DESC; + + +-- +-- Name: VIEW v_stale_source_embeddings; Type: COMMENT; Schema: kg_api; Owner: - +-- + +COMMENT ON VIEW kg_api.v_stale_source_embeddings IS 'ADR-068: Embeddings that are stale (source text changed since embedding generation)'; + + +-- +-- Name: v_types_needing_embedding_regeneration; Type: VIEW; Schema: kg_api; Owner: - +-- + +CREATE VIEW kg_api.v_types_needing_embedding_regeneration AS + SELECT relationship_type, + description, + category, + is_builtin, + usage_count, + embedding_model, + embedding_generated_at, + CASE + WHEN (embedding IS NULL) THEN 'missing'::text + WHEN ((embedding_validation_status)::text = 'stale'::text) THEN 'stale'::text + WHEN ((embedding_validation_status)::text = 'invalid'::text) THEN 'invalid'::text + ELSE 'unknown'::text + END AS regeneration_reason + FROM kg_api.relationship_vocabulary + WHERE ((is_active = true) AND ((embedding IS NULL) OR ((embedding_validation_status)::text = ANY ((ARRAY['stale'::character varying, 'invalid'::character varying, 'pending'::character varying])::text[])))) + ORDER BY is_builtin DESC, usage_count DESC, relationship_type; + + +-- +-- Name: VIEW v_types_needing_embedding_regeneration; Type: COMMENT; Schema: kg_api; Owner: - +-- + +COMMENT ON VIEW kg_api.v_types_needing_embedding_regeneration IS 'ADR-045: All vocabulary types requiring embedding (re)generation, prioritized by builtin status and usage'; + + +-- +-- Name: vocabulary_audit; Type: TABLE; Schema: kg_api; Owner: - +-- + +CREATE TABLE kg_api.vocabulary_audit ( + id integer NOT NULL, + relationship_type character varying(100), + action character varying(50) NOT NULL, + performed_by character varying(100), + performed_at timestamp with time zone DEFAULT now() NOT NULL, + details jsonb +); + + +-- +-- Name: vocabulary_audit_id_seq; Type: SEQUENCE; Schema: kg_api; Owner: - +-- + +CREATE SEQUENCE kg_api.vocabulary_audit_id_seq + AS integer + START WITH 1 + INCREMENT BY 1 + NO MINVALUE + NO MAXVALUE + CACHE 1; + + +-- +-- Name: vocabulary_audit_id_seq; Type: SEQUENCE OWNED BY; Schema: kg_api; Owner: - +-- + +ALTER SEQUENCE kg_api.vocabulary_audit_id_seq OWNED BY kg_api.vocabulary_audit.id; + + +-- +-- Name: vocabulary_config; Type: TABLE; Schema: kg_api; Owner: - +-- + +CREATE TABLE kg_api.vocabulary_config ( + key character varying(100) NOT NULL, + value text NOT NULL, + description text, + updated_at timestamp with time zone DEFAULT now() NOT NULL, + updated_by character varying(100) +); + + +-- +-- Name: TABLE vocabulary_config; Type: COMMENT; Schema: kg_api; Owner: - +-- + +COMMENT ON TABLE kg_api.vocabulary_config IS 'System configuration for automatic vocabulary management (ADR-032)'; + + +-- +-- Name: vocabulary_history; Type: TABLE; Schema: kg_api; Owner: - +-- + +CREATE TABLE kg_api.vocabulary_history ( + id integer NOT NULL, + relationship_type character varying(100) NOT NULL, + action character varying(50) NOT NULL, + performed_by character varying(100) NOT NULL, + performed_at timestamp with time zone DEFAULT now() NOT NULL, + target_type character varying(100), + reason text, + metadata jsonb, + aggressiveness numeric(4,3), + zone character varying(20), + vocab_size_before integer, + vocab_size_after integer, + CONSTRAINT vocabulary_history_action_check CHECK (((action)::text = ANY ((ARRAY['added'::character varying, 'merged'::character varying, 'pruned'::character varying, 'deprecated'::character varying, 'reactivated'::character varying])::text[]))) +); + + +-- +-- Name: TABLE vocabulary_history; Type: COMMENT; Schema: kg_api; Owner: - +-- + +COMMENT ON TABLE kg_api.vocabulary_history IS 'Detailed vocabulary change tracking with context (ADR-032)'; + + +-- +-- Name: vocabulary_history_id_seq; Type: SEQUENCE; Schema: kg_api; Owner: - +-- + +CREATE SEQUENCE kg_api.vocabulary_history_id_seq + AS integer + START WITH 1 + INCREMENT BY 1 + NO MINVALUE + NO MAXVALUE + CACHE 1; + + +-- +-- Name: vocabulary_history_id_seq; Type: SEQUENCE OWNED BY; Schema: kg_api; Owner: - +-- + +ALTER SEQUENCE kg_api.vocabulary_history_id_seq OWNED BY kg_api.vocabulary_history.id; + + +-- +-- Name: vocabulary_suggestions; Type: TABLE; Schema: kg_api; Owner: - +-- + +CREATE TABLE kg_api.vocabulary_suggestions ( + id integer NOT NULL, + relationship_type character varying(100) NOT NULL, + suggestion_type character varying(50) NOT NULL, + confidence numeric(3,2) NOT NULL, + suggested_canonical_type character varying(100), + suggested_category character varying(50), + suggested_description text, + similar_types jsonb, + reasoning text, + created_at timestamp with time zone DEFAULT now() NOT NULL, + reviewed boolean DEFAULT false, + curator_decision character varying(50), + curator_notes text, + CONSTRAINT vocabulary_suggestions_confidence_check CHECK (((confidence >= (0)::numeric) AND (confidence <= (1)::numeric))), + CONSTRAINT vocabulary_suggestions_suggestion_type_check CHECK (((suggestion_type)::text = ANY ((ARRAY['alias'::character varying, 'new_type'::character varying])::text[]))) +); + + +-- +-- Name: TABLE vocabulary_suggestions; Type: COMMENT; Schema: kg_api; Owner: - +-- + +COMMENT ON TABLE kg_api.vocabulary_suggestions IS 'LLM-assisted vocabulary curation suggestions - ADR-026'; + + +-- +-- Name: vocabulary_suggestions_id_seq; Type: SEQUENCE; Schema: kg_api; Owner: - +-- + +CREATE SEQUENCE kg_api.vocabulary_suggestions_id_seq + AS integer + START WITH 1 + INCREMENT BY 1 + NO MINVALUE + NO MAXVALUE + CACHE 1; + + +-- +-- Name: vocabulary_suggestions_id_seq; Type: SEQUENCE OWNED BY; Schema: kg_api; Owner: - +-- + +ALTER SEQUENCE kg_api.vocabulary_suggestions_id_seq OWNED BY kg_api.vocabulary_suggestions.id; + + +-- +-- Name: worker_lanes; Type: TABLE; Schema: kg_api; Owner: - +-- + +CREATE TABLE kg_api.worker_lanes ( + name text NOT NULL, + job_types text[] NOT NULL, + max_slots integer DEFAULT 1 NOT NULL, + poll_interval_ms integer DEFAULT 5000 NOT NULL, + stale_timeout_minutes integer DEFAULT 30 NOT NULL, + enabled boolean DEFAULT true NOT NULL, + updated_at timestamp with time zone DEFAULT now() NOT NULL +); + + +-- +-- Name: TABLE worker_lanes; Type: COMMENT; Schema: kg_api; Owner: - +-- + +COMMENT ON TABLE kg_api.worker_lanes IS 'Worker lane configuration for database-driven job dispatch (ADR-100)'; + + +-- +-- Name: worker_status; Type: TABLE; Schema: kg_api; Owner: - +-- + +CREATE TABLE kg_api.worker_status ( + worker_id character varying(100) NOT NULL, + last_heartbeat timestamp with time zone DEFAULT now() NOT NULL, + current_job_id character varying(50), + status character varying(50) NOT NULL, + metadata jsonb, + CONSTRAINT worker_status_status_check CHECK (((status)::text = ANY ((ARRAY['idle'::character varying, 'running'::character varying, 'error'::character varying, 'stopped'::character varying])::text[]))) +); + + +-- +-- Name: groups_id_seq; Type: SEQUENCE; Schema: kg_auth; Owner: - +-- + +CREATE SEQUENCE kg_auth.groups_id_seq + START WITH 1000 + INCREMENT BY 1 + NO MINVALUE + NO MAXVALUE + CACHE 1; + + +-- +-- Name: groups; Type: TABLE; Schema: kg_auth; Owner: - +-- + +CREATE TABLE kg_auth.groups ( + id integer DEFAULT nextval('kg_auth.groups_id_seq'::regclass) NOT NULL, + group_name character varying(100) NOT NULL, + display_name character varying(200), + description text, + is_system boolean DEFAULT false, + created_at timestamp with time zone DEFAULT now() NOT NULL, + created_by integer +); + + +-- +-- Name: TABLE groups; Type: COMMENT; Schema: kg_auth; Owner: - +-- + +COMMENT ON TABLE kg_auth.groups IS 'Group definitions for collaborative access control (ADR-082)'; + + +-- +-- Name: COLUMN groups.is_system; Type: COMMENT; Schema: kg_auth; Owner: - +-- + +COMMENT ON COLUMN kg_auth.groups.is_system IS 'System groups (public, admins) cannot be deleted'; + + +-- +-- Name: oauth_access_tokens; Type: TABLE; Schema: kg_auth; Owner: - +-- + +CREATE TABLE kg_auth.oauth_access_tokens ( + token_hash character varying(255) NOT NULL, + client_id character varying(255) NOT NULL, + user_id integer, + scopes text[], + expires_at timestamp with time zone NOT NULL, + revoked boolean DEFAULT false, + created_at timestamp with time zone DEFAULT now() +); + + +-- +-- Name: TABLE oauth_access_tokens; Type: COMMENT; Schema: kg_auth; Owner: - +-- + +COMMENT ON TABLE kg_auth.oauth_access_tokens IS 'OAuth access tokens issued to clients'; + + +-- +-- Name: COLUMN oauth_access_tokens.token_hash; Type: COMMENT; Schema: kg_auth; Owner: - +-- + +COMMENT ON COLUMN kg_auth.oauth_access_tokens.token_hash IS 'SHA256 hash of the actual token (tokens are not stored in plaintext)'; + + +-- +-- Name: COLUMN oauth_access_tokens.user_id; Type: COMMENT; Schema: kg_auth; Owner: - +-- + +COMMENT ON COLUMN kg_auth.oauth_access_tokens.user_id IS 'NULL for client_credentials grant (machine-to-machine), set for user-delegated grants'; + + +-- +-- Name: COLUMN oauth_access_tokens.expires_at; Type: COMMENT; Schema: kg_auth; Owner: - +-- + +COMMENT ON COLUMN kg_auth.oauth_access_tokens.expires_at IS 'Access tokens expire in 1 hour'; + + +-- +-- Name: oauth_authorization_codes; Type: TABLE; Schema: kg_auth; Owner: - +-- + +CREATE TABLE kg_auth.oauth_authorization_codes ( + code character varying(255) NOT NULL, + client_id character varying(255) NOT NULL, + user_id integer NOT NULL, + redirect_uri text NOT NULL, + scopes text[], + code_challenge character varying(255), + code_challenge_method character varying(10), + expires_at timestamp with time zone NOT NULL, + used boolean DEFAULT false, + created_at timestamp with time zone DEFAULT now(), + CONSTRAINT oauth_authorization_codes_code_challenge_method_check CHECK (((code_challenge_method)::text = ANY ((ARRAY['S256'::character varying, 'plain'::character varying])::text[]))) +); + + +-- +-- Name: TABLE oauth_authorization_codes; Type: COMMENT; Schema: kg_auth; Owner: - +-- + +COMMENT ON TABLE kg_auth.oauth_authorization_codes IS 'Temporary authorization codes for OAuth Authorization Code flow (web apps)'; + + +-- +-- Name: COLUMN oauth_authorization_codes.code_challenge; Type: COMMENT; Schema: kg_auth; Owner: - +-- + +COMMENT ON COLUMN kg_auth.oauth_authorization_codes.code_challenge IS 'PKCE code challenge (hash of code verifier)'; + + +-- +-- Name: COLUMN oauth_authorization_codes.expires_at; Type: COMMENT; Schema: kg_auth; Owner: - +-- + +COMMENT ON COLUMN kg_auth.oauth_authorization_codes.expires_at IS 'Authorization codes expire in 10 minutes'; + + +-- +-- Name: oauth_clients; Type: TABLE; Schema: kg_auth; Owner: - +-- + +CREATE TABLE kg_auth.oauth_clients ( + client_id character varying(255) NOT NULL, + client_secret_hash character varying(255), + client_name character varying(255) NOT NULL, + client_type character varying(50) NOT NULL, + grant_types text[] NOT NULL, + redirect_uris text[], + scopes text[], + is_active boolean DEFAULT true, + created_by integer, + created_at timestamp with time zone DEFAULT now(), + metadata jsonb DEFAULT '{}'::jsonb, + CONSTRAINT oauth_clients_client_type_check CHECK (((client_type)::text = ANY ((ARRAY['public'::character varying, 'confidential'::character varying])::text[]))) +); + + +-- +-- Name: TABLE oauth_clients; Type: COMMENT; Schema: kg_auth; Owner: - +-- + +COMMENT ON TABLE kg_auth.oauth_clients IS 'OAuth 2.0 client applications registered to use the API'; + + +-- +-- Name: COLUMN oauth_clients.client_type; Type: COMMENT; Schema: kg_auth; Owner: - +-- + +COMMENT ON COLUMN kg_auth.oauth_clients.client_type IS 'public = no client secret (CLI, web apps), confidential = has client secret (MCP server)'; + + +-- +-- Name: COLUMN oauth_clients.grant_types; Type: COMMENT; Schema: kg_auth; Owner: - +-- + +COMMENT ON COLUMN kg_auth.oauth_clients.grant_types IS 'Allowed OAuth grant types: authorization_code, urn:ietf:params:oauth:grant-type:device_code, client_credentials, refresh_token'; + + +-- +-- Name: oauth_device_codes; Type: TABLE; Schema: kg_auth; Owner: - +-- + +CREATE TABLE kg_auth.oauth_device_codes ( + device_code character varying(255) NOT NULL, + user_code character varying(50) NOT NULL, + client_id character varying(255) NOT NULL, + user_id integer, + scopes text[], + status character varying(50) DEFAULT 'pending'::character varying, + expires_at timestamp with time zone NOT NULL, + created_at timestamp with time zone DEFAULT now(), + CONSTRAINT oauth_device_codes_status_check CHECK (((status)::text = ANY ((ARRAY['pending'::character varying, 'authorized'::character varying, 'denied'::character varying, 'expired'::character varying])::text[]))) +); + + +-- +-- Name: TABLE oauth_device_codes; Type: COMMENT; Schema: kg_auth; Owner: - +-- + +COMMENT ON TABLE kg_auth.oauth_device_codes IS 'Device authorization codes for OAuth Device Authorization Grant flow (CLI tools)'; + + +-- +-- Name: COLUMN oauth_device_codes.device_code; Type: COMMENT; Schema: kg_auth; Owner: - +-- + +COMMENT ON COLUMN kg_auth.oauth_device_codes.device_code IS 'Long code used by device for polling'; + + +-- +-- Name: COLUMN oauth_device_codes.user_code; Type: COMMENT; Schema: kg_auth; Owner: - +-- + +COMMENT ON COLUMN kg_auth.oauth_device_codes.user_code IS 'Human-friendly code displayed to user (e.g., ABCD-1234)'; + + +-- +-- Name: COLUMN oauth_device_codes.expires_at; Type: COMMENT; Schema: kg_auth; Owner: - +-- + +COMMENT ON COLUMN kg_auth.oauth_device_codes.expires_at IS 'Device codes expire in 10 minutes'; + + +-- +-- Name: oauth_external_provider_tokens; Type: TABLE; Schema: kg_auth; Owner: - +-- + +CREATE TABLE kg_auth.oauth_external_provider_tokens ( + token_hash character varying(255) CONSTRAINT oauth_tokens_token_hash_not_null NOT NULL, + user_id integer, + provider character varying(50), + scopes text[], + expires_at timestamp with time zone CONSTRAINT oauth_tokens_expires_at_not_null NOT NULL +); + + +-- +-- Name: TABLE oauth_external_provider_tokens; Type: COMMENT; Schema: kg_auth; Owner: - +-- + +COMMENT ON TABLE kg_auth.oauth_external_provider_tokens IS 'OAuth tokens FROM external providers (Google, GitHub, etc.) - not tokens issued by our system'; + + +-- +-- Name: oauth_refresh_tokens; Type: TABLE; Schema: kg_auth; Owner: - +-- + +CREATE TABLE kg_auth.oauth_refresh_tokens ( + token_hash character varying(255) NOT NULL, + client_id character varying(255) NOT NULL, + user_id integer NOT NULL, + scopes text[], + access_token_hash character varying(255), + expires_at timestamp with time zone NOT NULL, + revoked boolean DEFAULT false, + created_at timestamp with time zone DEFAULT now(), + last_used timestamp with time zone +); + + +-- +-- Name: TABLE oauth_refresh_tokens; Type: COMMENT; Schema: kg_auth; Owner: - +-- + +COMMENT ON TABLE kg_auth.oauth_refresh_tokens IS 'OAuth refresh tokens for long-lived sessions'; + + +-- +-- Name: COLUMN oauth_refresh_tokens.expires_at; Type: COMMENT; Schema: kg_auth; Owner: - +-- + +COMMENT ON COLUMN kg_auth.oauth_refresh_tokens.expires_at IS 'Refresh tokens expire in 7 days (CLI) or 30 days (web)'; + + +-- +-- Name: COLUMN oauth_refresh_tokens.last_used; Type: COMMENT; Schema: kg_auth; Owner: - +-- + +COMMENT ON COLUMN kg_auth.oauth_refresh_tokens.last_used IS 'Updated when refresh token is used to obtain new access token'; + + +-- +-- Name: resource_grants; Type: TABLE; Schema: kg_auth; Owner: - +-- + +CREATE TABLE kg_auth.resource_grants ( + id integer NOT NULL, + resource_type character varying(50) NOT NULL, + resource_id character varying(200) NOT NULL, + principal_type character varying(20) NOT NULL, + principal_id integer NOT NULL, + permission character varying(20) NOT NULL, + granted_at timestamp with time zone DEFAULT now() NOT NULL, + granted_by integer, + CONSTRAINT resource_grants_permission_check CHECK (((permission)::text = ANY ((ARRAY['read'::character varying, 'write'::character varying, 'admin'::character varying])::text[]))), + CONSTRAINT resource_grants_principal_type_check CHECK (((principal_type)::text = ANY ((ARRAY['user'::character varying, 'group'::character varying])::text[]))) +); + + +-- +-- Name: TABLE resource_grants; Type: COMMENT; Schema: kg_auth; Owner: - +-- + +COMMENT ON TABLE kg_auth.resource_grants IS 'Instance-level access grants for owned resources (ADR-082)'; + + +-- +-- Name: COLUMN resource_grants.resource_type; Type: COMMENT; Schema: kg_auth; Owner: - +-- + +COMMENT ON COLUMN kg_auth.resource_grants.resource_type IS 'Type: ontology, artifact, report, etc.'; + + +-- +-- Name: COLUMN resource_grants.resource_id; Type: COMMENT; Schema: kg_auth; Owner: - +-- + +COMMENT ON COLUMN kg_auth.resource_grants.resource_id IS 'Specific resource identifier'; + + +-- +-- Name: COLUMN resource_grants.principal_type; Type: COMMENT; Schema: kg_auth; Owner: - +-- + +COMMENT ON COLUMN kg_auth.resource_grants.principal_type IS 'Grant to user or group'; + + +-- +-- Name: COLUMN resource_grants.permission; Type: COMMENT; Schema: kg_auth; Owner: - +-- + +COMMENT ON COLUMN kg_auth.resource_grants.permission IS 'read, write, or admin access'; + + +-- +-- Name: resource_grants_id_seq; Type: SEQUENCE; Schema: kg_auth; Owner: - +-- + +CREATE SEQUENCE kg_auth.resource_grants_id_seq + AS integer + START WITH 1 + INCREMENT BY 1 + NO MINVALUE + NO MAXVALUE + CACHE 1; + + +-- +-- Name: resource_grants_id_seq; Type: SEQUENCE OWNED BY; Schema: kg_auth; Owner: - +-- + +ALTER SEQUENCE kg_auth.resource_grants_id_seq OWNED BY kg_auth.resource_grants.id; + + +-- +-- Name: resources; Type: TABLE; Schema: kg_auth; Owner: - +-- + +CREATE TABLE kg_auth.resources ( + resource_type character varying(100) NOT NULL, + description text, + parent_type character varying(100), + available_actions character varying(50)[] NOT NULL, + supports_scoping boolean DEFAULT false, + metadata jsonb DEFAULT '{}'::jsonb, + registered_at timestamp with time zone DEFAULT now() NOT NULL, + registered_by character varying(100) +); + + +-- +-- Name: TABLE resources; Type: COMMENT; Schema: kg_auth; Owner: - +-- + +COMMENT ON TABLE kg_auth.resources IS 'Dynamic resource type registry (ADR-028)'; + + +-- +-- Name: role_permissions; Type: TABLE; Schema: kg_auth; Owner: - +-- + +CREATE TABLE kg_auth.role_permissions ( + id integer NOT NULL, + role_name character varying(50) NOT NULL, + resource_type character varying(100) NOT NULL, + action character varying(50) NOT NULL, + scope_type character varying(50), + scope_id character varying(200), + scope_filter jsonb, + granted boolean DEFAULT true NOT NULL, + inherited_from character varying(50), + created_at timestamp with time zone DEFAULT now() NOT NULL, + created_by integer +); + + +-- +-- Name: TABLE role_permissions; Type: COMMENT; Schema: kg_auth; Owner: - +-- + +COMMENT ON TABLE kg_auth.role_permissions IS 'Dynamic role permissions with scoping (ADR-028)'; + + +-- +-- Name: role_permissions_id_seq; Type: SEQUENCE; Schema: kg_auth; Owner: - +-- + +CREATE SEQUENCE kg_auth.role_permissions_id_seq + AS integer + START WITH 1 + INCREMENT BY 1 + NO MINVALUE + NO MAXVALUE + CACHE 1; + + +-- +-- Name: role_permissions_id_seq; Type: SEQUENCE OWNED BY; Schema: kg_auth; Owner: - +-- + +ALTER SEQUENCE kg_auth.role_permissions_id_seq OWNED BY kg_auth.role_permissions.id; + + +-- +-- Name: roles; Type: TABLE; Schema: kg_auth; Owner: - +-- + +CREATE TABLE kg_auth.roles ( + role_name character varying(50) NOT NULL, + display_name character varying(100) NOT NULL, + description text, + is_builtin boolean DEFAULT false, + is_active boolean DEFAULT true, + parent_role character varying(50), + created_at timestamp with time zone DEFAULT now() NOT NULL, + created_by integer, + metadata jsonb DEFAULT '{}'::jsonb +); + + +-- +-- Name: TABLE roles; Type: COMMENT; Schema: kg_auth; Owner: - +-- + +COMMENT ON TABLE kg_auth.roles IS 'Dynamic role definitions with inheritance (ADR-028)'; + + +-- +-- Name: user_groups; Type: TABLE; Schema: kg_auth; Owner: - +-- + +CREATE TABLE kg_auth.user_groups ( + user_id integer NOT NULL, + group_id integer NOT NULL, + added_at timestamp with time zone DEFAULT now() NOT NULL, + added_by integer +); + + +-- +-- Name: TABLE user_groups; Type: COMMENT; Schema: kg_auth; Owner: - +-- + +COMMENT ON TABLE kg_auth.user_groups IS 'Group membership assignments (ADR-082)'; + + +-- +-- Name: user_roles; Type: TABLE; Schema: kg_auth; Owner: - +-- + +CREATE TABLE kg_auth.user_roles ( + id integer NOT NULL, + user_id integer NOT NULL, + role_name character varying(50) NOT NULL, + scope_type character varying(50), + scope_id character varying(200), + assigned_at timestamp with time zone DEFAULT now() NOT NULL, + assigned_by integer, + expires_at timestamp with time zone +); + + +-- +-- Name: TABLE user_roles; Type: COMMENT; Schema: kg_auth; Owner: - +-- + +COMMENT ON TABLE kg_auth.user_roles IS 'User role assignments with optional scoping (ADR-028)'; + + +-- +-- Name: user_roles_id_seq; Type: SEQUENCE; Schema: kg_auth; Owner: - +-- + +CREATE SEQUENCE kg_auth.user_roles_id_seq + AS integer + START WITH 1 + INCREMENT BY 1 + NO MINVALUE + NO MAXVALUE + CACHE 1; + + +-- +-- Name: user_roles_id_seq; Type: SEQUENCE OWNED BY; Schema: kg_auth; Owner: - +-- + +ALTER SEQUENCE kg_auth.user_roles_id_seq OWNED BY kg_auth.user_roles.id; + + +-- +-- Name: users; Type: TABLE; Schema: kg_auth; Owner: - +-- + +CREATE TABLE kg_auth.users ( + id integer NOT NULL, + username character varying(100) NOT NULL, + password_hash character varying(255) NOT NULL, + primary_role character varying(50) NOT NULL, + created_at timestamp with time zone DEFAULT now() NOT NULL, + last_login timestamp with time zone, + disabled boolean DEFAULT false +); + + +-- +-- Name: COLUMN users.primary_role; Type: COMMENT; Schema: kg_auth; Owner: - +-- + +COMMENT ON COLUMN kg_auth.users.primary_role IS 'Primary role (backwards compatibility) - user can have additional roles in user_roles table'; + + +-- +-- Name: users_id_seq; Type: SEQUENCE; Schema: kg_auth; Owner: - +-- + +CREATE SEQUENCE kg_auth.users_id_seq + AS integer + START WITH 1 + INCREMENT BY 1 + NO MINVALUE + NO MAXVALUE + CACHE 1; + + +-- +-- Name: users_id_seq; Type: SEQUENCE OWNED BY; Schema: kg_auth; Owner: - +-- + +ALTER SEQUENCE kg_auth.users_id_seq OWNED BY kg_auth.users.id; + + +-- +-- Name: api_metrics; Type: TABLE; Schema: kg_logs; Owner: - +-- + +CREATE TABLE kg_logs.api_metrics ( + id integer NOT NULL, + "timestamp" timestamp with time zone DEFAULT now() NOT NULL, + endpoint character varying(200) NOT NULL, + method character varying(10) NOT NULL, + status_code integer NOT NULL, + duration_ms numeric(10,2) NOT NULL, + client_id character varying(100), + error_message text +); + + +-- +-- Name: api_metrics_id_seq; Type: SEQUENCE; Schema: kg_logs; Owner: - +-- + +CREATE SEQUENCE kg_logs.api_metrics_id_seq + AS integer + START WITH 1 + INCREMENT BY 1 + NO MINVALUE + NO MAXVALUE + CACHE 1; + + +-- +-- Name: api_metrics_id_seq; Type: SEQUENCE OWNED BY; Schema: kg_logs; Owner: - +-- + +ALTER SEQUENCE kg_logs.api_metrics_id_seq OWNED BY kg_logs.api_metrics.id; + + +-- +-- Name: audit_trail; Type: TABLE; Schema: kg_logs; Owner: - +-- + +CREATE TABLE kg_logs.audit_trail ( + id integer NOT NULL, + "timestamp" timestamp with time zone DEFAULT now() NOT NULL, + user_id integer, + action character varying(100) NOT NULL, + resource_type character varying(50) NOT NULL, + resource_id character varying(200), + details jsonb, + ip_address inet, + user_agent text, + outcome character varying(50) NOT NULL, + CONSTRAINT audit_trail_outcome_check CHECK (((outcome)::text = ANY ((ARRAY['success'::character varying, 'denied'::character varying, 'error'::character varying])::text[]))) +); + + +-- +-- Name: audit_trail_id_seq; Type: SEQUENCE; Schema: kg_logs; Owner: - +-- + +CREATE SEQUENCE kg_logs.audit_trail_id_seq + AS integer + START WITH 1 + INCREMENT BY 1 + NO MINVALUE + NO MAXVALUE + CACHE 1; + + +-- +-- Name: audit_trail_id_seq; Type: SEQUENCE OWNED BY; Schema: kg_logs; Owner: - +-- + +ALTER SEQUENCE kg_logs.audit_trail_id_seq OWNED BY kg_logs.audit_trail.id; + + +-- +-- Name: health_checks; Type: TABLE; Schema: kg_logs; Owner: - +-- + +CREATE TABLE kg_logs.health_checks ( + id integer NOT NULL, + "timestamp" timestamp with time zone DEFAULT now() NOT NULL, + service character varying(50) NOT NULL, + status character varying(50) NOT NULL, + metrics jsonb, + CONSTRAINT health_checks_status_check CHECK (((status)::text = ANY ((ARRAY['healthy'::character varying, 'degraded'::character varying, 'down'::character varying])::text[]))) +); + + +-- +-- Name: health_checks_id_seq; Type: SEQUENCE; Schema: kg_logs; Owner: - +-- + +CREATE SEQUENCE kg_logs.health_checks_id_seq + AS integer + START WITH 1 + INCREMENT BY 1 + NO MINVALUE + NO MAXVALUE + CACHE 1; + + +-- +-- Name: health_checks_id_seq; Type: SEQUENCE OWNED BY; Schema: kg_logs; Owner: - +-- + +ALTER SEQUENCE kg_logs.health_checks_id_seq OWNED BY kg_logs.health_checks.id; + + +-- +-- Name: job_events; Type: TABLE; Schema: kg_logs; Owner: - +-- + +CREATE TABLE kg_logs.job_events ( + id integer NOT NULL, + job_id character varying(50) NOT NULL, + "timestamp" timestamp with time zone DEFAULT now() NOT NULL, + event_type character varying(50) NOT NULL, + details jsonb +); + + +-- +-- Name: job_events_id_seq; Type: SEQUENCE; Schema: kg_logs; Owner: - +-- + +CREATE SEQUENCE kg_logs.job_events_id_seq + AS integer + START WITH 1 + INCREMENT BY 1 + NO MINVALUE + NO MAXVALUE + CACHE 1; + + +-- +-- Name: job_events_id_seq; Type: SEQUENCE OWNED BY; Schema: kg_logs; Owner: - +-- + +ALTER SEQUENCE kg_logs.job_events_id_seq OWNED BY kg_logs.job_events.id; + + +-- +-- Name: graph_metrics; Type: TABLE; Schema: public; Owner: - +-- + +CREATE TABLE public.graph_metrics ( + metric_name character varying(255) NOT NULL, + counter bigint DEFAULT 0 NOT NULL, + last_measured_counter bigint DEFAULT 0 NOT NULL, + last_measured_at timestamp without time zone, + updated_at timestamp without time zone DEFAULT CURRENT_TIMESTAMP, + notes text +); + + +-- +-- Name: TABLE graph_metrics; Type: COMMENT; Schema: public; Owner: - +-- + +COMMENT ON TABLE public.graph_metrics IS 'Change counters for triggering periodic epistemic status measurement'; + + +-- +-- Name: COLUMN graph_metrics.metric_name; Type: COMMENT; Schema: public; Owner: - +-- + +COMMENT ON COLUMN public.graph_metrics.metric_name IS 'Unique metric identifier (e.g., vocabulary_change_counter, concept_count)'; + + +-- +-- Name: COLUMN graph_metrics.counter; Type: COMMENT; Schema: public; Owner: - +-- + +COMMENT ON COLUMN public.graph_metrics.counter IS 'Increments on every change (create/delete/consolidate) - never decrements'; + + +-- +-- Name: COLUMN graph_metrics.last_measured_counter; Type: COMMENT; Schema: public; Owner: - +-- + +COMMENT ON COLUMN public.graph_metrics.last_measured_counter IS 'Counter value when epistemic status was last measured'; + + +-- +-- Name: COLUMN graph_metrics.last_measured_at; Type: COMMENT; Schema: public; Owner: - +-- + +COMMENT ON COLUMN public.graph_metrics.last_measured_at IS 'Timestamp when epistemic status was last measured'; + + +-- +-- Name: COLUMN graph_metrics.updated_at; Type: COMMENT; Schema: public; Owner: - +-- + +COMMENT ON COLUMN public.graph_metrics.updated_at IS 'Timestamp of last counter increment'; + + +-- +-- Name: graph_metrics_summary; Type: VIEW; Schema: public; Owner: - +-- + +CREATE VIEW public.graph_metrics_summary AS + SELECT metric_name, + counter, + last_measured_counter, + (counter - last_measured_counter) AS delta_since_last_measure, + updated_at, + CASE + WHEN ((metric_name)::text ~~ '%_count'::text) THEN 'snapshot'::text + WHEN ((metric_name)::text = 'graph_change_counter'::text) THEN 'snapshot'::text + WHEN ((metric_name)::text = ANY ((ARRAY['document_ingestion_counter'::character varying, 'chunk_processing_counter'::character varying, 'extraction_job_counter'::character varying, 'grounding_calculation_counter'::character varying, 'epistemic_measurement_counter'::character varying, 'vocabulary_consolidation_counter'::character varying])::text[])) THEN 'activity'::text + ELSE 'legacy_structure'::text + END AS counter_type, + notes + FROM public.graph_metrics + ORDER BY + CASE + WHEN ((metric_name)::text ~~ '%_count'::text) THEN 'snapshot'::text + WHEN ((metric_name)::text = 'graph_change_counter'::text) THEN 'snapshot'::text + WHEN ((metric_name)::text = ANY ((ARRAY['document_ingestion_counter'::character varying, 'chunk_processing_counter'::character varying, 'extraction_job_counter'::character varying, 'grounding_calculation_counter'::character varying, 'epistemic_measurement_counter'::character varying, 'vocabulary_consolidation_counter'::character varying])::text[])) THEN 'activity'::text + ELSE 'legacy_structure'::text + END, metric_name; + + +-- +-- Name: VIEW graph_metrics_summary; Type: COMMENT; Schema: public; Owner: - +-- + +COMMENT ON VIEW public.graph_metrics_summary IS 'Summary view of all graph metrics. snapshot counters are refreshed by refresh_graph_metrics(). +activity counters are incremented by application code. legacy_structure counters are kept +in sync with snapshots (AGE bypasses PostgreSQL triggers).'; + + +-- +-- Name: schema_migrations; Type: TABLE; Schema: public; Owner: - +-- + +CREATE TABLE public.schema_migrations ( + version integer NOT NULL, + name text NOT NULL, + applied_at timestamp without time zone DEFAULT now() NOT NULL +); + + +-- +-- Name: TABLE schema_migrations; Type: COMMENT; Schema: public; Owner: - +-- + +COMMENT ON TABLE public.schema_migrations IS 'Tracks applied schema migrations for safe schema evolution - ADR-040'; + + +-- +-- Name: COLUMN schema_migrations.version; Type: COMMENT; Schema: public; Owner: - +-- + +COMMENT ON COLUMN public.schema_migrations.version IS 'Sequential migration number (001, 002, 003, ...)'; + + +-- +-- Name: COLUMN schema_migrations.name; Type: COMMENT; Schema: public; Owner: - +-- + +COMMENT ON COLUMN public.schema_migrations.name IS 'Descriptive migration name (e.g., baseline, add_embedding_config)'; + + +-- +-- Name: COLUMN schema_migrations.applied_at; Type: COMMENT; Schema: public; Owner: - +-- + +COMMENT ON COLUMN public.schema_migrations.applied_at IS 'Timestamp when migration was applied'; + + +-- +-- Name: ai_extraction_config id; Type: DEFAULT; Schema: kg_api; Owner: - +-- + +ALTER TABLE ONLY kg_api.ai_extraction_config ALTER COLUMN id SET DEFAULT nextval('kg_api.ai_extraction_config_id_seq'::regclass); + + +-- +-- Name: ai_vision_config id; Type: DEFAULT; Schema: kg_api; Owner: - +-- + +ALTER TABLE ONLY kg_api.ai_vision_config ALTER COLUMN id SET DEFAULT nextval('kg_api.ai_vision_config_id_seq'::regclass); + + +-- +-- Name: annealing_pressure_history id; Type: DEFAULT; Schema: kg_api; Owner: - +-- + +ALTER TABLE ONLY kg_api.annealing_pressure_history ALTER COLUMN id SET DEFAULT nextval('kg_api.annealing_pressure_history_id_seq'::regclass); + + +-- +-- Name: annealing_proposals id; Type: DEFAULT; Schema: kg_api; Owner: - +-- + +ALTER TABLE ONLY kg_api.annealing_proposals ALTER COLUMN id SET DEFAULT nextval('kg_api.annealing_proposals_id_seq'::regclass); + + +-- +-- Name: artifacts id; Type: DEFAULT; Schema: kg_api; Owner: - +-- + +ALTER TABLE ONLY kg_api.artifacts ALTER COLUMN id SET DEFAULT nextval('kg_api.artifacts_id_seq'::regclass); + + +-- +-- Name: embedding_config_legacy id; Type: DEFAULT; Schema: kg_api; Owner: - +-- + +ALTER TABLE ONLY kg_api.embedding_config_legacy ALTER COLUMN id SET DEFAULT nextval('kg_api.embedding_config_id_seq'::regclass); + + +-- +-- Name: embedding_profile id; Type: DEFAULT; Schema: kg_api; Owner: - +-- + +ALTER TABLE ONLY kg_api.embedding_profile ALTER COLUMN id SET DEFAULT nextval('kg_api.embedding_profile_id_seq'::regclass); + + +-- +-- Name: graph_epochs event_id; Type: DEFAULT; Schema: kg_api; Owner: - +-- + +ALTER TABLE ONLY kg_api.graph_epochs ALTER COLUMN event_id SET DEFAULT nextval('kg_api.graph_epochs_event_id_seq'::regclass); + + +-- +-- Name: ontology_versions version_id; Type: DEFAULT; Schema: kg_api; Owner: - +-- + +ALTER TABLE ONLY kg_api.ontology_versions ALTER COLUMN version_id SET DEFAULT nextval('kg_api.ontology_versions_version_id_seq'::regclass); + + +-- +-- Name: provider_model_catalog id; Type: DEFAULT; Schema: kg_api; Owner: - +-- + +ALTER TABLE ONLY kg_api.provider_model_catalog ALTER COLUMN id SET DEFAULT nextval('kg_api.provider_model_catalog_id_seq'::regclass); + + +-- +-- Name: pruning_recommendations id; Type: DEFAULT; Schema: kg_api; Owner: - +-- + +ALTER TABLE ONLY kg_api.pruning_recommendations ALTER COLUMN id SET DEFAULT nextval('kg_api.pruning_recommendations_id_seq'::regclass); + + +-- +-- Name: query_definitions id; Type: DEFAULT; Schema: kg_api; Owner: - +-- + +ALTER TABLE ONLY kg_api.query_definitions ALTER COLUMN id SET DEFAULT nextval('kg_api.query_definitions_id_seq'::regclass); + + +-- +-- Name: scheduled_jobs id; Type: DEFAULT; Schema: kg_api; Owner: - +-- + +ALTER TABLE ONLY kg_api.scheduled_jobs ALTER COLUMN id SET DEFAULT nextval('kg_api.scheduled_jobs_id_seq'::regclass); + + +-- +-- Name: skipped_relationships id; Type: DEFAULT; Schema: kg_api; Owner: - +-- + +ALTER TABLE ONLY kg_api.skipped_relationships ALTER COLUMN id SET DEFAULT nextval('kg_api.skipped_relationships_id_seq'::regclass); + + +-- +-- Name: source_embeddings embedding_id; Type: DEFAULT; Schema: kg_api; Owner: - +-- + +ALTER TABLE ONLY kg_api.source_embeddings ALTER COLUMN embedding_id SET DEFAULT nextval('kg_api.source_embeddings_embedding_id_seq'::regclass); + + +-- +-- Name: vocabulary_audit id; Type: DEFAULT; Schema: kg_api; Owner: - +-- + +ALTER TABLE ONLY kg_api.vocabulary_audit ALTER COLUMN id SET DEFAULT nextval('kg_api.vocabulary_audit_id_seq'::regclass); + + +-- +-- Name: vocabulary_history id; Type: DEFAULT; Schema: kg_api; Owner: - +-- + +ALTER TABLE ONLY kg_api.vocabulary_history ALTER COLUMN id SET DEFAULT nextval('kg_api.vocabulary_history_id_seq'::regclass); + + +-- +-- Name: vocabulary_suggestions id; Type: DEFAULT; Schema: kg_api; Owner: - +-- + +ALTER TABLE ONLY kg_api.vocabulary_suggestions ALTER COLUMN id SET DEFAULT nextval('kg_api.vocabulary_suggestions_id_seq'::regclass); + + +-- +-- Name: resource_grants id; Type: DEFAULT; Schema: kg_auth; Owner: - +-- + +ALTER TABLE ONLY kg_auth.resource_grants ALTER COLUMN id SET DEFAULT nextval('kg_auth.resource_grants_id_seq'::regclass); + + +-- +-- Name: role_permissions id; Type: DEFAULT; Schema: kg_auth; Owner: - +-- + +ALTER TABLE ONLY kg_auth.role_permissions ALTER COLUMN id SET DEFAULT nextval('kg_auth.role_permissions_id_seq'::regclass); + + +-- +-- Name: user_roles id; Type: DEFAULT; Schema: kg_auth; Owner: - +-- + +ALTER TABLE ONLY kg_auth.user_roles ALTER COLUMN id SET DEFAULT nextval('kg_auth.user_roles_id_seq'::regclass); + + +-- +-- Name: users id; Type: DEFAULT; Schema: kg_auth; Owner: - +-- + +ALTER TABLE ONLY kg_auth.users ALTER COLUMN id SET DEFAULT nextval('kg_auth.users_id_seq'::regclass); + + +-- +-- Name: api_metrics id; Type: DEFAULT; Schema: kg_logs; Owner: - +-- + +ALTER TABLE ONLY kg_logs.api_metrics ALTER COLUMN id SET DEFAULT nextval('kg_logs.api_metrics_id_seq'::regclass); + + +-- +-- Name: audit_trail id; Type: DEFAULT; Schema: kg_logs; Owner: - +-- + +ALTER TABLE ONLY kg_logs.audit_trail ALTER COLUMN id SET DEFAULT nextval('kg_logs.audit_trail_id_seq'::regclass); + + +-- +-- Name: health_checks id; Type: DEFAULT; Schema: kg_logs; Owner: - +-- + +ALTER TABLE ONLY kg_logs.health_checks ALTER COLUMN id SET DEFAULT nextval('kg_logs.health_checks_id_seq'::regclass); + + +-- +-- Name: job_events id; Type: DEFAULT; Schema: kg_logs; Owner: - +-- + +ALTER TABLE ONLY kg_logs.job_events ALTER COLUMN id SET DEFAULT nextval('kg_logs.job_events_id_seq'::regclass); + + +-- +-- Name: aggressiveness_profiles aggressiveness_profiles_pkey; Type: CONSTRAINT; Schema: kg_api; Owner: - +-- + +ALTER TABLE ONLY kg_api.aggressiveness_profiles + ADD CONSTRAINT aggressiveness_profiles_pkey PRIMARY KEY (profile_name); + + +-- +-- Name: ai_extraction_config ai_extraction_config_pkey; Type: CONSTRAINT; Schema: kg_api; Owner: - +-- + +ALTER TABLE ONLY kg_api.ai_extraction_config + ADD CONSTRAINT ai_extraction_config_pkey PRIMARY KEY (id); + + +-- +-- Name: ai_extraction_config ai_extraction_config_provider_key; Type: CONSTRAINT; Schema: kg_api; Owner: - +-- + +ALTER TABLE ONLY kg_api.ai_extraction_config + ADD CONSTRAINT ai_extraction_config_provider_key UNIQUE (provider); + + +-- +-- Name: ai_vision_config ai_vision_config_pkey; Type: CONSTRAINT; Schema: kg_api; Owner: - +-- + +ALTER TABLE ONLY kg_api.ai_vision_config + ADD CONSTRAINT ai_vision_config_pkey PRIMARY KEY (id); + + +-- +-- Name: ai_vision_config ai_vision_config_provider_key; Type: CONSTRAINT; Schema: kg_api; Owner: - +-- + +ALTER TABLE ONLY kg_api.ai_vision_config + ADD CONSTRAINT ai_vision_config_provider_key UNIQUE (provider); + + +-- +-- Name: annealing_options annealing_options_pkey; Type: CONSTRAINT; Schema: kg_api; Owner: - +-- + +ALTER TABLE ONLY kg_api.annealing_options + ADD CONSTRAINT annealing_options_pkey PRIMARY KEY (key); + + +-- +-- Name: annealing_pressure_history annealing_pressure_history_pkey; Type: CONSTRAINT; Schema: kg_api; Owner: - +-- + +ALTER TABLE ONLY kg_api.annealing_pressure_history + ADD CONSTRAINT annealing_pressure_history_pkey PRIMARY KEY (id); + + +-- +-- Name: annealing_proposals annealing_proposals_pkey; Type: CONSTRAINT; Schema: kg_api; Owner: - +-- + +ALTER TABLE ONLY kg_api.annealing_proposals + ADD CONSTRAINT annealing_proposals_pkey PRIMARY KEY (id); + + +-- +-- Name: artifacts artifacts_pkey; Type: CONSTRAINT; Schema: kg_api; Owner: - +-- + +ALTER TABLE ONLY kg_api.artifacts + ADD CONSTRAINT artifacts_pkey PRIMARY KEY (id); + + +-- +-- Name: catalog_edge catalog_edge_pkey; Type: CONSTRAINT; Schema: kg_api; Owner: - +-- + +ALTER TABLE ONLY kg_api.catalog_edge + ADD CONSTRAINT catalog_edge_pkey PRIMARY KEY (parent_kind, parent_id, child_kind, child_id); + + +-- +-- Name: catalog_node catalog_node_pkey; Type: CONSTRAINT; Schema: kg_api; Owner: - +-- + +ALTER TABLE ONLY kg_api.catalog_node + ADD CONSTRAINT catalog_node_pkey PRIMARY KEY (kind, node_id); + + +-- +-- Name: concept_access_stats concept_access_stats_pkey; Type: CONSTRAINT; Schema: kg_api; Owner: - +-- + +ALTER TABLE ONLY kg_api.concept_access_stats + ADD CONSTRAINT concept_access_stats_pkey PRIMARY KEY (concept_id); + + +-- +-- Name: concept_version_metadata concept_version_metadata_pkey; Type: CONSTRAINT; Schema: kg_api; Owner: - +-- + +ALTER TABLE ONLY kg_api.concept_version_metadata + ADD CONSTRAINT concept_version_metadata_pkey PRIMARY KEY (concept_id); + + +-- +-- Name: edge_usage_stats edge_usage_stats_pkey; Type: CONSTRAINT; Schema: kg_api; Owner: - +-- + +ALTER TABLE ONLY kg_api.edge_usage_stats + ADD CONSTRAINT edge_usage_stats_pkey PRIMARY KEY (from_concept_id, to_concept_id, relationship_type); + + +-- +-- Name: embedding_config_legacy embedding_config_pkey; Type: CONSTRAINT; Schema: kg_api; Owner: - +-- + +ALTER TABLE ONLY kg_api.embedding_config_legacy + ADD CONSTRAINT embedding_config_pkey PRIMARY KEY (id); + + +-- +-- Name: embedding_generation_jobs embedding_generation_jobs_pkey; Type: CONSTRAINT; Schema: kg_api; Owner: - +-- + +ALTER TABLE ONLY kg_api.embedding_generation_jobs + ADD CONSTRAINT embedding_generation_jobs_pkey PRIMARY KEY (job_id); + + +-- +-- Name: embedding_profile embedding_profile_pkey; Type: CONSTRAINT; Schema: kg_api; Owner: - +-- + +ALTER TABLE ONLY kg_api.embedding_profile + ADD CONSTRAINT embedding_profile_pkey PRIMARY KEY (id); + + +-- +-- Name: graph_epoch_kinds graph_epoch_kinds_pkey; Type: CONSTRAINT; Schema: kg_api; Owner: - +-- + +ALTER TABLE ONLY kg_api.graph_epoch_kinds + ADD CONSTRAINT graph_epoch_kinds_pkey PRIMARY KEY (kind); + + +-- +-- Name: graph_epochs graph_epochs_pkey; Type: CONSTRAINT; Schema: kg_api; Owner: - +-- + +ALTER TABLE ONLY kg_api.graph_epochs + ADD CONSTRAINT graph_epochs_pkey PRIMARY KEY (event_id); + + +-- +-- Name: jobs jobs_pkey; Type: CONSTRAINT; Schema: kg_api; Owner: - +-- + +ALTER TABLE ONLY kg_api.jobs + ADD CONSTRAINT jobs_pkey PRIMARY KEY (job_id); + + +-- +-- Name: ontology_tombstones ontology_tombstones_pkey; Type: CONSTRAINT; Schema: kg_api; Owner: - +-- + +ALTER TABLE ONLY kg_api.ontology_tombstones + ADD CONSTRAINT ontology_tombstones_pkey PRIMARY KEY (name); + + +-- +-- Name: ontology_versions ontology_versions_pkey; Type: CONSTRAINT; Schema: kg_api; Owner: - +-- + +ALTER TABLE ONLY kg_api.ontology_versions + ADD CONSTRAINT ontology_versions_pkey PRIMARY KEY (version_id); + + +-- +-- Name: ontology_versions ontology_versions_version_number_key; Type: CONSTRAINT; Schema: kg_api; Owner: - +-- + +ALTER TABLE ONLY kg_api.ontology_versions + ADD CONSTRAINT ontology_versions_version_number_key UNIQUE (version_number); + + +-- +-- Name: platform_config platform_config_pkey; Type: CONSTRAINT; Schema: kg_api; Owner: - +-- + +ALTER TABLE ONLY kg_api.platform_config + ADD CONSTRAINT platform_config_pkey PRIMARY KEY (key); + + +-- +-- Name: provider_model_catalog provider_model_catalog_pkey; Type: CONSTRAINT; Schema: kg_api; Owner: - +-- + +ALTER TABLE ONLY kg_api.provider_model_catalog + ADD CONSTRAINT provider_model_catalog_pkey PRIMARY KEY (id); + + +-- +-- Name: provider_model_catalog provider_model_catalog_provider_model_id_category_key; Type: CONSTRAINT; Schema: kg_api; Owner: - +-- + +ALTER TABLE ONLY kg_api.provider_model_catalog + ADD CONSTRAINT provider_model_catalog_provider_model_id_category_key UNIQUE (provider, model_id, category); + + +-- +-- Name: pruning_recommendations pruning_recommendations_pkey; Type: CONSTRAINT; Schema: kg_api; Owner: - +-- + +ALTER TABLE ONLY kg_api.pruning_recommendations + ADD CONSTRAINT pruning_recommendations_pkey PRIMARY KEY (id); + + +-- +-- Name: query_definitions query_definitions_pkey; Type: CONSTRAINT; Schema: kg_api; Owner: - +-- + +ALTER TABLE ONLY kg_api.query_definitions + ADD CONSTRAINT query_definitions_pkey PRIMARY KEY (id); + + +-- +-- Name: rate_limits rate_limits_pkey; Type: CONSTRAINT; Schema: kg_api; Owner: - +-- + +ALTER TABLE ONLY kg_api.rate_limits + ADD CONSTRAINT rate_limits_pkey PRIMARY KEY (client_id, endpoint, window_start); + + +-- +-- Name: relationship_vocabulary relationship_vocabulary_pkey; Type: CONSTRAINT; Schema: kg_api; Owner: - +-- + +ALTER TABLE ONLY kg_api.relationship_vocabulary + ADD CONSTRAINT relationship_vocabulary_pkey PRIMARY KEY (relationship_type); + + +-- +-- Name: scheduled_jobs scheduled_jobs_name_key; Type: CONSTRAINT; Schema: kg_api; Owner: - +-- + +ALTER TABLE ONLY kg_api.scheduled_jobs + ADD CONSTRAINT scheduled_jobs_name_key UNIQUE (name); + + +-- +-- Name: scheduled_jobs scheduled_jobs_pkey; Type: CONSTRAINT; Schema: kg_api; Owner: - +-- + +ALTER TABLE ONLY kg_api.scheduled_jobs + ADD CONSTRAINT scheduled_jobs_pkey PRIMARY KEY (id); + + +-- +-- Name: schema_migrations schema_migrations_pkey; Type: CONSTRAINT; Schema: kg_api; Owner: - +-- + +ALTER TABLE ONLY kg_api.schema_migrations + ADD CONSTRAINT schema_migrations_pkey PRIMARY KEY (version); + + +-- +-- Name: sessions sessions_pkey; Type: CONSTRAINT; Schema: kg_api; Owner: - +-- + +ALTER TABLE ONLY kg_api.sessions + ADD CONSTRAINT sessions_pkey PRIMARY KEY (session_id); + + +-- +-- Name: skipped_relationships skipped_relationships_pkey; Type: CONSTRAINT; Schema: kg_api; Owner: - +-- + +ALTER TABLE ONLY kg_api.skipped_relationships + ADD CONSTRAINT skipped_relationships_pkey PRIMARY KEY (id); + + +-- +-- Name: skipped_relationships skipped_relationships_relationship_type_from_concept_label__key; Type: CONSTRAINT; Schema: kg_api; Owner: - +-- + +ALTER TABLE ONLY kg_api.skipped_relationships + ADD CONSTRAINT skipped_relationships_relationship_type_from_concept_label__key UNIQUE (relationship_type, from_concept_label, to_concept_label); + + +-- +-- Name: source_embeddings source_embeddings_pkey; Type: CONSTRAINT; Schema: kg_api; Owner: - +-- + +ALTER TABLE ONLY kg_api.source_embeddings + ADD CONSTRAINT source_embeddings_pkey PRIMARY KEY (embedding_id); + + +-- +-- Name: source_embeddings source_embeddings_source_id_chunk_index_chunk_strategy_key; Type: CONSTRAINT; Schema: kg_api; Owner: - +-- + +ALTER TABLE ONLY kg_api.source_embeddings + ADD CONSTRAINT source_embeddings_source_id_chunk_index_chunk_strategy_key UNIQUE (source_id, chunk_index, chunk_strategy); + + +-- +-- Name: synonym_clusters synonym_clusters_pkey; Type: CONSTRAINT; Schema: kg_api; Owner: - +-- + +ALTER TABLE ONLY kg_api.synonym_clusters + ADD CONSTRAINT synonym_clusters_pkey PRIMARY KEY (cluster_id); + + +-- +-- Name: system_api_keys system_api_keys_pkey; Type: CONSTRAINT; Schema: kg_api; Owner: - +-- + +ALTER TABLE ONLY kg_api.system_api_keys + ADD CONSTRAINT system_api_keys_pkey PRIMARY KEY (provider); + + +-- +-- Name: system_initialization_status system_initialization_status_pkey; Type: CONSTRAINT; Schema: kg_api; Owner: - +-- + +ALTER TABLE ONLY kg_api.system_initialization_status + ADD CONSTRAINT system_initialization_status_pkey PRIMARY KEY (component); + + +-- +-- Name: vocabulary_audit vocabulary_audit_pkey; Type: CONSTRAINT; Schema: kg_api; Owner: - +-- + +ALTER TABLE ONLY kg_api.vocabulary_audit + ADD CONSTRAINT vocabulary_audit_pkey PRIMARY KEY (id); + + +-- +-- Name: vocabulary_config vocabulary_config_pkey; Type: CONSTRAINT; Schema: kg_api; Owner: - +-- + +ALTER TABLE ONLY kg_api.vocabulary_config + ADD CONSTRAINT vocabulary_config_pkey PRIMARY KEY (key); + + +-- +-- Name: vocabulary_history vocabulary_history_pkey; Type: CONSTRAINT; Schema: kg_api; Owner: - +-- + +ALTER TABLE ONLY kg_api.vocabulary_history + ADD CONSTRAINT vocabulary_history_pkey PRIMARY KEY (id); + + +-- +-- Name: vocabulary_suggestions vocabulary_suggestions_pkey; Type: CONSTRAINT; Schema: kg_api; Owner: - +-- + +ALTER TABLE ONLY kg_api.vocabulary_suggestions + ADD CONSTRAINT vocabulary_suggestions_pkey PRIMARY KEY (id); + + +-- +-- Name: worker_lanes worker_lanes_pkey; Type: CONSTRAINT; Schema: kg_api; Owner: - +-- + +ALTER TABLE ONLY kg_api.worker_lanes + ADD CONSTRAINT worker_lanes_pkey PRIMARY KEY (name); + + +-- +-- Name: worker_status worker_status_pkey; Type: CONSTRAINT; Schema: kg_api; Owner: - +-- + +ALTER TABLE ONLY kg_api.worker_status + ADD CONSTRAINT worker_status_pkey PRIMARY KEY (worker_id); + + +-- +-- Name: groups groups_group_name_key; Type: CONSTRAINT; Schema: kg_auth; Owner: - +-- + +ALTER TABLE ONLY kg_auth.groups + ADD CONSTRAINT groups_group_name_key UNIQUE (group_name); + + +-- +-- Name: groups groups_pkey; Type: CONSTRAINT; Schema: kg_auth; Owner: - +-- + +ALTER TABLE ONLY kg_auth.groups + ADD CONSTRAINT groups_pkey PRIMARY KEY (id); + + +-- +-- Name: oauth_access_tokens oauth_access_tokens_pkey; Type: CONSTRAINT; Schema: kg_auth; Owner: - +-- + +ALTER TABLE ONLY kg_auth.oauth_access_tokens + ADD CONSTRAINT oauth_access_tokens_pkey PRIMARY KEY (token_hash); + + +-- +-- Name: oauth_authorization_codes oauth_authorization_codes_pkey; Type: CONSTRAINT; Schema: kg_auth; Owner: - +-- + +ALTER TABLE ONLY kg_auth.oauth_authorization_codes + ADD CONSTRAINT oauth_authorization_codes_pkey PRIMARY KEY (code); + + +-- +-- Name: oauth_clients oauth_clients_pkey; Type: CONSTRAINT; Schema: kg_auth; Owner: - +-- + +ALTER TABLE ONLY kg_auth.oauth_clients + ADD CONSTRAINT oauth_clients_pkey PRIMARY KEY (client_id); + + +-- +-- Name: oauth_device_codes oauth_device_codes_pkey; Type: CONSTRAINT; Schema: kg_auth; Owner: - +-- + +ALTER TABLE ONLY kg_auth.oauth_device_codes + ADD CONSTRAINT oauth_device_codes_pkey PRIMARY KEY (device_code); + + +-- +-- Name: oauth_device_codes oauth_device_codes_user_code_key; Type: CONSTRAINT; Schema: kg_auth; Owner: - +-- + +ALTER TABLE ONLY kg_auth.oauth_device_codes + ADD CONSTRAINT oauth_device_codes_user_code_key UNIQUE (user_code); + + +-- +-- Name: oauth_refresh_tokens oauth_refresh_tokens_pkey; Type: CONSTRAINT; Schema: kg_auth; Owner: - +-- + +ALTER TABLE ONLY kg_auth.oauth_refresh_tokens + ADD CONSTRAINT oauth_refresh_tokens_pkey PRIMARY KEY (token_hash); + + +-- +-- Name: oauth_external_provider_tokens oauth_tokens_pkey; Type: CONSTRAINT; Schema: kg_auth; Owner: - +-- + +ALTER TABLE ONLY kg_auth.oauth_external_provider_tokens + ADD CONSTRAINT oauth_tokens_pkey PRIMARY KEY (token_hash); + + +-- +-- Name: resource_grants resource_grants_pkey; Type: CONSTRAINT; Schema: kg_auth; Owner: - +-- + +ALTER TABLE ONLY kg_auth.resource_grants + ADD CONSTRAINT resource_grants_pkey PRIMARY KEY (id); + + +-- +-- Name: resource_grants resource_grants_resource_type_resource_id_principal_type_pr_key; Type: CONSTRAINT; Schema: kg_auth; Owner: - +-- + +ALTER TABLE ONLY kg_auth.resource_grants + ADD CONSTRAINT resource_grants_resource_type_resource_id_principal_type_pr_key UNIQUE (resource_type, resource_id, principal_type, principal_id, permission); + + +-- +-- Name: resources resources_pkey; Type: CONSTRAINT; Schema: kg_auth; Owner: - +-- + +ALTER TABLE ONLY kg_auth.resources + ADD CONSTRAINT resources_pkey PRIMARY KEY (resource_type); + + +-- +-- Name: role_permissions role_permissions_pkey; Type: CONSTRAINT; Schema: kg_auth; Owner: - +-- + +ALTER TABLE ONLY kg_auth.role_permissions + ADD CONSTRAINT role_permissions_pkey PRIMARY KEY (id); + + +-- +-- Name: roles roles_pkey; Type: CONSTRAINT; Schema: kg_auth; Owner: - +-- + +ALTER TABLE ONLY kg_auth.roles + ADD CONSTRAINT roles_pkey PRIMARY KEY (role_name); + + +-- +-- Name: user_groups user_groups_pkey; Type: CONSTRAINT; Schema: kg_auth; Owner: - +-- + +ALTER TABLE ONLY kg_auth.user_groups + ADD CONSTRAINT user_groups_pkey PRIMARY KEY (user_id, group_id); + + +-- +-- Name: user_roles user_roles_pkey; Type: CONSTRAINT; Schema: kg_auth; Owner: - +-- + +ALTER TABLE ONLY kg_auth.user_roles + ADD CONSTRAINT user_roles_pkey PRIMARY KEY (id); + + +-- +-- Name: user_roles user_roles_user_id_role_name_scope_type_scope_id_key; Type: CONSTRAINT; Schema: kg_auth; Owner: - +-- + +ALTER TABLE ONLY kg_auth.user_roles + ADD CONSTRAINT user_roles_user_id_role_name_scope_type_scope_id_key UNIQUE (user_id, role_name, scope_type, scope_id); + + +-- +-- Name: users users_pkey; Type: CONSTRAINT; Schema: kg_auth; Owner: - +-- + +ALTER TABLE ONLY kg_auth.users + ADD CONSTRAINT users_pkey PRIMARY KEY (id); + + +-- +-- Name: users users_username_key; Type: CONSTRAINT; Schema: kg_auth; Owner: - +-- + +ALTER TABLE ONLY kg_auth.users + ADD CONSTRAINT users_username_key UNIQUE (username); + + +-- +-- Name: api_metrics api_metrics_pkey; Type: CONSTRAINT; Schema: kg_logs; Owner: - +-- + +ALTER TABLE ONLY kg_logs.api_metrics + ADD CONSTRAINT api_metrics_pkey PRIMARY KEY (id); + + +-- +-- Name: audit_trail audit_trail_pkey; Type: CONSTRAINT; Schema: kg_logs; Owner: - +-- + +ALTER TABLE ONLY kg_logs.audit_trail + ADD CONSTRAINT audit_trail_pkey PRIMARY KEY (id); + + +-- +-- Name: health_checks health_checks_pkey; Type: CONSTRAINT; Schema: kg_logs; Owner: - +-- + +ALTER TABLE ONLY kg_logs.health_checks + ADD CONSTRAINT health_checks_pkey PRIMARY KEY (id); + + +-- +-- Name: job_events job_events_pkey; Type: CONSTRAINT; Schema: kg_logs; Owner: - +-- + +ALTER TABLE ONLY kg_logs.job_events + ADD CONSTRAINT job_events_pkey PRIMARY KEY (id); + + +-- +-- Name: graph_metrics graph_metrics_pkey; Type: CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.graph_metrics + ADD CONSTRAINT graph_metrics_pkey PRIMARY KEY (metric_name); + + +-- +-- Name: schema_migrations schema_migrations_pkey; Type: CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.schema_migrations + ADD CONSTRAINT schema_migrations_pkey PRIMARY KEY (version); + + +-- +-- Name: idx_aggressiveness_profiles_builtin; Type: INDEX; Schema: kg_api; Owner: - +-- + +CREATE INDEX idx_aggressiveness_profiles_builtin ON kg_api.aggressiveness_profiles USING btree (is_builtin); + + +-- +-- Name: idx_ai_extraction_config_unique_active; Type: INDEX; Schema: kg_api; Owner: - +-- + +CREATE UNIQUE INDEX idx_ai_extraction_config_unique_active ON kg_api.ai_extraction_config USING btree (active) WHERE (active = true); + + +-- +-- Name: idx_ai_vision_config_unique_active; Type: INDEX; Schema: kg_api; Owner: - +-- + +CREATE UNIQUE INDEX idx_ai_vision_config_unique_active ON kg_api.ai_vision_config USING btree (active) WHERE (active = true); + + +-- +-- Name: idx_annealing_pressure_history_epoch; Type: INDEX; Schema: kg_api; Owner: - +-- + +CREATE INDEX idx_annealing_pressure_history_epoch ON kg_api.annealing_pressure_history USING btree (epoch DESC); + + +-- +-- Name: idx_annealing_pressure_history_recorded_at; Type: INDEX; Schema: kg_api; Owner: - +-- + +CREATE INDEX idx_annealing_pressure_history_recorded_at ON kg_api.annealing_pressure_history USING btree (recorded_at DESC); + + +-- +-- Name: idx_annealing_proposals_approved; Type: INDEX; Schema: kg_api; Owner: - +-- + +CREATE INDEX idx_annealing_proposals_approved ON kg_api.annealing_proposals USING btree (status) WHERE ((status)::text = 'approved'::text); + + +-- +-- Name: idx_annealing_proposals_control_kind; Type: INDEX; Schema: kg_api; Owner: - +-- + +CREATE INDEX idx_annealing_proposals_control_kind ON kg_api.annealing_proposals USING btree (proposal_kind, status) WHERE ((proposal_kind)::text = 'control'::text); + + +-- +-- Name: idx_annealing_proposals_ontology; Type: INDEX; Schema: kg_api; Owner: - +-- + +CREATE INDEX idx_annealing_proposals_ontology ON kg_api.annealing_proposals USING btree (ontology_name); + + +-- +-- Name: idx_annealing_proposals_status; Type: INDEX; Schema: kg_api; Owner: - +-- + +CREATE INDEX idx_annealing_proposals_status ON kg_api.annealing_proposals USING btree (status); + + +-- +-- Name: idx_artifacts_created; Type: INDEX; Schema: kg_api; Owner: - +-- + +CREATE INDEX idx_artifacts_created ON kg_api.artifacts USING btree (created_at DESC); + + +-- +-- Name: idx_artifacts_epoch; Type: INDEX; Schema: kg_api; Owner: - +-- + +CREATE INDEX idx_artifacts_epoch ON kg_api.artifacts USING btree (graph_epoch); + + +-- +-- Name: idx_artifacts_expires; Type: INDEX; Schema: kg_api; Owner: - +-- + +CREATE INDEX idx_artifacts_expires ON kg_api.artifacts USING btree (expires_at) WHERE (expires_at IS NOT NULL); + + +-- +-- Name: idx_artifacts_ontology; Type: INDEX; Schema: kg_api; Owner: - +-- + +CREATE INDEX idx_artifacts_ontology ON kg_api.artifacts USING btree (ontology); + + +-- +-- Name: idx_artifacts_owner; Type: INDEX; Schema: kg_api; Owner: - +-- + +CREATE INDEX idx_artifacts_owner ON kg_api.artifacts USING btree (owner_id); + + +-- +-- Name: idx_artifacts_owner_repr; Type: INDEX; Schema: kg_api; Owner: - +-- + +CREATE INDEX idx_artifacts_owner_repr ON kg_api.artifacts USING btree (owner_id, representation, created_at DESC); + + +-- +-- Name: idx_artifacts_owner_type; Type: INDEX; Schema: kg_api; Owner: - +-- + +CREATE INDEX idx_artifacts_owner_type ON kg_api.artifacts USING btree (owner_id, artifact_type, created_at DESC); + + +-- +-- Name: idx_artifacts_query_def; Type: INDEX; Schema: kg_api; Owner: - +-- + +CREATE INDEX idx_artifacts_query_def ON kg_api.artifacts USING btree (query_definition_id); + + +-- +-- Name: idx_artifacts_representation; Type: INDEX; Schema: kg_api; Owner: - +-- + +CREATE INDEX idx_artifacts_representation ON kg_api.artifacts USING btree (representation); + + +-- +-- Name: idx_artifacts_type; Type: INDEX; Schema: kg_api; Owner: - +-- + +CREATE INDEX idx_artifacts_type ON kg_api.artifacts USING btree (artifact_type); + + +-- +-- Name: idx_catalog_default; Type: INDEX; Schema: kg_api; Owner: - +-- + +CREATE UNIQUE INDEX idx_catalog_default ON kg_api.provider_model_catalog USING btree (provider, category) WHERE (is_default = true); + + +-- +-- Name: idx_catalog_edge_child; Type: INDEX; Schema: kg_api; Owner: - +-- + +CREATE INDEX idx_catalog_edge_child ON kg_api.catalog_edge USING btree (child_kind, child_id); + + +-- +-- Name: idx_catalog_edge_parent; Type: INDEX; Schema: kg_api; Owner: - +-- + +CREATE INDEX idx_catalog_edge_parent ON kg_api.catalog_edge USING btree (parent_kind, parent_id, child_kind); + + +-- +-- Name: idx_catalog_node_kind; Type: INDEX; Schema: kg_api; Owner: - +-- + +CREATE INDEX idx_catalog_node_kind ON kg_api.catalog_node USING btree (kind, name); + + +-- +-- Name: idx_catalog_node_name_trgm; Type: INDEX; Schema: kg_api; Owner: - +-- + +CREATE INDEX idx_catalog_node_name_trgm ON kg_api.catalog_node USING gin (name_lower public.gin_trgm_ops); + + +-- +-- Name: idx_catalog_provider; Type: INDEX; Schema: kg_api; Owner: - +-- + +CREATE INDEX idx_catalog_provider ON kg_api.provider_model_catalog USING btree (provider, enabled); + + +-- +-- Name: idx_category_confidence; Type: INDEX; Schema: kg_api; Owner: - +-- + +CREATE INDEX idx_category_confidence ON kg_api.relationship_vocabulary USING btree (category_confidence); + + +-- +-- Name: idx_category_source; Type: INDEX; Schema: kg_api; Owner: - +-- + +CREATE INDEX idx_category_source ON kg_api.relationship_vocabulary USING btree (category_source); + + +-- +-- Name: idx_concept_access_count; Type: INDEX; Schema: kg_api; Owner: - +-- + +CREATE INDEX idx_concept_access_count ON kg_api.concept_access_stats USING btree (access_count DESC); + + +-- +-- Name: idx_concept_access_last; Type: INDEX; Schema: kg_api; Owner: - +-- + +CREATE INDEX idx_concept_access_last ON kg_api.concept_access_stats USING btree (last_accessed DESC); + + +-- +-- Name: idx_direction_semantics; Type: INDEX; Schema: kg_api; Owner: - +-- + +CREATE INDEX idx_direction_semantics ON kg_api.relationship_vocabulary USING btree (direction_semantics); + + +-- +-- Name: idx_edge_usage_count; Type: INDEX; Schema: kg_api; Owner: - +-- + +CREATE INDEX idx_edge_usage_count ON kg_api.edge_usage_stats USING btree (traversal_count DESC); + + +-- +-- Name: idx_edge_usage_from; Type: INDEX; Schema: kg_api; Owner: - +-- + +CREATE INDEX idx_edge_usage_from ON kg_api.edge_usage_stats USING btree (from_concept_id); + + +-- +-- Name: idx_edge_usage_type; Type: INDEX; Schema: kg_api; Owner: - +-- + +CREATE INDEX idx_edge_usage_type ON kg_api.edge_usage_stats USING btree (relationship_type); + + +-- +-- Name: idx_embedding_config_unique_active; Type: INDEX; Schema: kg_api; Owner: - +-- + +CREATE UNIQUE INDEX idx_embedding_config_unique_active ON kg_api.embedding_config_legacy USING btree (active) WHERE (active = true); + + +-- +-- Name: idx_embedding_jobs_status; Type: INDEX; Schema: kg_api; Owner: - +-- + +CREATE INDEX idx_embedding_jobs_status ON kg_api.embedding_generation_jobs USING btree (status, created_at DESC); + + +-- +-- Name: idx_embedding_jobs_type; Type: INDEX; Schema: kg_api; Owner: - +-- + +CREATE INDEX idx_embedding_jobs_type ON kg_api.embedding_generation_jobs USING btree (job_type, created_at DESC); + + +-- +-- Name: idx_embedding_profile_unique_active; Type: INDEX; Schema: kg_api; Owner: - +-- + +CREATE UNIQUE INDEX idx_embedding_profile_unique_active ON kg_api.embedding_profile USING btree (active) WHERE (active = true); + + +-- +-- Name: idx_graph_epochs_in_flight; Type: INDEX; Schema: kg_api; Owner: - +-- + +CREATE INDEX idx_graph_epochs_in_flight ON kg_api.graph_epochs USING btree (event_id) WHERE (status = 'in_progress'::text); + + +-- +-- Name: idx_graph_epochs_kind; Type: INDEX; Schema: kg_api; Owner: - +-- + +CREATE INDEX idx_graph_epochs_kind ON kg_api.graph_epochs USING btree (kind, occurred_at); + + +-- +-- Name: idx_graph_epochs_occurred_at; Type: INDEX; Schema: kg_api; Owner: - +-- + +CREATE INDEX idx_graph_epochs_occurred_at ON kg_api.graph_epochs USING btree (occurred_at); + + +-- +-- Name: idx_hot_concepts_id; Type: INDEX; Schema: kg_api; Owner: - +-- + +CREATE INDEX idx_hot_concepts_id ON kg_api.hot_concepts USING btree (concept_id); + + +-- +-- Name: idx_hot_edges_lookup; Type: INDEX; Schema: kg_api; Owner: - +-- + +CREATE INDEX idx_hot_edges_lookup ON kg_api.hot_edges USING btree (from_concept_id, to_concept_id); + + +-- +-- Name: idx_jobs_artifact_id; Type: INDEX; Schema: kg_api; Owner: - +-- + +CREATE INDEX idx_jobs_artifact_id ON kg_api.jobs USING btree (artifact_id) WHERE (artifact_id IS NOT NULL); + + +-- +-- Name: idx_jobs_claim; Type: INDEX; Schema: kg_api; Owner: - +-- + +CREATE INDEX idx_jobs_claim ON kg_api.jobs USING btree (status, priority DESC, created_at) WHERE (status = 'approved'::text); + + +-- +-- Name: idx_jobs_ownership; Type: INDEX; Schema: kg_api; Owner: - +-- + +CREATE INDEX idx_jobs_ownership ON kg_api.jobs USING btree (created_by, is_system_job); + + +-- +-- Name: idx_jobs_source_type; Type: INDEX; Schema: kg_api; Owner: - +-- + +CREATE INDEX idx_jobs_source_type ON kg_api.jobs USING btree (source_type); + + +-- +-- Name: idx_jobs_stale_recovery; Type: INDEX; Schema: kg_api; Owner: - +-- + +CREATE INDEX idx_jobs_stale_recovery ON kg_api.jobs USING btree (claimed_at) WHERE ((status = 'running'::text) AND (claimed_at IS NOT NULL)); + + +-- +-- Name: idx_jobs_user_id; Type: INDEX; Schema: kg_api; Owner: - +-- + +CREATE INDEX idx_jobs_user_id ON kg_api.jobs USING btree (user_id); + + +-- +-- Name: idx_ontology_versions_active; Type: INDEX; Schema: kg_api; Owner: - +-- + +CREATE INDEX idx_ontology_versions_active ON kg_api.ontology_versions USING btree (is_active); + + +-- +-- Name: idx_ontology_versions_number; Type: INDEX; Schema: kg_api; Owner: - +-- + +CREATE INDEX idx_ontology_versions_number ON kg_api.ontology_versions USING btree (version_number); + + +-- +-- Name: idx_pruning_recs_created_at; Type: INDEX; Schema: kg_api; Owner: - +-- + +CREATE INDEX idx_pruning_recs_created_at ON kg_api.pruning_recommendations USING btree (created_at DESC); + + +-- +-- Name: idx_pruning_recs_expires_at; Type: INDEX; Schema: kg_api; Owner: - +-- + +CREATE INDEX idx_pruning_recs_expires_at ON kg_api.pruning_recommendations USING btree (expires_at) WHERE (expires_at IS NOT NULL); + + +-- +-- Name: idx_pruning_recs_review_level; Type: INDEX; Schema: kg_api; Owner: - +-- + +CREATE INDEX idx_pruning_recs_review_level ON kg_api.pruning_recommendations USING btree (review_level); + + +-- +-- Name: idx_pruning_recs_status; Type: INDEX; Schema: kg_api; Owner: - +-- + +CREATE INDEX idx_pruning_recs_status ON kg_api.pruning_recommendations USING btree (status); + + +-- +-- Name: idx_pruning_recs_type; Type: INDEX; Schema: kg_api; Owner: - +-- + +CREATE INDEX idx_pruning_recs_type ON kg_api.pruning_recommendations USING btree (relationship_type); + + +-- +-- Name: idx_query_def_name; Type: INDEX; Schema: kg_api; Owner: - +-- + +CREATE INDEX idx_query_def_name ON kg_api.query_definitions USING btree (name); + + +-- +-- Name: idx_query_def_owner; Type: INDEX; Schema: kg_api; Owner: - +-- + +CREATE INDEX idx_query_def_owner ON kg_api.query_definitions USING btree (owner_id); + + +-- +-- Name: idx_query_def_type; Type: INDEX; Schema: kg_api; Owner: - +-- + +CREATE INDEX idx_query_def_type ON kg_api.query_definitions USING btree (definition_type); + + +-- +-- Name: idx_query_def_updated; Type: INDEX; Schema: kg_api; Owner: - +-- + +CREATE INDEX idx_query_def_updated ON kg_api.query_definitions USING btree (updated_at DESC); + + +-- +-- Name: idx_rate_limits_window; Type: INDEX; Schema: kg_api; Owner: - +-- + +CREATE INDEX idx_rate_limits_window ON kg_api.rate_limits USING btree (window_start); + + +-- +-- Name: idx_relationship_category; Type: INDEX; Schema: kg_api; Owner: - +-- + +CREATE INDEX idx_relationship_category ON kg_api.relationship_vocabulary USING btree (category); + + +-- +-- Name: idx_scheduled_jobs_enabled; Type: INDEX; Schema: kg_api; Owner: - +-- + +CREATE INDEX idx_scheduled_jobs_enabled ON kg_api.scheduled_jobs USING btree (enabled); + + +-- +-- Name: idx_scheduled_jobs_next_run; Type: INDEX; Schema: kg_api; Owner: - +-- + +CREATE INDEX idx_scheduled_jobs_next_run ON kg_api.scheduled_jobs USING btree (next_run) WHERE (enabled = true); + + +-- +-- Name: idx_sessions_expires_at; Type: INDEX; Schema: kg_api; Owner: - +-- + +CREATE INDEX idx_sessions_expires_at ON kg_api.sessions USING btree (expires_at); + + +-- +-- Name: idx_sessions_user_id; Type: INDEX; Schema: kg_api; Owner: - +-- + +CREATE INDEX idx_sessions_user_id ON kg_api.sessions USING btree (user_id); + + +-- +-- Name: idx_skipped_count; Type: INDEX; Schema: kg_api; Owner: - +-- + +CREATE INDEX idx_skipped_count ON kg_api.skipped_relationships USING btree (occurrence_count DESC); + + +-- +-- Name: idx_skipped_first_seen; Type: INDEX; Schema: kg_api; Owner: - +-- + +CREATE INDEX idx_skipped_first_seen ON kg_api.skipped_relationships USING btree (first_seen DESC); + + +-- +-- Name: idx_skipped_ontology; Type: INDEX; Schema: kg_api; Owner: - +-- + +CREATE INDEX idx_skipped_ontology ON kg_api.skipped_relationships USING btree (ontology); + + +-- +-- Name: idx_skipped_type; Type: INDEX; Schema: kg_api; Owner: - +-- + +CREATE INDEX idx_skipped_type ON kg_api.skipped_relationships USING btree (relationship_type); + + +-- +-- Name: idx_source_embeddings_chunk_strategy; Type: INDEX; Schema: kg_api; Owner: - +-- + +CREATE INDEX idx_source_embeddings_chunk_strategy ON kg_api.source_embeddings USING btree (chunk_strategy); + + +-- +-- Name: idx_source_embeddings_created_at; Type: INDEX; Schema: kg_api; Owner: - +-- + +CREATE INDEX idx_source_embeddings_created_at ON kg_api.source_embeddings USING btree (created_at DESC); + + +-- +-- Name: idx_source_embeddings_source_hash; Type: INDEX; Schema: kg_api; Owner: - +-- + +CREATE INDEX idx_source_embeddings_source_hash ON kg_api.source_embeddings USING btree (source_hash); + + +-- +-- Name: idx_source_embeddings_source_id; Type: INDEX; Schema: kg_api; Owner: - +-- + +CREATE INDEX idx_source_embeddings_source_id ON kg_api.source_embeddings USING btree (source_id); + + +-- +-- Name: idx_synonym_clusters_active; Type: INDEX; Schema: kg_api; Owner: - +-- + +CREATE INDEX idx_synonym_clusters_active ON kg_api.synonym_clusters USING btree (is_active) WHERE (is_active = true); + + +-- +-- Name: idx_synonym_clusters_merge_recommended; Type: INDEX; Schema: kg_api; Owner: - +-- + +CREATE INDEX idx_synonym_clusters_merge_recommended ON kg_api.synonym_clusters USING btree (merge_recommended) WHERE ((merge_recommended = true) AND (is_active = true)); + + +-- +-- Name: idx_system_api_keys_updated; Type: INDEX; Schema: kg_api; Owner: - +-- + +CREATE INDEX idx_system_api_keys_updated ON kg_api.system_api_keys USING btree (updated_at); + + +-- +-- Name: idx_vocab_active; Type: INDEX; Schema: kg_api; Owner: - +-- + +CREATE INDEX idx_vocab_active ON kg_api.relationship_vocabulary USING btree (is_active); + + +-- +-- Name: idx_vocab_audit_performed_at; Type: INDEX; Schema: kg_api; Owner: - +-- + +CREATE INDEX idx_vocab_audit_performed_at ON kg_api.vocabulary_audit USING btree (performed_at DESC); + + +-- +-- Name: idx_vocab_audit_type; Type: INDEX; Schema: kg_api; Owner: - +-- + +CREATE INDEX idx_vocab_audit_type ON kg_api.vocabulary_audit USING btree (relationship_type); + + +-- +-- Name: idx_vocab_category; Type: INDEX; Schema: kg_api; Owner: - +-- + +CREATE INDEX idx_vocab_category ON kg_api.relationship_vocabulary USING btree (category); + + +-- +-- Name: idx_vocab_config_updated_at; Type: INDEX; Schema: kg_api; Owner: - +-- + +CREATE INDEX idx_vocab_config_updated_at ON kg_api.vocabulary_config USING btree (updated_at DESC); + + +-- +-- Name: idx_vocab_embedding_model; Type: INDEX; Schema: kg_api; Owner: - +-- + +CREATE INDEX idx_vocab_embedding_model ON kg_api.relationship_vocabulary USING btree (embedding_model) WHERE (embedding IS NOT NULL); + + +-- +-- Name: idx_vocab_grounding_contribution; Type: INDEX; Schema: kg_api; Owner: - +-- + +CREATE INDEX idx_vocab_grounding_contribution ON kg_api.relationship_vocabulary USING btree (grounding_contribution DESC NULLS LAST) WHERE (grounding_contribution IS NOT NULL); + + +-- +-- Name: idx_vocab_grounding_staleness; Type: INDEX; Schema: kg_api; Owner: - +-- + +CREATE INDEX idx_vocab_grounding_staleness ON kg_api.relationship_vocabulary USING btree (last_grounding_calculated NULLS FIRST) WHERE (is_active = true); + + +-- +-- Name: idx_vocab_history_action; Type: INDEX; Schema: kg_api; Owner: - +-- + +CREATE INDEX idx_vocab_history_action ON kg_api.vocabulary_history USING btree (action); + + +-- +-- Name: idx_vocab_history_performed_at; Type: INDEX; Schema: kg_api; Owner: - +-- + +CREATE INDEX idx_vocab_history_performed_at ON kg_api.vocabulary_history USING btree (performed_at DESC); + + +-- +-- Name: idx_vocab_history_performed_by; Type: INDEX; Schema: kg_api; Owner: - +-- + +CREATE INDEX idx_vocab_history_performed_by ON kg_api.vocabulary_history USING btree (performed_by); + + +-- +-- Name: idx_vocab_history_type; Type: INDEX; Schema: kg_api; Owner: - +-- + +CREATE INDEX idx_vocab_history_type ON kg_api.vocabulary_history USING btree (relationship_type); + + +-- +-- Name: idx_vocab_suggestions_confidence; Type: INDEX; Schema: kg_api; Owner: - +-- + +CREATE INDEX idx_vocab_suggestions_confidence ON kg_api.vocabulary_suggestions USING btree (confidence DESC); + + +-- +-- Name: idx_vocab_suggestions_reviewed; Type: INDEX; Schema: kg_api; Owner: - +-- + +CREATE INDEX idx_vocab_suggestions_reviewed ON kg_api.vocabulary_suggestions USING btree (reviewed); + + +-- +-- Name: idx_vocab_suggestions_type; Type: INDEX; Schema: kg_api; Owner: - +-- + +CREATE INDEX idx_vocab_suggestions_type ON kg_api.vocabulary_suggestions USING btree (relationship_type); + + +-- +-- Name: idx_vocab_usage; Type: INDEX; Schema: kg_api; Owner: - +-- + +CREATE INDEX idx_vocab_usage ON kg_api.relationship_vocabulary USING btree (usage_count DESC); + + +-- +-- Name: idx_worker_heartbeat; Type: INDEX; Schema: kg_api; Owner: - +-- + +CREATE INDEX idx_worker_heartbeat ON kg_api.worker_status USING btree (last_heartbeat DESC); + + +-- +-- Name: idx_groups_name; Type: INDEX; Schema: kg_auth; Owner: - +-- + +CREATE INDEX idx_groups_name ON kg_auth.groups USING btree (group_name); + + +-- +-- Name: idx_groups_system; Type: INDEX; Schema: kg_auth; Owner: - +-- + +CREATE INDEX idx_groups_system ON kg_auth.groups USING btree (is_system); + + +-- +-- Name: idx_oauth_access_tokens_client_id; Type: INDEX; Schema: kg_auth; Owner: - +-- + +CREATE INDEX idx_oauth_access_tokens_client_id ON kg_auth.oauth_access_tokens USING btree (client_id); + + +-- +-- Name: idx_oauth_access_tokens_expires_at; Type: INDEX; Schema: kg_auth; Owner: - +-- + +CREATE INDEX idx_oauth_access_tokens_expires_at ON kg_auth.oauth_access_tokens USING btree (expires_at); + + +-- +-- Name: idx_oauth_access_tokens_revoked; Type: INDEX; Schema: kg_auth; Owner: - +-- + +CREATE INDEX idx_oauth_access_tokens_revoked ON kg_auth.oauth_access_tokens USING btree (revoked); + + +-- +-- Name: idx_oauth_access_tokens_user_id; Type: INDEX; Schema: kg_auth; Owner: - +-- + +CREATE INDEX idx_oauth_access_tokens_user_id ON kg_auth.oauth_access_tokens USING btree (user_id); + + +-- +-- Name: idx_oauth_auth_codes_client_id; Type: INDEX; Schema: kg_auth; Owner: - +-- + +CREATE INDEX idx_oauth_auth_codes_client_id ON kg_auth.oauth_authorization_codes USING btree (client_id); + + +-- +-- Name: idx_oauth_auth_codes_expires_at; Type: INDEX; Schema: kg_auth; Owner: - +-- + +CREATE INDEX idx_oauth_auth_codes_expires_at ON kg_auth.oauth_authorization_codes USING btree (expires_at); + + +-- +-- Name: idx_oauth_auth_codes_used; Type: INDEX; Schema: kg_auth; Owner: - +-- + +CREATE INDEX idx_oauth_auth_codes_used ON kg_auth.oauth_authorization_codes USING btree (used); + + +-- +-- Name: idx_oauth_auth_codes_user_id; Type: INDEX; Schema: kg_auth; Owner: - +-- + +CREATE INDEX idx_oauth_auth_codes_user_id ON kg_auth.oauth_authorization_codes USING btree (user_id); + + +-- +-- Name: idx_oauth_clients_active; Type: INDEX; Schema: kg_auth; Owner: - +-- + +CREATE INDEX idx_oauth_clients_active ON kg_auth.oauth_clients USING btree (is_active); + + +-- +-- Name: idx_oauth_clients_created_by; Type: INDEX; Schema: kg_auth; Owner: - +-- + +CREATE INDEX idx_oauth_clients_created_by ON kg_auth.oauth_clients USING btree (created_by); + + +-- +-- Name: idx_oauth_device_codes_client_id; Type: INDEX; Schema: kg_auth; Owner: - +-- + +CREATE INDEX idx_oauth_device_codes_client_id ON kg_auth.oauth_device_codes USING btree (client_id); + + +-- +-- Name: idx_oauth_device_codes_expires_at; Type: INDEX; Schema: kg_auth; Owner: - +-- + +CREATE INDEX idx_oauth_device_codes_expires_at ON kg_auth.oauth_device_codes USING btree (expires_at); + + +-- +-- Name: idx_oauth_device_codes_status; Type: INDEX; Schema: kg_auth; Owner: - +-- + +CREATE INDEX idx_oauth_device_codes_status ON kg_auth.oauth_device_codes USING btree (status); + + +-- +-- Name: idx_oauth_device_codes_user_id; Type: INDEX; Schema: kg_auth; Owner: - +-- + +CREATE INDEX idx_oauth_device_codes_user_id ON kg_auth.oauth_device_codes USING btree (user_id); + + +-- +-- Name: idx_oauth_expires; Type: INDEX; Schema: kg_auth; Owner: - +-- + +CREATE INDEX idx_oauth_expires ON kg_auth.oauth_external_provider_tokens USING btree (expires_at); + + +-- +-- Name: idx_oauth_refresh_tokens_access_token; Type: INDEX; Schema: kg_auth; Owner: - +-- + +CREATE INDEX idx_oauth_refresh_tokens_access_token ON kg_auth.oauth_refresh_tokens USING btree (access_token_hash); + + +-- +-- Name: idx_oauth_refresh_tokens_client_id; Type: INDEX; Schema: kg_auth; Owner: - +-- + +CREATE INDEX idx_oauth_refresh_tokens_client_id ON kg_auth.oauth_refresh_tokens USING btree (client_id); + + +-- +-- Name: idx_oauth_refresh_tokens_expires_at; Type: INDEX; Schema: kg_auth; Owner: - +-- + +CREATE INDEX idx_oauth_refresh_tokens_expires_at ON kg_auth.oauth_refresh_tokens USING btree (expires_at); + + +-- +-- Name: idx_oauth_refresh_tokens_revoked; Type: INDEX; Schema: kg_auth; Owner: - +-- + +CREATE INDEX idx_oauth_refresh_tokens_revoked ON kg_auth.oauth_refresh_tokens USING btree (revoked); + + +-- +-- Name: idx_oauth_refresh_tokens_user_id; Type: INDEX; Schema: kg_auth; Owner: - +-- + +CREATE INDEX idx_oauth_refresh_tokens_user_id ON kg_auth.oauth_refresh_tokens USING btree (user_id); + + +-- +-- Name: idx_oauth_user_id; Type: INDEX; Schema: kg_auth; Owner: - +-- + +CREATE INDEX idx_oauth_user_id ON kg_auth.oauth_external_provider_tokens USING btree (user_id); + + +-- +-- Name: idx_resource_grants_lookup; Type: INDEX; Schema: kg_auth; Owner: - +-- + +CREATE INDEX idx_resource_grants_lookup ON kg_auth.resource_grants USING btree (resource_type, resource_id, principal_type, principal_id); + + +-- +-- Name: idx_resource_grants_principal; Type: INDEX; Schema: kg_auth; Owner: - +-- + +CREATE INDEX idx_resource_grants_principal ON kg_auth.resource_grants USING btree (principal_type, principal_id); + + +-- +-- Name: idx_resource_grants_resource; Type: INDEX; Schema: kg_auth; Owner: - +-- + +CREATE INDEX idx_resource_grants_resource ON kg_auth.resource_grants USING btree (resource_type, resource_id); + + +-- +-- Name: idx_resources_parent; Type: INDEX; Schema: kg_auth; Owner: - +-- + +CREATE INDEX idx_resources_parent ON kg_auth.resources USING btree (parent_type); + + +-- +-- Name: idx_role_perms_granted; Type: INDEX; Schema: kg_auth; Owner: - +-- + +CREATE INDEX idx_role_perms_granted ON kg_auth.role_permissions USING btree (granted) WHERE (granted = false); + + +-- +-- Name: idx_role_perms_resource; Type: INDEX; Schema: kg_auth; Owner: - +-- + +CREATE INDEX idx_role_perms_resource ON kg_auth.role_permissions USING btree (resource_type, action); + + +-- +-- Name: idx_role_perms_role; Type: INDEX; Schema: kg_auth; Owner: - +-- + +CREATE INDEX idx_role_perms_role ON kg_auth.role_permissions USING btree (role_name); + + +-- +-- Name: idx_role_perms_scope; Type: INDEX; Schema: kg_auth; Owner: - +-- + +CREATE INDEX idx_role_perms_scope ON kg_auth.role_permissions USING btree (scope_type, scope_id); + + +-- +-- Name: idx_role_perms_unique_with_scope; Type: INDEX; Schema: kg_auth; Owner: - +-- + +CREATE UNIQUE INDEX idx_role_perms_unique_with_scope ON kg_auth.role_permissions USING btree (role_name, resource_type, action, scope_type, scope_id) WHERE (scope_id IS NOT NULL); + + +-- +-- Name: idx_role_perms_unique_without_scope; Type: INDEX; Schema: kg_auth; Owner: - +-- + +CREATE UNIQUE INDEX idx_role_perms_unique_without_scope ON kg_auth.role_permissions USING btree (role_name, resource_type, action, scope_type) WHERE (scope_id IS NULL); + + +-- +-- Name: idx_roles_active; Type: INDEX; Schema: kg_auth; Owner: - +-- + +CREATE INDEX idx_roles_active ON kg_auth.roles USING btree (is_active); + + +-- +-- Name: idx_roles_builtin; Type: INDEX; Schema: kg_auth; Owner: - +-- + +CREATE INDEX idx_roles_builtin ON kg_auth.roles USING btree (is_builtin); + + +-- +-- Name: idx_roles_parent; Type: INDEX; Schema: kg_auth; Owner: - +-- + +CREATE INDEX idx_roles_parent ON kg_auth.roles USING btree (parent_role); + + +-- +-- Name: idx_user_groups_group; Type: INDEX; Schema: kg_auth; Owner: - +-- + +CREATE INDEX idx_user_groups_group ON kg_auth.user_groups USING btree (group_id); + + +-- +-- Name: idx_user_groups_user; Type: INDEX; Schema: kg_auth; Owner: - +-- + +CREATE INDEX idx_user_groups_user ON kg_auth.user_groups USING btree (user_id); + + +-- +-- Name: idx_user_roles_expires; Type: INDEX; Schema: kg_auth; Owner: - +-- + +CREATE INDEX idx_user_roles_expires ON kg_auth.user_roles USING btree (expires_at) WHERE (expires_at IS NOT NULL); + + +-- +-- Name: idx_user_roles_role; Type: INDEX; Schema: kg_auth; Owner: - +-- + +CREATE INDEX idx_user_roles_role ON kg_auth.user_roles USING btree (role_name); + + +-- +-- Name: idx_user_roles_scope; Type: INDEX; Schema: kg_auth; Owner: - +-- + +CREATE INDEX idx_user_roles_scope ON kg_auth.user_roles USING btree (scope_type, scope_id); + + +-- +-- Name: idx_user_roles_user; Type: INDEX; Schema: kg_auth; Owner: - +-- + +CREATE INDEX idx_user_roles_user ON kg_auth.user_roles USING btree (user_id); + + +-- +-- Name: idx_users_primary_role; Type: INDEX; Schema: kg_auth; Owner: - +-- + +CREATE INDEX idx_users_primary_role ON kg_auth.users USING btree (primary_role); + + +-- +-- Name: idx_users_username; Type: INDEX; Schema: kg_auth; Owner: - +-- + +CREATE INDEX idx_users_username ON kg_auth.users USING btree (username); + + +-- +-- Name: idx_api_metrics_duration; Type: INDEX; Schema: kg_logs; Owner: - +-- + +CREATE INDEX idx_api_metrics_duration ON kg_logs.api_metrics USING btree (duration_ms DESC); + + +-- +-- Name: idx_api_metrics_endpoint; Type: INDEX; Schema: kg_logs; Owner: - +-- + +CREATE INDEX idx_api_metrics_endpoint ON kg_logs.api_metrics USING btree (endpoint); + + +-- +-- Name: idx_api_metrics_timestamp; Type: INDEX; Schema: kg_logs; Owner: - +-- + +CREATE INDEX idx_api_metrics_timestamp ON kg_logs.api_metrics USING btree ("timestamp" DESC); + + +-- +-- Name: idx_audit_action; Type: INDEX; Schema: kg_logs; Owner: - +-- + +CREATE INDEX idx_audit_action ON kg_logs.audit_trail USING btree (action); + + +-- +-- Name: idx_audit_resource; Type: INDEX; Schema: kg_logs; Owner: - +-- + +CREATE INDEX idx_audit_resource ON kg_logs.audit_trail USING btree (resource_type, resource_id); + + +-- +-- Name: idx_audit_timestamp; Type: INDEX; Schema: kg_logs; Owner: - +-- + +CREATE INDEX idx_audit_timestamp ON kg_logs.audit_trail USING btree ("timestamp" DESC); + + +-- +-- Name: idx_audit_user_id; Type: INDEX; Schema: kg_logs; Owner: - +-- + +CREATE INDEX idx_audit_user_id ON kg_logs.audit_trail USING btree (user_id); + + +-- +-- Name: idx_health_service; Type: INDEX; Schema: kg_logs; Owner: - +-- + +CREATE INDEX idx_health_service ON kg_logs.health_checks USING btree (service); + + +-- +-- Name: idx_health_timestamp; Type: INDEX; Schema: kg_logs; Owner: - +-- + +CREATE INDEX idx_health_timestamp ON kg_logs.health_checks USING btree ("timestamp" DESC); + + +-- +-- Name: idx_job_events_job_id; Type: INDEX; Schema: kg_logs; Owner: - +-- + +CREATE INDEX idx_job_events_job_id ON kg_logs.job_events USING btree (job_id); + + +-- +-- Name: idx_job_events_timestamp; Type: INDEX; Schema: kg_logs; Owner: - +-- + +CREATE INDEX idx_job_events_timestamp ON kg_logs.job_events USING btree ("timestamp" DESC); + + +-- +-- Name: idx_graph_metrics_updated_at; Type: INDEX; Schema: public; Owner: - +-- + +CREATE INDEX idx_graph_metrics_updated_at ON public.graph_metrics USING btree (updated_at); + + +-- +-- Name: idx_schema_migrations_applied; Type: INDEX; Schema: public; Owner: - +-- + +CREATE INDEX idx_schema_migrations_applied ON public.schema_migrations USING btree (applied_at DESC); + + +-- +-- Name: aggressiveness_profiles aggressiveness_profiles_update_timestamp; Type: TRIGGER; Schema: kg_api; Owner: - +-- + +CREATE TRIGGER aggressiveness_profiles_update_timestamp BEFORE UPDATE ON kg_api.aggressiveness_profiles FOR EACH ROW EXECUTE FUNCTION kg_api.update_aggressiveness_profiles_timestamp(); + + +-- +-- Name: ai_extraction_config ai_extraction_config_update_timestamp; Type: TRIGGER; Schema: kg_api; Owner: - +-- + +CREATE TRIGGER ai_extraction_config_update_timestamp BEFORE UPDATE ON kg_api.ai_extraction_config FOR EACH ROW EXECUTE FUNCTION kg_api.update_ai_extraction_config_timestamp(); + + +-- +-- Name: ai_vision_config ai_vision_config_update_timestamp; Type: TRIGGER; Schema: kg_api; Owner: - +-- + +CREATE TRIGGER ai_vision_config_update_timestamp BEFORE UPDATE ON kg_api.ai_vision_config FOR EACH ROW EXECUTE FUNCTION kg_api.update_ai_extraction_config_timestamp(); + + +-- +-- Name: embedding_config_legacy embedding_config_update_timestamp; Type: TRIGGER; Schema: kg_api; Owner: - +-- + +CREATE TRIGGER embedding_config_update_timestamp BEFORE UPDATE ON kg_api.embedding_config_legacy FOR EACH ROW EXECUTE FUNCTION kg_api.update_embedding_config_timestamp(); + + +-- +-- Name: embedding_profile embedding_profile_update_timestamp; Type: TRIGGER; Schema: kg_api; Owner: - +-- + +CREATE TRIGGER embedding_profile_update_timestamp BEFORE UPDATE ON kg_api.embedding_profile FOR EACH ROW EXECUTE FUNCTION kg_api.update_embedding_profile_timestamp(); + + +-- +-- Name: aggressiveness_profiles prevent_builtin_aggressiveness_profile_deletion; Type: TRIGGER; Schema: kg_api; Owner: - +-- + +CREATE TRIGGER prevent_builtin_aggressiveness_profile_deletion BEFORE DELETE ON kg_api.aggressiveness_profiles FOR EACH ROW EXECUTE FUNCTION kg_api.prevent_builtin_profile_deletion(); + + +-- +-- Name: query_definitions trg_query_definition_updated; Type: TRIGGER; Schema: kg_api; Owner: - +-- + +CREATE TRIGGER trg_query_definition_updated BEFORE UPDATE ON kg_api.query_definitions FOR EACH ROW EXECUTE FUNCTION kg_api.update_query_definition_timestamp(); + + +-- +-- Name: source_embeddings trigger_update_source_embedding_timestamp; Type: TRIGGER; Schema: kg_api; Owner: - +-- + +CREATE TRIGGER trigger_update_source_embedding_timestamp BEFORE UPDATE ON kg_api.source_embeddings FOR EACH ROW EXECUTE FUNCTION kg_api.update_source_embedding_timestamp(); + + +-- +-- Name: TRIGGER trigger_update_source_embedding_timestamp ON source_embeddings; Type: COMMENT; Schema: kg_api; Owner: - +-- + +COMMENT ON TRIGGER trigger_update_source_embedding_timestamp ON kg_api.source_embeddings IS 'ADR-068: Automatically update updated_at timestamp on embedding updates'; + + +-- +-- Name: relationship_vocabulary trigger_validate_vocabulary_embedding; Type: TRIGGER; Schema: kg_api; Owner: - +-- + +CREATE TRIGGER trigger_validate_vocabulary_embedding BEFORE INSERT OR UPDATE OF embedding ON kg_api.relationship_vocabulary FOR EACH ROW WHEN ((new.embedding IS NOT NULL)) EXECUTE FUNCTION kg_api.auto_validate_vocabulary_embedding(); + + +-- +-- Name: TRIGGER trigger_validate_vocabulary_embedding ON relationship_vocabulary; Type: COMMENT; Schema: kg_api; Owner: - +-- + +COMMENT ON TRIGGER trigger_validate_vocabulary_embedding ON kg_api.relationship_vocabulary IS 'ADR-045: Automatically validates embedding structure when set/updated'; + + +-- +-- Name: user_roles audit_user_roles; Type: TRIGGER; Schema: kg_auth; Owner: - +-- + +CREATE TRIGGER audit_user_roles AFTER INSERT OR DELETE ON kg_auth.user_roles FOR EACH ROW EXECUTE FUNCTION kg_auth.audit_role_assignment(); + + +-- +-- Name: artifacts artifacts_owner_id_fkey; Type: FK CONSTRAINT; Schema: kg_api; Owner: - +-- + +ALTER TABLE ONLY kg_api.artifacts + ADD CONSTRAINT artifacts_owner_id_fkey FOREIGN KEY (owner_id) REFERENCES kg_auth.users(id); + + +-- +-- Name: artifacts artifacts_query_definition_id_fkey; Type: FK CONSTRAINT; Schema: kg_api; Owner: - +-- + +ALTER TABLE ONLY kg_api.artifacts + ADD CONSTRAINT artifacts_query_definition_id_fkey FOREIGN KEY (query_definition_id) REFERENCES kg_api.query_definitions(id) ON DELETE SET NULL; + + +-- +-- Name: concept_version_metadata concept_version_metadata_created_in_version_fkey; Type: FK CONSTRAINT; Schema: kg_api; Owner: - +-- + +ALTER TABLE ONLY kg_api.concept_version_metadata + ADD CONSTRAINT concept_version_metadata_created_in_version_fkey FOREIGN KEY (created_in_version) REFERENCES kg_api.ontology_versions(version_id); + + +-- +-- Name: concept_version_metadata concept_version_metadata_last_modified_version_fkey; Type: FK CONSTRAINT; Schema: kg_api; Owner: - +-- + +ALTER TABLE ONLY kg_api.concept_version_metadata + ADD CONSTRAINT concept_version_metadata_last_modified_version_fkey FOREIGN KEY (last_modified_version) REFERENCES kg_api.ontology_versions(version_id); + + +-- +-- Name: jobs fk_jobs_user; Type: FK CONSTRAINT; Schema: kg_api; Owner: - +-- + +ALTER TABLE ONLY kg_api.jobs + ADD CONSTRAINT fk_jobs_user FOREIGN KEY (user_id) REFERENCES kg_auth.users(id); + + +-- +-- Name: graph_epochs graph_epochs_kind_fkey; Type: FK CONSTRAINT; Schema: kg_api; Owner: - +-- + +ALTER TABLE ONLY kg_api.graph_epochs + ADD CONSTRAINT graph_epochs_kind_fkey FOREIGN KEY (kind) REFERENCES kg_api.graph_epoch_kinds(kind) ON UPDATE CASCADE ON DELETE RESTRICT; + + +-- +-- Name: jobs jobs_artifact_id_fkey; Type: FK CONSTRAINT; Schema: kg_api; Owner: - +-- + +ALTER TABLE ONLY kg_api.jobs + ADD CONSTRAINT jobs_artifact_id_fkey FOREIGN KEY (artifact_id) REFERENCES kg_api.artifacts(id) ON DELETE SET NULL; + + +-- +-- Name: query_definitions query_definitions_owner_id_fkey; Type: FK CONSTRAINT; Schema: kg_api; Owner: - +-- + +ALTER TABLE ONLY kg_api.query_definitions + ADD CONSTRAINT query_definitions_owner_id_fkey FOREIGN KEY (owner_id) REFERENCES kg_auth.users(id); + + +-- +-- Name: synonym_clusters synonym_clusters_representative_type_fkey; Type: FK CONSTRAINT; Schema: kg_api; Owner: - +-- + +ALTER TABLE ONLY kg_api.synonym_clusters + ADD CONSTRAINT synonym_clusters_representative_type_fkey FOREIGN KEY (representative_type) REFERENCES kg_api.relationship_vocabulary(relationship_type); + + +-- +-- Name: system_initialization_status system_initialization_status_initialization_job_id_fkey; Type: FK CONSTRAINT; Schema: kg_api; Owner: - +-- + +ALTER TABLE ONLY kg_api.system_initialization_status + ADD CONSTRAINT system_initialization_status_initialization_job_id_fkey FOREIGN KEY (initialization_job_id) REFERENCES kg_api.embedding_generation_jobs(job_id); + + +-- +-- Name: roles fk_roles_created_by; Type: FK CONSTRAINT; Schema: kg_auth; Owner: - +-- + +ALTER TABLE ONLY kg_auth.roles + ADD CONSTRAINT fk_roles_created_by FOREIGN KEY (created_by) REFERENCES kg_auth.users(id); + + +-- +-- Name: groups groups_created_by_fkey; Type: FK CONSTRAINT; Schema: kg_auth; Owner: - +-- + +ALTER TABLE ONLY kg_auth.groups + ADD CONSTRAINT groups_created_by_fkey FOREIGN KEY (created_by) REFERENCES kg_auth.users(id); + + +-- +-- Name: oauth_access_tokens oauth_access_tokens_client_id_fkey; Type: FK CONSTRAINT; Schema: kg_auth; Owner: - +-- + +ALTER TABLE ONLY kg_auth.oauth_access_tokens + ADD CONSTRAINT oauth_access_tokens_client_id_fkey FOREIGN KEY (client_id) REFERENCES kg_auth.oauth_clients(client_id) ON DELETE CASCADE; + + +-- +-- Name: oauth_access_tokens oauth_access_tokens_user_id_fkey; Type: FK CONSTRAINT; Schema: kg_auth; Owner: - +-- + +ALTER TABLE ONLY kg_auth.oauth_access_tokens + ADD CONSTRAINT oauth_access_tokens_user_id_fkey FOREIGN KEY (user_id) REFERENCES kg_auth.users(id) ON DELETE CASCADE; + + +-- +-- Name: oauth_authorization_codes oauth_authorization_codes_client_id_fkey; Type: FK CONSTRAINT; Schema: kg_auth; Owner: - +-- + +ALTER TABLE ONLY kg_auth.oauth_authorization_codes + ADD CONSTRAINT oauth_authorization_codes_client_id_fkey FOREIGN KEY (client_id) REFERENCES kg_auth.oauth_clients(client_id) ON DELETE CASCADE; + + +-- +-- Name: oauth_authorization_codes oauth_authorization_codes_user_id_fkey; Type: FK CONSTRAINT; Schema: kg_auth; Owner: - +-- + +ALTER TABLE ONLY kg_auth.oauth_authorization_codes + ADD CONSTRAINT oauth_authorization_codes_user_id_fkey FOREIGN KEY (user_id) REFERENCES kg_auth.users(id) ON DELETE CASCADE; + + +-- +-- Name: oauth_clients oauth_clients_created_by_fkey; Type: FK CONSTRAINT; Schema: kg_auth; Owner: - +-- + +ALTER TABLE ONLY kg_auth.oauth_clients + ADD CONSTRAINT oauth_clients_created_by_fkey FOREIGN KEY (created_by) REFERENCES kg_auth.users(id); + + +-- +-- Name: oauth_device_codes oauth_device_codes_client_id_fkey; Type: FK CONSTRAINT; Schema: kg_auth; Owner: - +-- + +ALTER TABLE ONLY kg_auth.oauth_device_codes + ADD CONSTRAINT oauth_device_codes_client_id_fkey FOREIGN KEY (client_id) REFERENCES kg_auth.oauth_clients(client_id) ON DELETE CASCADE; + + +-- +-- Name: oauth_device_codes oauth_device_codes_user_id_fkey; Type: FK CONSTRAINT; Schema: kg_auth; Owner: - +-- + +ALTER TABLE ONLY kg_auth.oauth_device_codes + ADD CONSTRAINT oauth_device_codes_user_id_fkey FOREIGN KEY (user_id) REFERENCES kg_auth.users(id) ON DELETE CASCADE; + + +-- +-- Name: oauth_refresh_tokens oauth_refresh_tokens_access_token_hash_fkey; Type: FK CONSTRAINT; Schema: kg_auth; Owner: - +-- + +ALTER TABLE ONLY kg_auth.oauth_refresh_tokens + ADD CONSTRAINT oauth_refresh_tokens_access_token_hash_fkey FOREIGN KEY (access_token_hash) REFERENCES kg_auth.oauth_access_tokens(token_hash) ON DELETE CASCADE; + + +-- +-- Name: oauth_refresh_tokens oauth_refresh_tokens_client_id_fkey; Type: FK CONSTRAINT; Schema: kg_auth; Owner: - +-- + +ALTER TABLE ONLY kg_auth.oauth_refresh_tokens + ADD CONSTRAINT oauth_refresh_tokens_client_id_fkey FOREIGN KEY (client_id) REFERENCES kg_auth.oauth_clients(client_id) ON DELETE CASCADE; + + +-- +-- Name: oauth_refresh_tokens oauth_refresh_tokens_user_id_fkey; Type: FK CONSTRAINT; Schema: kg_auth; Owner: - +-- + +ALTER TABLE ONLY kg_auth.oauth_refresh_tokens + ADD CONSTRAINT oauth_refresh_tokens_user_id_fkey FOREIGN KEY (user_id) REFERENCES kg_auth.users(id) ON DELETE CASCADE; + + +-- +-- Name: oauth_external_provider_tokens oauth_tokens_user_id_fkey; Type: FK CONSTRAINT; Schema: kg_auth; Owner: - +-- + +ALTER TABLE ONLY kg_auth.oauth_external_provider_tokens + ADD CONSTRAINT oauth_tokens_user_id_fkey FOREIGN KEY (user_id) REFERENCES kg_auth.users(id) ON DELETE CASCADE; + + +-- +-- Name: resource_grants resource_grants_granted_by_fkey; Type: FK CONSTRAINT; Schema: kg_auth; Owner: - +-- + +ALTER TABLE ONLY kg_auth.resource_grants + ADD CONSTRAINT resource_grants_granted_by_fkey FOREIGN KEY (granted_by) REFERENCES kg_auth.users(id); + + +-- +-- Name: resources resources_parent_type_fkey; Type: FK CONSTRAINT; Schema: kg_auth; Owner: - +-- + +ALTER TABLE ONLY kg_auth.resources + ADD CONSTRAINT resources_parent_type_fkey FOREIGN KEY (parent_type) REFERENCES kg_auth.resources(resource_type); + + +-- +-- Name: role_permissions role_permissions_created_by_fkey; Type: FK CONSTRAINT; Schema: kg_auth; Owner: - +-- + +ALTER TABLE ONLY kg_auth.role_permissions + ADD CONSTRAINT role_permissions_created_by_fkey FOREIGN KEY (created_by) REFERENCES kg_auth.users(id); + + +-- +-- Name: role_permissions role_permissions_inherited_from_fkey; Type: FK CONSTRAINT; Schema: kg_auth; Owner: - +-- + +ALTER TABLE ONLY kg_auth.role_permissions + ADD CONSTRAINT role_permissions_inherited_from_fkey FOREIGN KEY (inherited_from) REFERENCES kg_auth.roles(role_name); + + +-- +-- Name: role_permissions role_permissions_resource_type_fkey; Type: FK CONSTRAINT; Schema: kg_auth; Owner: - +-- + +ALTER TABLE ONLY kg_auth.role_permissions + ADD CONSTRAINT role_permissions_resource_type_fkey FOREIGN KEY (resource_type) REFERENCES kg_auth.resources(resource_type); + + +-- +-- Name: role_permissions role_permissions_role_name_fkey; Type: FK CONSTRAINT; Schema: kg_auth; Owner: - +-- + +ALTER TABLE ONLY kg_auth.role_permissions + ADD CONSTRAINT role_permissions_role_name_fkey FOREIGN KEY (role_name) REFERENCES kg_auth.roles(role_name) ON DELETE CASCADE; + + +-- +-- Name: roles roles_parent_role_fkey; Type: FK CONSTRAINT; Schema: kg_auth; Owner: - +-- + +ALTER TABLE ONLY kg_auth.roles + ADD CONSTRAINT roles_parent_role_fkey FOREIGN KEY (parent_role) REFERENCES kg_auth.roles(role_name); + + +-- +-- Name: user_groups user_groups_added_by_fkey; Type: FK CONSTRAINT; Schema: kg_auth; Owner: - +-- + +ALTER TABLE ONLY kg_auth.user_groups + ADD CONSTRAINT user_groups_added_by_fkey FOREIGN KEY (added_by) REFERENCES kg_auth.users(id); + + +-- +-- Name: user_groups user_groups_group_id_fkey; Type: FK CONSTRAINT; Schema: kg_auth; Owner: - +-- + +ALTER TABLE ONLY kg_auth.user_groups + ADD CONSTRAINT user_groups_group_id_fkey FOREIGN KEY (group_id) REFERENCES kg_auth.groups(id) ON DELETE CASCADE; + + +-- +-- Name: user_groups user_groups_user_id_fkey; Type: FK CONSTRAINT; Schema: kg_auth; Owner: - +-- + +ALTER TABLE ONLY kg_auth.user_groups + ADD CONSTRAINT user_groups_user_id_fkey FOREIGN KEY (user_id) REFERENCES kg_auth.users(id) ON DELETE CASCADE; + + +-- +-- Name: user_roles user_roles_assigned_by_fkey; Type: FK CONSTRAINT; Schema: kg_auth; Owner: - +-- + +ALTER TABLE ONLY kg_auth.user_roles + ADD CONSTRAINT user_roles_assigned_by_fkey FOREIGN KEY (assigned_by) REFERENCES kg_auth.users(id); + + +-- +-- Name: user_roles user_roles_role_name_fkey; Type: FK CONSTRAINT; Schema: kg_auth; Owner: - +-- + +ALTER TABLE ONLY kg_auth.user_roles + ADD CONSTRAINT user_roles_role_name_fkey FOREIGN KEY (role_name) REFERENCES kg_auth.roles(role_name) ON DELETE CASCADE; + + +-- +-- Name: user_roles user_roles_user_id_fkey; Type: FK CONSTRAINT; Schema: kg_auth; Owner: - +-- + +ALTER TABLE ONLY kg_auth.user_roles + ADD CONSTRAINT user_roles_user_id_fkey FOREIGN KEY (user_id) REFERENCES kg_auth.users(id) ON DELETE CASCADE; + + +-- +-- Name: users users_primary_role_fkey; Type: FK CONSTRAINT; Schema: kg_auth; Owner: - +-- + +ALTER TABLE ONLY kg_auth.users + ADD CONSTRAINT users_primary_role_fkey FOREIGN KEY (primary_role) REFERENCES kg_auth.roles(role_name); + + +-- +-- Name: TABLE aggressiveness_profiles; Type: ACL; Schema: kg_api; Owner: - +-- + +GRANT SELECT ON TABLE kg_api.aggressiveness_profiles TO PUBLIC; + + +-- +-- Name: TABLE vocabulary_config; Type: ACL; Schema: kg_api; Owner: - +-- + +GRANT SELECT ON TABLE kg_api.vocabulary_config TO PUBLIC; + + +-- +-- PostgreSQL database dump complete +-- + + + +-- ---------------------------------------------------------------------------- +-- 3. Seed data +-- ---------------------------------------------------------------------------- +-- Rows come from a valid fully-migrated database; FK triggers are disabled +-- during load because pg_dump cannot order rows of self-referencing tables +-- (kg_auth.roles.parent_role). Requires superuser — initdb runs as one. +SET session_replication_role = replica; +-- +-- PostgreSQL database dump +-- + + +-- Dumped from database version 18.1 (Debian 18.1-1.pgdg13+2) +-- Dumped by pg_dump version 18.1 (Debian 18.1-1.pgdg13+2) + +SET statement_timeout = 0; +SET lock_timeout = 0; +SET idle_in_transaction_session_timeout = 0; +SET transaction_timeout = 0; +SET client_encoding = 'UTF8'; +SET standard_conforming_strings = on; +SELECT pg_catalog.set_config('search_path', '', false); +SET check_function_bodies = false; +SET xmloption = content; +SET client_min_messages = warning; +SET row_security = off; + +-- +-- Data for Name: aggressiveness_profiles; Type: TABLE DATA; Schema: kg_api; Owner: - +-- + +INSERT INTO kg_api.aggressiveness_profiles (profile_name, control_x1, control_y1, control_x2, control_y2, description, is_builtin, created_at, updated_at) VALUES ('linear', 0, 0, 1, 1, 'Constant rate increase. Predictable, good for testing.', true, '2026-07-02 03:56:42.23943', '2026-07-02 03:56:42.23943') ON CONFLICT DO NOTHING; +INSERT INTO kg_api.aggressiveness_profiles (profile_name, control_x1, control_y1, control_x2, control_y2, description, is_builtin, created_at, updated_at) VALUES ('ease', 0.25, 0.1, 0.25, 1, 'CSS default. Balanced acceleration.', true, '2026-07-02 03:56:42.23943', '2026-07-02 03:56:42.23943') ON CONFLICT DO NOTHING; +INSERT INTO kg_api.aggressiveness_profiles (profile_name, control_x1, control_y1, control_x2, control_y2, description, is_builtin, created_at, updated_at) VALUES ('ease-in', 0.42, 0, 1, 1, 'Slow start, fast end. Gradual then sharp.', true, '2026-07-02 03:56:42.23943', '2026-07-02 03:56:42.23943') ON CONFLICT DO NOTHING; +INSERT INTO kg_api.aggressiveness_profiles (profile_name, control_x1, control_y1, control_x2, control_y2, description, is_builtin, created_at, updated_at) VALUES ('ease-out', 0, 0, 0.58, 1, 'Fast start, slow end. Sharp then gradual.', true, '2026-07-02 03:56:42.23943', '2026-07-02 03:56:42.23943') ON CONFLICT DO NOTHING; +INSERT INTO kg_api.aggressiveness_profiles (profile_name, control_x1, control_y1, control_x2, control_y2, description, is_builtin, created_at, updated_at) VALUES ('ease-in-out', 0.42, 0, 0.58, 1, 'Smooth S-curve. Balanced transitions.', true, '2026-07-02 03:56:42.23943', '2026-07-02 03:56:42.23943') ON CONFLICT DO NOTHING; +INSERT INTO kg_api.aggressiveness_profiles (profile_name, control_x1, control_y1, control_x2, control_y2, description, is_builtin, created_at, updated_at) VALUES ('aggressive', 0.1, 0, 0.9, 1, 'RECOMMENDED: Stay passive until 75%, then sharp acceleration. Best for production - avoids premature optimization.', true, '2026-07-02 03:56:42.23943', '2026-07-02 03:56:42.23943') ON CONFLICT DO NOTHING; +INSERT INTO kg_api.aggressiveness_profiles (profile_name, control_x1, control_y1, control_x2, control_y2, description, is_builtin, created_at, updated_at) VALUES ('gentle', 0.5, 0.5, 0.5, 0.5, 'Very gradual. Good for high-churn environments.', true, '2026-07-02 03:56:42.23943', '2026-07-02 03:56:42.23943') ON CONFLICT DO NOTHING; +INSERT INTO kg_api.aggressiveness_profiles (profile_name, control_x1, control_y1, control_x2, control_y2, description, is_builtin, created_at, updated_at) VALUES ('exponential', 0.7, 0, 0.84, 0, 'Explosive near limit. Use when capacity is strict.', true, '2026-07-02 03:56:42.23943', '2026-07-02 03:56:42.23943') ON CONFLICT DO NOTHING; + + +-- +-- Data for Name: ai_extraction_config; Type: TABLE DATA; Schema: kg_api; Owner: - +-- + +INSERT INTO kg_api.ai_extraction_config (id, provider, model_name, supports_vision, supports_json_mode, max_tokens, created_at, updated_at, updated_by, active, base_url, temperature, top_p, gpu_layers, num_threads, thinking_mode, max_concurrent_requests, max_retries) VALUES (1, 'openai', 'gpt-4o', true, true, 16384, '2026-07-02 03:56:41.705999+00', '2026-07-02 03:56:42.305073+00', 'system', true, NULL, 0.1, 0.9, -1, 4, 'off', 8, 8) ON CONFLICT DO NOTHING; + + +-- +-- Data for Name: ai_vision_config; Type: TABLE DATA; Schema: kg_api; Owner: - +-- + + + +-- +-- Data for Name: annealing_options; Type: TABLE DATA; Schema: kg_api; Owner: - +-- + +INSERT INTO kg_api.annealing_options (key, value, description, updated_at) VALUES ('epoch_interval', '5', 'Minimum epoch delta before a annealing cycle can trigger', '2026-07-02 03:56:43.593741+00') ON CONFLICT DO NOTHING; +INSERT INTO kg_api.annealing_options (key, value, description, updated_at) VALUES ('demotion_threshold', '0.15', 'Protection score below which an ontology is a demotion candidate', '2026-07-02 03:56:43.593741+00') ON CONFLICT DO NOTHING; +INSERT INTO kg_api.annealing_options (key, value, description, updated_at) VALUES ('promotion_min_degree', '10', 'Concept degree above which a concept is a promotion candidate', '2026-07-02 03:56:43.593741+00') ON CONFLICT DO NOTHING; +INSERT INTO kg_api.annealing_options (key, value, description, updated_at) VALUES ('max_proposals', '5', 'Maximum proposals generated per annealing cycle', '2026-07-02 03:56:43.593741+00') ON CONFLICT DO NOTHING; +INSERT INTO kg_api.annealing_options (key, value, description, updated_at) VALUES ('enabled', 'true', 'Master switch — set to false to disable all annealing', '2026-07-02 03:56:43.593741+00') ON CONFLICT DO NOTHING; +INSERT INTO kg_api.annealing_options (key, value, description, updated_at) VALUES ('overlap_threshold', '0.1', 'Minimum affinity score to create an OVERLAPS edge', '2026-07-02 03:56:43.643494+00') ON CONFLICT DO NOTHING; +INSERT INTO kg_api.annealing_options (key, value, description, updated_at) VALUES ('specializes_threshold', '0.3', 'Minimum asymmetry ratio for SPECIALIZES/GENERALIZES edges', '2026-07-02 03:56:43.643494+00') ON CONFLICT DO NOTHING; +INSERT INTO kg_api.annealing_options (key, value, description, updated_at) VALUES ('derive_edges', 'true', 'Whether annealing cycles should derive ontology-to-ontology edges', '2026-07-02 03:56:43.643494+00') ON CONFLICT DO NOTHING; +INSERT INTO kg_api.annealing_options (key, value, description, updated_at) VALUES ('primordial_pool_name', 'primordial', 'Name of the primordial/default ontology for unroutable sources', '2026-07-02 03:56:43.696505+00') ON CONFLICT DO NOTHING; +INSERT INTO kg_api.annealing_options (key, value, description, updated_at) VALUES ('automation_level', 'autonomous', 'Automation level: autonomous (default, proposals auto-execute), hitl (human review)', '2026-07-02 03:56:43.696505+00') ON CONFLICT DO NOTHING; +INSERT INTO kg_api.annealing_options (key, value, description, updated_at) VALUES ('min_ontology_age_epochs', '3', 'Ontology must exist for at least this many epochs before annealing evaluates it. Prevents acting on ontologies still settling from recent ingestion (#402 Defect C).', '2026-07-02 03:56:44.504252+00') ON CONFLICT DO NOTHING; +INSERT INTO kg_api.annealing_options (key, value, description, updated_at) VALUES ('min_ontology_concept_count', '5', 'Ontology must hold at least this many concepts before annealing evaluates it. Below this floor, scores are dominated by per-concept noise rather than ontology structure (#402 Defect C).', '2026-07-02 03:56:44.504252+00') ON CONFLICT DO NOTHING; +INSERT INTO kg_api.annealing_options (key, value, description, updated_at) VALUES ('failure_cooldown_epochs', '5', 'After a failed execution, suppress new proposals against the same (anchor_concept_id, proposal_type, target) triple for this many epochs. Bezier-tunable via ADJUST_CONTROL (ADR-206 Phase 3).', '2026-07-02 03:56:44.600978+00') ON CONFLICT DO NOTHING; +INSERT INTO kg_api.annealing_options (key, value, description, updated_at) VALUES ('max_proposals_per_cycle', '10', 'Maximum number of proposals (any verb) emitted in a single annealing cycle. Throttles decision volume under high ecological pressure. Bezier-tunable via ADJUST_CONTROL (ADR-206 Phase 3).', '2026-07-02 03:56:44.600978+00') ON CONFLICT DO NOTHING; +INSERT INTO kg_api.annealing_options (key, value, description, updated_at) VALUES ('min_activity_for_cycle', '1', 'Minimum number of epoch increments since the last cycle before a new cycle is eligible. Raises the no-op floor when the graph is quiet. Bezier-tunable via ADJUST_CONTROL (ADR-206 Phase 3).', '2026-07-02 03:56:44.600978+00') ON CONFLICT DO NOTHING; + + +-- +-- Data for Name: annealing_pressure_history; Type: TABLE DATA; Schema: kg_api; Owner: - +-- + + + +-- +-- Data for Name: annealing_proposals; Type: TABLE DATA; Schema: kg_api; Owner: - +-- + + + +-- +-- Data for Name: roles; Type: TABLE DATA; Schema: kg_auth; Owner: - +-- + +INSERT INTO kg_auth.roles (role_name, display_name, description, is_builtin, is_active, parent_role, created_at, created_by, metadata) VALUES ('read_only', 'Read Only', 'Read access to public resources', true, true, NULL, '2026-07-02 03:56:38.526738+00', NULL, '{}') ON CONFLICT DO NOTHING; +INSERT INTO kg_auth.roles (role_name, display_name, description, is_builtin, is_active, parent_role, created_at, created_by, metadata) VALUES ('contributor', 'Contributor', 'Can create and modify content', true, true, NULL, '2026-07-02 03:56:38.526738+00', NULL, '{}') ON CONFLICT DO NOTHING; +INSERT INTO kg_auth.roles (role_name, display_name, description, is_builtin, is_active, parent_role, created_at, created_by, metadata) VALUES ('platform_admin', 'Platform Administrator', 'Full platform access including critical operations. Recovery requires re-running migration.', true, true, 'admin', '2026-07-02 03:56:42.846364+00', NULL, '{}') ON CONFLICT DO NOTHING; +INSERT INTO kg_auth.roles (role_name, display_name, description, is_builtin, is_active, parent_role, created_at, created_by, metadata) VALUES ('admin', 'Administrator', 'Full system access', true, true, 'curator', '2026-07-02 03:56:38.526738+00', NULL, '{}') ON CONFLICT DO NOTHING; +INSERT INTO kg_auth.roles (role_name, display_name, description, is_builtin, is_active, parent_role, created_at, created_by, metadata) VALUES ('curator', 'Curator', 'Can approve and manage content', true, true, 'contributor', '2026-07-02 03:56:38.526738+00', NULL, '{}') ON CONFLICT DO NOTHING; + + +-- +-- Data for Name: users; Type: TABLE DATA; Schema: kg_auth; Owner: - +-- + +INSERT INTO kg_auth.users (id, username, password_hash, primary_role, created_at, last_login, disabled) VALUES (1, 'system', 'SYSTEM_NO_LOGIN', 'admin', '2026-07-02 03:56:42.478029+00', NULL, true) ON CONFLICT DO NOTHING; +INSERT INTO kg_auth.users (id, username, password_hash, primary_role, created_at, last_login, disabled) VALUES (1000, 'admin', '$2b$12$LQv3c1yqBWVHxkd0LHAkCOYz6TtxMQJqhN8/LewY5oCDxDO1b5tZK', 'platform_admin', '2026-07-02 03:56:42.478029+00', NULL, false) ON CONFLICT DO NOTHING; + + +-- +-- Data for Name: query_definitions; Type: TABLE DATA; Schema: kg_api; Owner: - +-- + + + +-- +-- Data for Name: artifacts; Type: TABLE DATA; Schema: kg_api; Owner: - +-- + + + +-- +-- Data for Name: catalog_edge; Type: TABLE DATA; Schema: kg_api; Owner: - +-- + + + +-- +-- Data for Name: catalog_node; Type: TABLE DATA; Schema: kg_api; Owner: - +-- + + + +-- +-- Data for Name: concept_access_stats; Type: TABLE DATA; Schema: kg_api; Owner: - +-- + + + +-- +-- Data for Name: ontology_versions; Type: TABLE DATA; Schema: kg_api; Owner: - +-- + +INSERT INTO kg_api.ontology_versions (version_id, version_number, created_at, created_by, change_summary, is_active, vocabulary_snapshot, types_added, types_aliased, types_deprecated, backward_compatible, migration_required) VALUES (1, '2.0.0', '2026-07-02 03:56:38.540789+00', 'system', 'Baseline schema consolidation: 30 builtin relationship types', true, '[{"added_at": "2026-07-02T03:56:38.532988+00:00", "added_by": null, "category": "logical", "synonyms": null, "embedding": null, "is_active": true, "is_builtin": true, "description": "One concept logically implies another", "usage_count": 0, "embedding_model": null, "relationship_type": "IMPLIES", "deprecation_reason": null, "embedding_generated_at": null}, {"added_at": "2026-07-02T03:56:38.532988+00:00", "added_by": null, "category": "evidential", "synonyms": null, "embedding": null, "is_active": true, "is_builtin": true, "description": "One concept provides evidence for another", "usage_count": 0, "embedding_model": null, "relationship_type": "SUPPORTS", "deprecation_reason": null, "embedding_generated_at": null}, {"added_at": "2026-07-02T03:56:38.532988+00:00", "added_by": null, "category": "logical", "synonyms": null, "embedding": null, "is_active": true, "is_builtin": true, "description": "One concept contradicts another", "usage_count": 0, "embedding_model": null, "relationship_type": "CONTRADICTS", "deprecation_reason": null, "embedding_generated_at": null}, {"added_at": "2026-07-02T03:56:38.532988+00:00", "added_by": null, "category": "causation", "synonyms": null, "embedding": null, "is_active": true, "is_builtin": true, "description": "One concept is a consequence of another", "usage_count": 0, "embedding_model": null, "relationship_type": "RESULTS_FROM", "deprecation_reason": null, "embedding_generated_at": null}, {"added_at": "2026-07-02T03:56:38.532988+00:00", "added_by": null, "category": "causation", "synonyms": null, "embedding": null, "is_active": true, "is_builtin": true, "description": "One concept makes another possible", "usage_count": 0, "embedding_model": null, "relationship_type": "ENABLES", "deprecation_reason": null, "embedding_generated_at": null}, {"added_at": "2026-07-02T03:56:38.532988+00:00", "added_by": null, "category": "dependency", "synonyms": null, "embedding": null, "is_active": true, "is_builtin": true, "description": "One concept needs another to exist or function", "usage_count": 0, "embedding_model": null, "relationship_type": "REQUIRES", "deprecation_reason": null, "embedding_generated_at": null}, {"added_at": "2026-07-02T03:56:38.532988+00:00", "added_by": null, "category": "causation", "synonyms": null, "embedding": null, "is_active": true, "is_builtin": true, "description": "One concept affects another without direct causation", "usage_count": 0, "embedding_model": null, "relationship_type": "INFLUENCES", "deprecation_reason": null, "embedding_generated_at": null}, {"added_at": "2026-07-02T03:56:38.532988+00:00", "added_by": null, "category": "composition", "synonyms": null, "embedding": null, "is_active": true, "is_builtin": true, "description": "One concept works together with another", "usage_count": 0, "embedding_model": null, "relationship_type": "COMPLEMENTS", "deprecation_reason": null, "embedding_generated_at": null}, {"added_at": "2026-07-02T03:56:38.532988+00:00", "added_by": null, "category": "semantic", "synonyms": null, "embedding": null, "is_active": true, "is_builtin": true, "description": "Two concepts share common elements", "usage_count": 0, "embedding_model": null, "relationship_type": "OVERLAPS", "deprecation_reason": null, "embedding_generated_at": null}, {"added_at": "2026-07-02T03:56:38.532988+00:00", "added_by": null, "category": "composition", "synonyms": null, "embedding": null, "is_active": true, "is_builtin": true, "description": "One concept builds upon another", "usage_count": 0, "embedding_model": null, "relationship_type": "EXTENDS", "deprecation_reason": null, "embedding_generated_at": null}, {"added_at": "2026-07-02T03:56:38.532988+00:00", "added_by": null, "category": "hierarchical", "synonyms": null, "embedding": null, "is_active": true, "is_builtin": true, "description": "One concept is a specific instance of another", "usage_count": 0, "embedding_model": null, "relationship_type": "SPECIALIZES", "deprecation_reason": null, "embedding_generated_at": null}, {"added_at": "2026-07-02T03:56:38.532988+00:00", "added_by": null, "category": "hierarchical", "synonyms": null, "embedding": null, "is_active": true, "is_builtin": true, "description": "One concept is a broader category of another", "usage_count": 0, "embedding_model": null, "relationship_type": "GENERALIZES", "deprecation_reason": null, "embedding_generated_at": null}, {"added_at": "2026-07-02T03:56:38.532988+00:00", "added_by": null, "category": "composition", "synonyms": null, "embedding": null, "is_active": true, "is_builtin": true, "description": "One concept is a component of another", "usage_count": 0, "embedding_model": null, "relationship_type": "PART_OF", "deprecation_reason": null, "embedding_generated_at": null}, {"added_at": "2026-07-02T03:56:38.532988+00:00", "added_by": null, "category": "composition", "synonyms": null, "embedding": null, "is_active": true, "is_builtin": true, "description": "One concept contains another as a component", "usage_count": 0, "embedding_model": null, "relationship_type": "HAS_PART", "deprecation_reason": null, "embedding_generated_at": null}, {"added_at": "2026-07-02T03:56:38.532988+00:00", "added_by": null, "category": "temporal", "synonyms": null, "embedding": null, "is_active": true, "is_builtin": true, "description": "One concept comes before another in sequence", "usage_count": 0, "embedding_model": null, "relationship_type": "PRECEDES", "deprecation_reason": null, "embedding_generated_at": null}, {"added_at": "2026-07-02T03:56:38.532988+00:00", "added_by": null, "category": "temporal", "synonyms": null, "embedding": null, "is_active": true, "is_builtin": true, "description": "One concept comes after another in sequence", "usage_count": 0, "embedding_model": null, "relationship_type": "FOLLOWS", "deprecation_reason": null, "embedding_generated_at": null}, {"added_at": "2026-07-02T03:56:38.532988+00:00", "added_by": null, "category": "causation", "synonyms": null, "embedding": null, "is_active": true, "is_builtin": true, "description": "One concept directly causes another", "usage_count": 0, "embedding_model": null, "relationship_type": "CAUSES", "deprecation_reason": null, "embedding_generated_at": null}, {"added_at": "2026-07-02T03:56:38.532988+00:00", "added_by": null, "category": "causation", "synonyms": null, "embedding": null, "is_active": true, "is_builtin": true, "description": "One concept stops another from occurring", "usage_count": 0, "embedding_model": null, "relationship_type": "PREVENTS", "deprecation_reason": null, "embedding_generated_at": null}, {"added_at": "2026-07-02T03:56:38.532988+00:00", "added_by": null, "category": "semantic", "synonyms": null, "embedding": null, "is_active": true, "is_builtin": true, "description": "Generic relationship when specific type unclear", "usage_count": 0, "embedding_model": null, "relationship_type": "RELATED_TO", "deprecation_reason": null, "embedding_generated_at": null}, {"added_at": "2026-07-02T03:56:38.532988+00:00", "added_by": null, "category": "semantic", "synonyms": null, "embedding": null, "is_active": true, "is_builtin": true, "description": "One concept is the inverse or contrary of another", "usage_count": 0, "embedding_model": null, "relationship_type": "OPPOSITE_OF", "deprecation_reason": null, "embedding_generated_at": null}, {"added_at": "2026-07-02T03:56:38.532988+00:00", "added_by": null, "category": "semantic", "synonyms": null, "embedding": null, "is_active": true, "is_builtin": true, "description": "One concept is similar in structure or function to another", "usage_count": 0, "embedding_model": null, "relationship_type": "ANALOGOUS_TO", "deprecation_reason": null, "embedding_generated_at": null}, {"added_at": "2026-07-02T03:56:38.532988+00:00", "added_by": null, "category": "dependency", "synonyms": null, "embedding": null, "is_active": true, "is_builtin": true, "description": "One concept relies on another for functionality", "usage_count": 0, "embedding_model": null, "relationship_type": "DEPENDS_ON", "deprecation_reason": null, "embedding_generated_at": null}, {"added_at": "2026-07-02T03:56:38.532988+00:00", "added_by": null, "category": "hierarchical", "synonyms": null, "embedding": null, "is_active": true, "is_builtin": true, "description": "One concept is a concrete example of another", "usage_count": 0, "embedding_model": null, "relationship_type": "INSTANTIATES", "deprecation_reason": null, "embedding_generated_at": null}, {"added_at": "2026-07-02T03:56:38.532988+00:00", "added_by": null, "category": "semantic", "synonyms": null, "embedding": null, "is_active": true, "is_builtin": true, "description": "One concept provides the definition or meaning of another", "usage_count": 0, "embedding_model": null, "relationship_type": "DEFINES", "deprecation_reason": null, "embedding_generated_at": null}, {"added_at": "2026-07-02T03:56:38.532988+00:00", "added_by": null, "category": "implementation", "synonyms": null, "embedding": null, "is_active": true, "is_builtin": true, "description": "One concept is realized through another", "usage_count": 0, "embedding_model": null, "relationship_type": "IMPLEMENTED_BY", "deprecation_reason": null, "embedding_generated_at": null}, {"added_at": "2026-07-02T03:56:38.532988+00:00", "added_by": null, "category": "implementation", "synonyms": null, "embedding": null, "is_active": true, "is_builtin": true, "description": "One concept realizes or executes another", "usage_count": 0, "embedding_model": null, "relationship_type": "IMPLEMENTS", "deprecation_reason": null, "embedding_generated_at": null}, {"added_at": "2026-07-02T03:56:38.532988+00:00", "added_by": null, "category": "composition", "synonyms": null, "embedding": null, "is_active": true, "is_builtin": true, "description": "One concept is made up of multiple instances of another", "usage_count": 0, "embedding_model": null, "relationship_type": "COMPOSED_OF", "deprecation_reason": null, "embedding_generated_at": null}, {"added_at": "2026-07-02T03:56:38.532988+00:00", "added_by": null, "category": "derivation", "synonyms": null, "embedding": null, "is_active": true, "is_builtin": true, "description": "One concept originates from another", "usage_count": 0, "embedding_model": null, "relationship_type": "DERIVED_FROM", "deprecation_reason": null, "embedding_generated_at": null}, {"added_at": "2026-07-02T03:56:38.532988+00:00", "added_by": null, "category": "causation", "synonyms": null, "embedding": null, "is_active": true, "is_builtin": true, "description": "One concept creates or generates another", "usage_count": 0, "embedding_model": null, "relationship_type": "PRODUCES", "deprecation_reason": null, "embedding_generated_at": null}, {"added_at": "2026-07-02T03:56:38.532988+00:00", "added_by": null, "category": "dependency", "synonyms": null, "embedding": null, "is_active": true, "is_builtin": true, "description": "One concept uses or depletes another", "usage_count": 0, "embedding_model": null, "relationship_type": "CONSUMES", "deprecation_reason": null, "embedding_generated_at": null}]', '{IMPLIES,SUPPORTS,CONTRADICTS,RESULTS_FROM,ENABLES,REQUIRES,INFLUENCES,COMPLEMENTS,OVERLAPS,EXTENDS,SPECIALIZES,GENERALIZES,PART_OF,HAS_PART,PRECEDES,FOLLOWS,CAUSES,PREVENTS,RELATED_TO,OPPOSITE_OF,ANALOGOUS_TO,DEPENDS_ON,INSTANTIATES,DEFINES,IMPLEMENTED_BY,IMPLEMENTS,COMPOSED_OF,DERIVED_FROM,PRODUCES,CONSUMES}', NULL, NULL, true, false) ON CONFLICT DO NOTHING; + + +-- +-- Data for Name: concept_version_metadata; Type: TABLE DATA; Schema: kg_api; Owner: - +-- + + + +-- +-- Data for Name: edge_usage_stats; Type: TABLE DATA; Schema: kg_api; Owner: - +-- + + + +-- +-- Data for Name: embedding_config_legacy; Type: TABLE DATA; Schema: kg_api; Owner: - +-- + +INSERT INTO kg_api.embedding_config_legacy (id, provider, model_name, embedding_dimensions, "precision", max_memory_mb, num_threads, device, batch_size, max_seq_length, normalize_embeddings, created_at, updated_at, updated_by, active, delete_protected, change_protected) VALUES (1, 'openai', 'text-embedding-3-small', 1536, 'float32', NULL, NULL, NULL, 8, 8191, true, '2026-07-02 03:56:41.655625+00', '2026-07-02 03:56:41.784803+00', 'system', false, true, true) ON CONFLICT DO NOTHING; +INSERT INTO kg_api.embedding_config_legacy (id, provider, model_name, embedding_dimensions, "precision", max_memory_mb, num_threads, device, batch_size, max_seq_length, normalize_embeddings, created_at, updated_at, updated_by, active, delete_protected, change_protected) VALUES (2, 'local', 'nomic-ai/nomic-embed-text-v1.5', 768, 'float16', 512, 4, 'cpu', 8, 8192, true, '2026-07-02 03:56:41.859416+00', '2026-07-02 03:56:41.859416+00', 'system', true, false, false) ON CONFLICT DO NOTHING; + + +-- +-- Data for Name: embedding_generation_jobs; Type: TABLE DATA; Schema: kg_api; Owner: - +-- + + + +-- +-- Data for Name: embedding_profile; Type: TABLE DATA; Schema: kg_api; Owner: - +-- + +INSERT INTO kg_api.embedding_profile (id, name, vector_space, multimodal, text_provider, text_model_name, text_loader, text_revision, text_dimensions, text_precision, text_trust_remote_code, image_provider, image_model_name, image_loader, image_revision, image_dimensions, image_precision, image_trust_remote_code, device, max_memory_mb, num_threads, batch_size, max_seq_length, normalize_embeddings, active, delete_protected, change_protected, created_at, updated_at, updated_by, text_query_prefix, text_document_prefix, image_vector_space) VALUES (2, 'OpenAI Small', 'openai-v3', false, 'openai', 'text-embedding-3-small', 'api', NULL, 1536, 'float32', false, NULL, NULL, NULL, NULL, NULL, 'float16', false, NULL, NULL, NULL, 8, 8191, true, false, true, true, '2026-07-02 03:56:43.970872+00', '2026-07-02 03:56:43.970872+00', 'migration-055', NULL, NULL, NULL) ON CONFLICT DO NOTHING; +INSERT INTO kg_api.embedding_profile (id, name, vector_space, multimodal, text_provider, text_model_name, text_loader, text_revision, text_dimensions, text_precision, text_trust_remote_code, image_provider, image_model_name, image_loader, image_revision, image_dimensions, image_precision, image_trust_remote_code, device, max_memory_mb, num_threads, batch_size, max_seq_length, normalize_embeddings, active, delete_protected, change_protected, created_at, updated_at, updated_by, text_query_prefix, text_document_prefix, image_vector_space) VALUES (1, 'Nomic v1.5', 'nomic-v1.5', false, 'local', 'nomic-ai/nomic-embed-text-v1.5', 'sentence-transformers', NULL, 768, 'float16', true, 'local', 'nomic-ai/nomic-embed-vision-v1.5', 'transformers', NULL, 768, 'float16', true, 'cpu', 512, 4, 8, 8192, true, true, false, false, '2026-07-02 03:56:43.970872+00', '2026-07-02 03:56:44.020355+00', 'migration-056', 'search_query: ', 'search_document: ', NULL) ON CONFLICT DO NOTHING; + + +-- +-- Data for Name: graph_epoch_kinds; Type: TABLE DATA; Schema: kg_api; Owner: - +-- + +INSERT INTO kg_api.graph_epoch_kinds (kind, semantic_wallclock, description) VALUES ('ingestion', true, 'External corpus arrived via the ingestion pipeline.') ON CONFLICT DO NOTHING; +INSERT INTO kg_api.graph_epoch_kinds (kind, semantic_wallclock, description) VALUES ('edit', true, 'Explicit manual mutation by an operator.') ON CONFLICT DO NOTHING; +INSERT INTO kg_api.graph_epoch_kinds (kind, semantic_wallclock, description) VALUES ('reasoning', false, 'Agent-driven reasoning session mutated the graph internally.') ON CONFLICT DO NOTHING; +INSERT INTO kg_api.graph_epoch_kinds (kind, semantic_wallclock, description) VALUES ('annealing', false, 'Ontology annealing pass mutated the graph internally.') ON CONFLICT DO NOTHING; +INSERT INTO kg_api.graph_epoch_kinds (kind, semantic_wallclock, description) VALUES ('restore', true, 'Graph state restored from a portable backup (ADR-102).') ON CONFLICT DO NOTHING; + + +-- +-- Data for Name: graph_epochs; Type: TABLE DATA; Schema: kg_api; Owner: - +-- + + + +-- +-- Data for Name: jobs; Type: TABLE DATA; Schema: kg_api; Owner: - +-- + + + +-- +-- Data for Name: ontology_tombstones; Type: TABLE DATA; Schema: kg_api; Owner: - +-- + + + +-- +-- Data for Name: platform_config; Type: TABLE DATA; Schema: kg_api; Owner: - +-- + +INSERT INTO kg_api.platform_config (key, value, description, updated_at, updated_by) VALUES ('dev_mode', 'false', 'Enable development mode with hot reload (true/false)', '2026-07-02 03:56:42.958622+00', 'system') ON CONFLICT DO NOTHING; +INSERT INTO kg_api.platform_config (key, value, description, updated_at, updated_by) VALUES ('gpu_mode', 'auto', 'GPU acceleration mode: auto, nvidia, mac, cpu', '2026-07-02 03:56:42.958622+00', 'system') ON CONFLICT DO NOTHING; +INSERT INTO kg_api.platform_config (key, value, description, updated_at, updated_by) VALUES ('initialized_at', '', 'Timestamp of initial quickstart setup', '2026-07-02 03:56:42.958622+00', 'system') ON CONFLICT DO NOTHING; +INSERT INTO kg_api.platform_config (key, value, description, updated_at, updated_by) VALUES ('initialized_by', '', 'How platform was initialized (quickstart/manual)', '2026-07-02 03:56:42.958622+00', 'system') ON CONFLICT DO NOTHING; +INSERT INTO kg_api.platform_config (key, value, description, updated_at, updated_by) VALUES ('compose_files', '', 'Comma-separated list of compose files used', '2026-07-02 03:56:42.958622+00', 'system') ON CONFLICT DO NOTHING; +INSERT INTO kg_api.platform_config (key, value, description, updated_at, updated_by) VALUES ('registration_enabled', 'true', 'Allow open self-registration via POST /auth/register (true/false). Set false for internet-facing deployments; elevated roles are assigned only through the users:create-gated admin path.', '2026-07-02 03:56:44.770093+00', 'system') ON CONFLICT DO NOTHING; +INSERT INTO kg_api.platform_config (key, value, description, updated_at, updated_by) VALUES ('search_default_similarity_threshold', '0.6', 'Default min cosine similarity for /query/search when a client omits min_similarity (0.0-1.0). ADR-508.', '2026-07-02 03:56:45.11757+00', 'system') ON CONFLICT DO NOTHING; + + +-- +-- Data for Name: provider_model_catalog; Type: TABLE DATA; Schema: kg_api; Owner: - +-- + +INSERT INTO kg_api.provider_model_catalog (id, provider, model_id, display_name, category, context_length, max_completion_tokens, supports_vision, supports_json_mode, supports_tool_use, supports_streaming, price_prompt_per_m, price_completion_per_m, price_cache_read_per_m, enabled, is_default, sort_order, upstream_provider, raw_metadata, fetched_at, created_at, updated_at) VALUES (1, 'openai', 'gpt-4o', 'GPT-4o', 'extraction', 128000, NULL, true, true, true, true, 2.50, 10.00, NULL, true, true, 1, NULL, NULL, NULL, '2026-07-02 03:56:44.217796+00', '2026-07-02 03:56:44.217796+00') ON CONFLICT DO NOTHING; +INSERT INTO kg_api.provider_model_catalog (id, provider, model_id, display_name, category, context_length, max_completion_tokens, supports_vision, supports_json_mode, supports_tool_use, supports_streaming, price_prompt_per_m, price_completion_per_m, price_cache_read_per_m, enabled, is_default, sort_order, upstream_provider, raw_metadata, fetched_at, created_at, updated_at) VALUES (2, 'openai', 'gpt-4o-mini', 'GPT-4o Mini', 'extraction', 128000, NULL, true, true, true, true, 0.15, 0.60, NULL, true, false, 2, NULL, NULL, NULL, '2026-07-02 03:56:44.217796+00', '2026-07-02 03:56:44.217796+00') ON CONFLICT DO NOTHING; +INSERT INTO kg_api.provider_model_catalog (id, provider, model_id, display_name, category, context_length, max_completion_tokens, supports_vision, supports_json_mode, supports_tool_use, supports_streaming, price_prompt_per_m, price_completion_per_m, price_cache_read_per_m, enabled, is_default, sort_order, upstream_provider, raw_metadata, fetched_at, created_at, updated_at) VALUES (3, 'openai', 'gpt-4-turbo', 'GPT-4 Turbo', 'extraction', 128000, NULL, true, true, true, true, 10.00, 30.00, NULL, false, false, 3, NULL, NULL, NULL, '2026-07-02 03:56:44.217796+00', '2026-07-02 03:56:44.217796+00') ON CONFLICT DO NOTHING; +INSERT INTO kg_api.provider_model_catalog (id, provider, model_id, display_name, category, context_length, max_completion_tokens, supports_vision, supports_json_mode, supports_tool_use, supports_streaming, price_prompt_per_m, price_completion_per_m, price_cache_read_per_m, enabled, is_default, sort_order, upstream_provider, raw_metadata, fetched_at, created_at, updated_at) VALUES (4, 'openai', 'o1-preview', 'o1 Preview', 'extraction', 128000, NULL, false, false, false, true, 15.00, 60.00, NULL, false, false, 4, NULL, NULL, NULL, '2026-07-02 03:56:44.217796+00', '2026-07-02 03:56:44.217796+00') ON CONFLICT DO NOTHING; +INSERT INTO kg_api.provider_model_catalog (id, provider, model_id, display_name, category, context_length, max_completion_tokens, supports_vision, supports_json_mode, supports_tool_use, supports_streaming, price_prompt_per_m, price_completion_per_m, price_cache_read_per_m, enabled, is_default, sort_order, upstream_provider, raw_metadata, fetched_at, created_at, updated_at) VALUES (5, 'openai', 'o1-mini', 'o1 Mini', 'extraction', 128000, NULL, false, false, false, true, 3.00, 12.00, NULL, false, false, 5, NULL, NULL, NULL, '2026-07-02 03:56:44.217796+00', '2026-07-02 03:56:44.217796+00') ON CONFLICT DO NOTHING; +INSERT INTO kg_api.provider_model_catalog (id, provider, model_id, display_name, category, context_length, max_completion_tokens, supports_vision, supports_json_mode, supports_tool_use, supports_streaming, price_prompt_per_m, price_completion_per_m, price_cache_read_per_m, enabled, is_default, sort_order, upstream_provider, raw_metadata, fetched_at, created_at, updated_at) VALUES (6, 'openai', 'text-embedding-3-small', 'Embedding v3 Small', 'embedding', 8191, NULL, false, false, false, true, 0.02, NULL, NULL, true, true, 1, NULL, NULL, NULL, '2026-07-02 03:56:44.220565+00', '2026-07-02 03:56:44.220565+00') ON CONFLICT DO NOTHING; +INSERT INTO kg_api.provider_model_catalog (id, provider, model_id, display_name, category, context_length, max_completion_tokens, supports_vision, supports_json_mode, supports_tool_use, supports_streaming, price_prompt_per_m, price_completion_per_m, price_cache_read_per_m, enabled, is_default, sort_order, upstream_provider, raw_metadata, fetched_at, created_at, updated_at) VALUES (7, 'openai', 'text-embedding-3-large', 'Embedding v3 Large', 'embedding', 8191, NULL, false, false, false, true, 0.13, NULL, NULL, true, false, 2, NULL, NULL, NULL, '2026-07-02 03:56:44.220565+00', '2026-07-02 03:56:44.220565+00') ON CONFLICT DO NOTHING; +INSERT INTO kg_api.provider_model_catalog (id, provider, model_id, display_name, category, context_length, max_completion_tokens, supports_vision, supports_json_mode, supports_tool_use, supports_streaming, price_prompt_per_m, price_completion_per_m, price_cache_read_per_m, enabled, is_default, sort_order, upstream_provider, raw_metadata, fetched_at, created_at, updated_at) VALUES (8, 'openai', 'text-embedding-ada-002', 'Embedding Ada 002', 'embedding', 8191, NULL, false, false, false, true, 0.10, NULL, NULL, false, false, 3, NULL, NULL, NULL, '2026-07-02 03:56:44.220565+00', '2026-07-02 03:56:44.220565+00') ON CONFLICT DO NOTHING; +INSERT INTO kg_api.provider_model_catalog (id, provider, model_id, display_name, category, context_length, max_completion_tokens, supports_vision, supports_json_mode, supports_tool_use, supports_streaming, price_prompt_per_m, price_completion_per_m, price_cache_read_per_m, enabled, is_default, sort_order, upstream_provider, raw_metadata, fetched_at, created_at, updated_at) VALUES (9, 'anthropic', 'claude-sonnet-4-20250514', 'Claude Sonnet 4', 'extraction', 200000, NULL, true, true, true, true, 3.00, 15.00, NULL, true, true, 1, NULL, NULL, NULL, '2026-07-02 03:56:44.223137+00', '2026-07-02 03:56:44.223137+00') ON CONFLICT DO NOTHING; +INSERT INTO kg_api.provider_model_catalog (id, provider, model_id, display_name, category, context_length, max_completion_tokens, supports_vision, supports_json_mode, supports_tool_use, supports_streaming, price_prompt_per_m, price_completion_per_m, price_cache_read_per_m, enabled, is_default, sort_order, upstream_provider, raw_metadata, fetched_at, created_at, updated_at) VALUES (10, 'anthropic', 'claude-3-5-sonnet-20241022', 'Claude 3.5 Sonnet', 'extraction', 200000, NULL, true, true, true, true, 3.00, 15.00, NULL, false, false, 2, NULL, NULL, NULL, '2026-07-02 03:56:44.223137+00', '2026-07-02 03:56:44.223137+00') ON CONFLICT DO NOTHING; +INSERT INTO kg_api.provider_model_catalog (id, provider, model_id, display_name, category, context_length, max_completion_tokens, supports_vision, supports_json_mode, supports_tool_use, supports_streaming, price_prompt_per_m, price_completion_per_m, price_cache_read_per_m, enabled, is_default, sort_order, upstream_provider, raw_metadata, fetched_at, created_at, updated_at) VALUES (11, 'anthropic', 'claude-3-opus-20240229', 'Claude 3 Opus', 'extraction', 200000, NULL, true, true, true, true, 15.00, 75.00, NULL, false, false, 3, NULL, NULL, NULL, '2026-07-02 03:56:44.223137+00', '2026-07-02 03:56:44.223137+00') ON CONFLICT DO NOTHING; +INSERT INTO kg_api.provider_model_catalog (id, provider, model_id, display_name, category, context_length, max_completion_tokens, supports_vision, supports_json_mode, supports_tool_use, supports_streaming, price_prompt_per_m, price_completion_per_m, price_cache_read_per_m, enabled, is_default, sort_order, upstream_provider, raw_metadata, fetched_at, created_at, updated_at) VALUES (12, 'anthropic', 'claude-3-sonnet-20240229', 'Claude 3 Sonnet', 'extraction', 200000, NULL, true, true, true, true, 3.00, 15.00, NULL, false, false, 4, NULL, NULL, NULL, '2026-07-02 03:56:44.223137+00', '2026-07-02 03:56:44.223137+00') ON CONFLICT DO NOTHING; +INSERT INTO kg_api.provider_model_catalog (id, provider, model_id, display_name, category, context_length, max_completion_tokens, supports_vision, supports_json_mode, supports_tool_use, supports_streaming, price_prompt_per_m, price_completion_per_m, price_cache_read_per_m, enabled, is_default, sort_order, upstream_provider, raw_metadata, fetched_at, created_at, updated_at) VALUES (13, 'anthropic', 'claude-3-haiku-20240307', 'Claude 3 Haiku', 'extraction', 200000, NULL, true, true, true, true, 0.25, 1.25, NULL, false, false, 5, NULL, NULL, NULL, '2026-07-02 03:56:44.223137+00', '2026-07-02 03:56:44.223137+00') ON CONFLICT DO NOTHING; + + +-- +-- Data for Name: pruning_recommendations; Type: TABLE DATA; Schema: kg_api; Owner: - +-- + + + +-- +-- Data for Name: rate_limits; Type: TABLE DATA; Schema: kg_api; Owner: - +-- + + + +-- +-- Data for Name: relationship_vocabulary; Type: TABLE DATA; Schema: kg_api; Owner: - +-- + +INSERT INTO kg_api.relationship_vocabulary (relationship_type, description, category, added_by, added_at, usage_count, is_active, is_builtin, synonyms, deprecation_reason, embedding, embedding_model, embedding_generated_at, grounding_contribution, last_grounding_calculated, avg_confidence, semantic_diversity, embedding_quality_score, embedding_validation_status, category_source, category_confidence, category_scores, category_ambiguous, direction_semantics) VALUES ('IMPLIES', 'One concept logically implies another', 'logical', NULL, '2026-07-02 03:56:38.532988+00', 0, true, true, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 'builtin', NULL, NULL, false, NULL) ON CONFLICT DO NOTHING; +INSERT INTO kg_api.relationship_vocabulary (relationship_type, description, category, added_by, added_at, usage_count, is_active, is_builtin, synonyms, deprecation_reason, embedding, embedding_model, embedding_generated_at, grounding_contribution, last_grounding_calculated, avg_confidence, semantic_diversity, embedding_quality_score, embedding_validation_status, category_source, category_confidence, category_scores, category_ambiguous, direction_semantics) VALUES ('SUPPORTS', 'One concept provides evidence for another', 'evidential', NULL, '2026-07-02 03:56:38.532988+00', 0, true, true, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 'builtin', NULL, NULL, false, NULL) ON CONFLICT DO NOTHING; +INSERT INTO kg_api.relationship_vocabulary (relationship_type, description, category, added_by, added_at, usage_count, is_active, is_builtin, synonyms, deprecation_reason, embedding, embedding_model, embedding_generated_at, grounding_contribution, last_grounding_calculated, avg_confidence, semantic_diversity, embedding_quality_score, embedding_validation_status, category_source, category_confidence, category_scores, category_ambiguous, direction_semantics) VALUES ('CONTRADICTS', 'One concept contradicts another', 'logical', NULL, '2026-07-02 03:56:38.532988+00', 0, true, true, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 'builtin', NULL, NULL, false, NULL) ON CONFLICT DO NOTHING; +INSERT INTO kg_api.relationship_vocabulary (relationship_type, description, category, added_by, added_at, usage_count, is_active, is_builtin, synonyms, deprecation_reason, embedding, embedding_model, embedding_generated_at, grounding_contribution, last_grounding_calculated, avg_confidence, semantic_diversity, embedding_quality_score, embedding_validation_status, category_source, category_confidence, category_scores, category_ambiguous, direction_semantics) VALUES ('RESULTS_FROM', 'One concept is a consequence of another', 'causation', NULL, '2026-07-02 03:56:38.532988+00', 0, true, true, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 'builtin', NULL, NULL, false, NULL) ON CONFLICT DO NOTHING; +INSERT INTO kg_api.relationship_vocabulary (relationship_type, description, category, added_by, added_at, usage_count, is_active, is_builtin, synonyms, deprecation_reason, embedding, embedding_model, embedding_generated_at, grounding_contribution, last_grounding_calculated, avg_confidence, semantic_diversity, embedding_quality_score, embedding_validation_status, category_source, category_confidence, category_scores, category_ambiguous, direction_semantics) VALUES ('ENABLES', 'One concept makes another possible', 'causation', NULL, '2026-07-02 03:56:38.532988+00', 0, true, true, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 'builtin', NULL, NULL, false, NULL) ON CONFLICT DO NOTHING; +INSERT INTO kg_api.relationship_vocabulary (relationship_type, description, category, added_by, added_at, usage_count, is_active, is_builtin, synonyms, deprecation_reason, embedding, embedding_model, embedding_generated_at, grounding_contribution, last_grounding_calculated, avg_confidence, semantic_diversity, embedding_quality_score, embedding_validation_status, category_source, category_confidence, category_scores, category_ambiguous, direction_semantics) VALUES ('REQUIRES', 'One concept needs another to exist or function', 'dependency', NULL, '2026-07-02 03:56:38.532988+00', 0, true, true, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 'builtin', NULL, NULL, false, NULL) ON CONFLICT DO NOTHING; +INSERT INTO kg_api.relationship_vocabulary (relationship_type, description, category, added_by, added_at, usage_count, is_active, is_builtin, synonyms, deprecation_reason, embedding, embedding_model, embedding_generated_at, grounding_contribution, last_grounding_calculated, avg_confidence, semantic_diversity, embedding_quality_score, embedding_validation_status, category_source, category_confidence, category_scores, category_ambiguous, direction_semantics) VALUES ('INFLUENCES', 'One concept affects another without direct causation', 'causation', NULL, '2026-07-02 03:56:38.532988+00', 0, true, true, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 'builtin', NULL, NULL, false, NULL) ON CONFLICT DO NOTHING; +INSERT INTO kg_api.relationship_vocabulary (relationship_type, description, category, added_by, added_at, usage_count, is_active, is_builtin, synonyms, deprecation_reason, embedding, embedding_model, embedding_generated_at, grounding_contribution, last_grounding_calculated, avg_confidence, semantic_diversity, embedding_quality_score, embedding_validation_status, category_source, category_confidence, category_scores, category_ambiguous, direction_semantics) VALUES ('COMPLEMENTS', 'One concept works together with another', 'composition', NULL, '2026-07-02 03:56:38.532988+00', 0, true, true, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 'builtin', NULL, NULL, false, NULL) ON CONFLICT DO NOTHING; +INSERT INTO kg_api.relationship_vocabulary (relationship_type, description, category, added_by, added_at, usage_count, is_active, is_builtin, synonyms, deprecation_reason, embedding, embedding_model, embedding_generated_at, grounding_contribution, last_grounding_calculated, avg_confidence, semantic_diversity, embedding_quality_score, embedding_validation_status, category_source, category_confidence, category_scores, category_ambiguous, direction_semantics) VALUES ('OVERLAPS', 'Two concepts share common elements', 'semantic', NULL, '2026-07-02 03:56:38.532988+00', 0, true, true, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 'builtin', NULL, NULL, false, NULL) ON CONFLICT DO NOTHING; +INSERT INTO kg_api.relationship_vocabulary (relationship_type, description, category, added_by, added_at, usage_count, is_active, is_builtin, synonyms, deprecation_reason, embedding, embedding_model, embedding_generated_at, grounding_contribution, last_grounding_calculated, avg_confidence, semantic_diversity, embedding_quality_score, embedding_validation_status, category_source, category_confidence, category_scores, category_ambiguous, direction_semantics) VALUES ('EXTENDS', 'One concept builds upon another', 'composition', NULL, '2026-07-02 03:56:38.532988+00', 0, true, true, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 'builtin', NULL, NULL, false, NULL) ON CONFLICT DO NOTHING; +INSERT INTO kg_api.relationship_vocabulary (relationship_type, description, category, added_by, added_at, usage_count, is_active, is_builtin, synonyms, deprecation_reason, embedding, embedding_model, embedding_generated_at, grounding_contribution, last_grounding_calculated, avg_confidence, semantic_diversity, embedding_quality_score, embedding_validation_status, category_source, category_confidence, category_scores, category_ambiguous, direction_semantics) VALUES ('SPECIALIZES', 'One concept is a specific instance of another', 'hierarchical', NULL, '2026-07-02 03:56:38.532988+00', 0, true, true, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 'builtin', NULL, NULL, false, NULL) ON CONFLICT DO NOTHING; +INSERT INTO kg_api.relationship_vocabulary (relationship_type, description, category, added_by, added_at, usage_count, is_active, is_builtin, synonyms, deprecation_reason, embedding, embedding_model, embedding_generated_at, grounding_contribution, last_grounding_calculated, avg_confidence, semantic_diversity, embedding_quality_score, embedding_validation_status, category_source, category_confidence, category_scores, category_ambiguous, direction_semantics) VALUES ('GENERALIZES', 'One concept is a broader category of another', 'hierarchical', NULL, '2026-07-02 03:56:38.532988+00', 0, true, true, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 'builtin', NULL, NULL, false, NULL) ON CONFLICT DO NOTHING; +INSERT INTO kg_api.relationship_vocabulary (relationship_type, description, category, added_by, added_at, usage_count, is_active, is_builtin, synonyms, deprecation_reason, embedding, embedding_model, embedding_generated_at, grounding_contribution, last_grounding_calculated, avg_confidence, semantic_diversity, embedding_quality_score, embedding_validation_status, category_source, category_confidence, category_scores, category_ambiguous, direction_semantics) VALUES ('PART_OF', 'One concept is a component of another', 'composition', NULL, '2026-07-02 03:56:38.532988+00', 0, true, true, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 'builtin', NULL, NULL, false, NULL) ON CONFLICT DO NOTHING; +INSERT INTO kg_api.relationship_vocabulary (relationship_type, description, category, added_by, added_at, usage_count, is_active, is_builtin, synonyms, deprecation_reason, embedding, embedding_model, embedding_generated_at, grounding_contribution, last_grounding_calculated, avg_confidence, semantic_diversity, embedding_quality_score, embedding_validation_status, category_source, category_confidence, category_scores, category_ambiguous, direction_semantics) VALUES ('HAS_PART', 'One concept contains another as a component', 'composition', NULL, '2026-07-02 03:56:38.532988+00', 0, true, true, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 'builtin', NULL, NULL, false, NULL) ON CONFLICT DO NOTHING; +INSERT INTO kg_api.relationship_vocabulary (relationship_type, description, category, added_by, added_at, usage_count, is_active, is_builtin, synonyms, deprecation_reason, embedding, embedding_model, embedding_generated_at, grounding_contribution, last_grounding_calculated, avg_confidence, semantic_diversity, embedding_quality_score, embedding_validation_status, category_source, category_confidence, category_scores, category_ambiguous, direction_semantics) VALUES ('PRECEDES', 'One concept comes before another in sequence', 'temporal', NULL, '2026-07-02 03:56:38.532988+00', 0, true, true, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 'builtin', NULL, NULL, false, NULL) ON CONFLICT DO NOTHING; +INSERT INTO kg_api.relationship_vocabulary (relationship_type, description, category, added_by, added_at, usage_count, is_active, is_builtin, synonyms, deprecation_reason, embedding, embedding_model, embedding_generated_at, grounding_contribution, last_grounding_calculated, avg_confidence, semantic_diversity, embedding_quality_score, embedding_validation_status, category_source, category_confidence, category_scores, category_ambiguous, direction_semantics) VALUES ('FOLLOWS', 'One concept comes after another in sequence', 'temporal', NULL, '2026-07-02 03:56:38.532988+00', 0, true, true, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 'builtin', NULL, NULL, false, NULL) ON CONFLICT DO NOTHING; +INSERT INTO kg_api.relationship_vocabulary (relationship_type, description, category, added_by, added_at, usage_count, is_active, is_builtin, synonyms, deprecation_reason, embedding, embedding_model, embedding_generated_at, grounding_contribution, last_grounding_calculated, avg_confidence, semantic_diversity, embedding_quality_score, embedding_validation_status, category_source, category_confidence, category_scores, category_ambiguous, direction_semantics) VALUES ('CAUSES', 'One concept directly causes another', 'causation', NULL, '2026-07-02 03:56:38.532988+00', 0, true, true, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 'builtin', NULL, NULL, false, NULL) ON CONFLICT DO NOTHING; +INSERT INTO kg_api.relationship_vocabulary (relationship_type, description, category, added_by, added_at, usage_count, is_active, is_builtin, synonyms, deprecation_reason, embedding, embedding_model, embedding_generated_at, grounding_contribution, last_grounding_calculated, avg_confidence, semantic_diversity, embedding_quality_score, embedding_validation_status, category_source, category_confidence, category_scores, category_ambiguous, direction_semantics) VALUES ('PREVENTS', 'One concept stops another from occurring', 'causation', NULL, '2026-07-02 03:56:38.532988+00', 0, true, true, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 'builtin', NULL, NULL, false, NULL) ON CONFLICT DO NOTHING; +INSERT INTO kg_api.relationship_vocabulary (relationship_type, description, category, added_by, added_at, usage_count, is_active, is_builtin, synonyms, deprecation_reason, embedding, embedding_model, embedding_generated_at, grounding_contribution, last_grounding_calculated, avg_confidence, semantic_diversity, embedding_quality_score, embedding_validation_status, category_source, category_confidence, category_scores, category_ambiguous, direction_semantics) VALUES ('RELATED_TO', 'Generic relationship when specific type unclear', 'semantic', NULL, '2026-07-02 03:56:38.532988+00', 0, true, true, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 'builtin', NULL, NULL, false, NULL) ON CONFLICT DO NOTHING; +INSERT INTO kg_api.relationship_vocabulary (relationship_type, description, category, added_by, added_at, usage_count, is_active, is_builtin, synonyms, deprecation_reason, embedding, embedding_model, embedding_generated_at, grounding_contribution, last_grounding_calculated, avg_confidence, semantic_diversity, embedding_quality_score, embedding_validation_status, category_source, category_confidence, category_scores, category_ambiguous, direction_semantics) VALUES ('OPPOSITE_OF', 'One concept is the inverse or contrary of another', 'semantic', NULL, '2026-07-02 03:56:38.532988+00', 0, true, true, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 'builtin', NULL, NULL, false, NULL) ON CONFLICT DO NOTHING; +INSERT INTO kg_api.relationship_vocabulary (relationship_type, description, category, added_by, added_at, usage_count, is_active, is_builtin, synonyms, deprecation_reason, embedding, embedding_model, embedding_generated_at, grounding_contribution, last_grounding_calculated, avg_confidence, semantic_diversity, embedding_quality_score, embedding_validation_status, category_source, category_confidence, category_scores, category_ambiguous, direction_semantics) VALUES ('ANALOGOUS_TO', 'One concept is similar in structure or function to another', 'semantic', NULL, '2026-07-02 03:56:38.532988+00', 0, true, true, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 'builtin', NULL, NULL, false, NULL) ON CONFLICT DO NOTHING; +INSERT INTO kg_api.relationship_vocabulary (relationship_type, description, category, added_by, added_at, usage_count, is_active, is_builtin, synonyms, deprecation_reason, embedding, embedding_model, embedding_generated_at, grounding_contribution, last_grounding_calculated, avg_confidence, semantic_diversity, embedding_quality_score, embedding_validation_status, category_source, category_confidence, category_scores, category_ambiguous, direction_semantics) VALUES ('DEPENDS_ON', 'One concept relies on another for functionality', 'dependency', NULL, '2026-07-02 03:56:38.532988+00', 0, true, true, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 'builtin', NULL, NULL, false, NULL) ON CONFLICT DO NOTHING; +INSERT INTO kg_api.relationship_vocabulary (relationship_type, description, category, added_by, added_at, usage_count, is_active, is_builtin, synonyms, deprecation_reason, embedding, embedding_model, embedding_generated_at, grounding_contribution, last_grounding_calculated, avg_confidence, semantic_diversity, embedding_quality_score, embedding_validation_status, category_source, category_confidence, category_scores, category_ambiguous, direction_semantics) VALUES ('INSTANTIATES', 'One concept is a concrete example of another', 'hierarchical', NULL, '2026-07-02 03:56:38.532988+00', 0, true, true, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 'builtin', NULL, NULL, false, NULL) ON CONFLICT DO NOTHING; +INSERT INTO kg_api.relationship_vocabulary (relationship_type, description, category, added_by, added_at, usage_count, is_active, is_builtin, synonyms, deprecation_reason, embedding, embedding_model, embedding_generated_at, grounding_contribution, last_grounding_calculated, avg_confidence, semantic_diversity, embedding_quality_score, embedding_validation_status, category_source, category_confidence, category_scores, category_ambiguous, direction_semantics) VALUES ('DEFINES', 'One concept provides the definition or meaning of another', 'semantic', NULL, '2026-07-02 03:56:38.532988+00', 0, true, true, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 'builtin', NULL, NULL, false, NULL) ON CONFLICT DO NOTHING; +INSERT INTO kg_api.relationship_vocabulary (relationship_type, description, category, added_by, added_at, usage_count, is_active, is_builtin, synonyms, deprecation_reason, embedding, embedding_model, embedding_generated_at, grounding_contribution, last_grounding_calculated, avg_confidence, semantic_diversity, embedding_quality_score, embedding_validation_status, category_source, category_confidence, category_scores, category_ambiguous, direction_semantics) VALUES ('IMPLEMENTED_BY', 'One concept is realized through another', 'implementation', NULL, '2026-07-02 03:56:38.532988+00', 0, true, true, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 'builtin', NULL, NULL, false, NULL) ON CONFLICT DO NOTHING; +INSERT INTO kg_api.relationship_vocabulary (relationship_type, description, category, added_by, added_at, usage_count, is_active, is_builtin, synonyms, deprecation_reason, embedding, embedding_model, embedding_generated_at, grounding_contribution, last_grounding_calculated, avg_confidence, semantic_diversity, embedding_quality_score, embedding_validation_status, category_source, category_confidence, category_scores, category_ambiguous, direction_semantics) VALUES ('IMPLEMENTS', 'One concept realizes or executes another', 'implementation', NULL, '2026-07-02 03:56:38.532988+00', 0, true, true, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 'builtin', NULL, NULL, false, NULL) ON CONFLICT DO NOTHING; +INSERT INTO kg_api.relationship_vocabulary (relationship_type, description, category, added_by, added_at, usage_count, is_active, is_builtin, synonyms, deprecation_reason, embedding, embedding_model, embedding_generated_at, grounding_contribution, last_grounding_calculated, avg_confidence, semantic_diversity, embedding_quality_score, embedding_validation_status, category_source, category_confidence, category_scores, category_ambiguous, direction_semantics) VALUES ('COMPOSED_OF', 'One concept is made up of multiple instances of another', 'composition', NULL, '2026-07-02 03:56:38.532988+00', 0, true, true, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 'builtin', NULL, NULL, false, NULL) ON CONFLICT DO NOTHING; +INSERT INTO kg_api.relationship_vocabulary (relationship_type, description, category, added_by, added_at, usage_count, is_active, is_builtin, synonyms, deprecation_reason, embedding, embedding_model, embedding_generated_at, grounding_contribution, last_grounding_calculated, avg_confidence, semantic_diversity, embedding_quality_score, embedding_validation_status, category_source, category_confidence, category_scores, category_ambiguous, direction_semantics) VALUES ('DERIVED_FROM', 'One concept originates from another', 'derivation', NULL, '2026-07-02 03:56:38.532988+00', 0, true, true, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 'builtin', NULL, NULL, false, NULL) ON CONFLICT DO NOTHING; +INSERT INTO kg_api.relationship_vocabulary (relationship_type, description, category, added_by, added_at, usage_count, is_active, is_builtin, synonyms, deprecation_reason, embedding, embedding_model, embedding_generated_at, grounding_contribution, last_grounding_calculated, avg_confidence, semantic_diversity, embedding_quality_score, embedding_validation_status, category_source, category_confidence, category_scores, category_ambiguous, direction_semantics) VALUES ('PRODUCES', 'One concept creates or generates another', 'causation', NULL, '2026-07-02 03:56:38.532988+00', 0, true, true, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 'builtin', NULL, NULL, false, NULL) ON CONFLICT DO NOTHING; +INSERT INTO kg_api.relationship_vocabulary (relationship_type, description, category, added_by, added_at, usage_count, is_active, is_builtin, synonyms, deprecation_reason, embedding, embedding_model, embedding_generated_at, grounding_contribution, last_grounding_calculated, avg_confidence, semantic_diversity, embedding_quality_score, embedding_validation_status, category_source, category_confidence, category_scores, category_ambiguous, direction_semantics) VALUES ('CONSUMES', 'One concept uses or depletes another', 'dependency', NULL, '2026-07-02 03:56:38.532988+00', 0, true, true, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 'builtin', NULL, NULL, false, NULL) ON CONFLICT DO NOTHING; + + +-- +-- Data for Name: scheduled_jobs; Type: TABLE DATA; Schema: kg_api; Owner: - +-- + +INSERT INTO kg_api.scheduled_jobs (id, name, launcher_class, schedule_cron, enabled, max_retries, retry_count, last_run, last_success, last_failure, next_run, created_at, updated_at) VALUES (1, 'category_refresh', 'CategoryRefreshLauncher', '0 */6 * * *', true, 5, 0, NULL, NULL, NULL, NULL, '2026-07-02 03:56:42.434777', '2026-07-02 03:56:42.434777') ON CONFLICT DO NOTHING; +INSERT INTO kg_api.scheduled_jobs (id, name, launcher_class, schedule_cron, enabled, max_retries, retry_count, last_run, last_success, last_failure, next_run, created_at, updated_at) VALUES (2, 'vocab_consolidation', 'VocabConsolidationLauncher', '0 */12 * * *', true, 5, 0, NULL, NULL, NULL, NULL, '2026-07-02 03:56:42.434777', '2026-07-02 03:56:42.434777') ON CONFLICT DO NOTHING; +INSERT INTO kg_api.scheduled_jobs (id, name, launcher_class, schedule_cron, enabled, max_retries, retry_count, last_run, last_success, last_failure, next_run, created_at, updated_at) VALUES (3, 'epistemic_remeasurement', 'EpistemicRemeasurementLauncher', '0 * * * *', true, 5, 0, NULL, NULL, NULL, NULL, '2026-07-02 03:56:42.766852', '2026-07-02 03:56:42.766852') ON CONFLICT DO NOTHING; +INSERT INTO kg_api.scheduled_jobs (id, name, launcher_class, schedule_cron, enabled, max_retries, retry_count, last_run, last_success, last_failure, next_run, created_at, updated_at) VALUES (4, 'projection_refresh', 'ProjectionLauncher', '15 * * * *', true, 5, 0, NULL, NULL, NULL, NULL, '2026-07-02 03:56:42.996654', '2026-07-02 03:56:42.996654') ON CONFLICT DO NOTHING; +INSERT INTO kg_api.scheduled_jobs (id, name, launcher_class, schedule_cron, enabled, max_retries, retry_count, last_run, last_success, last_failure, next_run, created_at, updated_at) VALUES (5, 'artifact_cleanup', 'ArtifactCleanupLauncher', '0 2 * * *', true, 5, 0, NULL, NULL, NULL, NULL, '2026-07-02 03:56:43.156115', '2026-07-02 03:56:43.156115') ON CONFLICT DO NOTHING; +INSERT INTO kg_api.scheduled_jobs (id, name, launcher_class, schedule_cron, enabled, max_retries, retry_count, last_run, last_success, last_failure, next_run, created_at, updated_at) VALUES (6, 'ontology_annealing', 'AnnealingLauncher', '30 */6 * * *', true, 5, 0, NULL, NULL, NULL, NULL, '2026-07-02 03:56:43.597952', '2026-07-02 03:56:43.597952') ON CONFLICT DO NOTHING; +INSERT INTO kg_api.scheduled_jobs (id, name, launcher_class, schedule_cron, enabled, max_retries, retry_count, last_run, last_success, last_failure, next_run, created_at, updated_at) VALUES (7, 'vocab_embedding', 'VocabEmbeddingLauncher', '0 * * * *', true, 5, 0, NULL, NULL, NULL, NULL, '2026-07-02 03:56:44.733554', '2026-07-02 03:56:44.733554') ON CONFLICT DO NOTHING; + + +-- +-- Data for Name: schema_migrations; Type: TABLE DATA; Schema: kg_api; Owner: - +-- + +INSERT INTO kg_api.schema_migrations (version, description, applied_at) VALUES (1, 'Initial schema (graph, sources, instances)', '2025-10-01 00:00:00') ON CONFLICT DO NOTHING; +INSERT INTO kg_api.schema_migrations (version, description, applied_at) VALUES (2, 'Add relationship vocabulary table', '2025-10-05 00:00:00') ON CONFLICT DO NOTHING; +INSERT INTO kg_api.schema_migrations (version, description, applied_at) VALUES (3, 'Add embedding configuration', '2025-10-08 00:00:00') ON CONFLICT DO NOTHING; +INSERT INTO kg_api.schema_migrations (version, description, applied_at) VALUES (4, 'Add job queue tables', '2025-10-10 00:00:00') ON CONFLICT DO NOTHING; +INSERT INTO kg_api.schema_migrations (version, description, applied_at) VALUES (5, 'Add permissions and RBAC', '2025-10-12 00:00:00') ON CONFLICT DO NOTHING; +INSERT INTO kg_api.schema_migrations (version, description, applied_at) VALUES (6, 'Add API keys and authentication', '2025-10-14 00:00:00') ON CONFLICT DO NOTHING; +INSERT INTO kg_api.schema_migrations (version, description, applied_at) VALUES (7, 'Add encrypted keys storage', '2025-10-16 00:00:00') ON CONFLICT DO NOTHING; +INSERT INTO kg_api.schema_migrations (version, description, applied_at) VALUES (8, 'Add backup integrity tracking', '2025-10-18 00:00:00') ON CONFLICT DO NOTHING; +INSERT INTO kg_api.schema_migrations (version, description, applied_at) VALUES (9, 'Add synonym detection', '2025-10-20 00:00:00') ON CONFLICT DO NOTHING; +INSERT INTO kg_api.schema_migrations (version, description, applied_at) VALUES (10, 'Add aggressiveness curve configuration', '2025-10-22 00:00:00') ON CONFLICT DO NOTHING; +INSERT INTO kg_api.schema_migrations (version, description, applied_at) VALUES (11, 'Add grounding metrics (ADR-044)', '2025-10-24 00:00:00') ON CONFLICT DO NOTHING; +INSERT INTO kg_api.schema_migrations (version, description, applied_at) VALUES (12, 'Add embedding worker support (ADR-045)', '2025-10-25 00:00:00') ON CONFLICT DO NOTHING; +INSERT INTO kg_api.schema_migrations (version, description, applied_at) VALUES (13, 'Add schema version tracking (ADR-015)', '2025-10-26 00:00:00') ON CONFLICT DO NOTHING; +INSERT INTO kg_api.schema_migrations (version, description, applied_at) VALUES (15, 'Probabilistic Vocabulary Categorization (ADR-047)', '2026-07-02 03:56:42.144809') ON CONFLICT DO NOTHING; +INSERT INTO kg_api.schema_migrations (version, description, applied_at) VALUES (16, 'LLM-Determined Relationship Direction Semantics (ADR-049)', '2026-07-02 03:56:42.192104') ON CONFLICT DO NOTHING; + + +-- +-- Data for Name: sessions; Type: TABLE DATA; Schema: kg_api; Owner: - +-- + + + +-- +-- Data for Name: skipped_relationships; Type: TABLE DATA; Schema: kg_api; Owner: - +-- + + + +-- +-- Data for Name: source_embeddings; Type: TABLE DATA; Schema: kg_api; Owner: - +-- + + + +-- +-- Data for Name: synonym_clusters; Type: TABLE DATA; Schema: kg_api; Owner: - +-- + + + +-- +-- Data for Name: system_api_keys; Type: TABLE DATA; Schema: kg_api; Owner: - +-- + + + +-- +-- Data for Name: system_initialization_status; Type: TABLE DATA; Schema: kg_api; Owner: - +-- + +INSERT INTO kg_api.system_initialization_status (component, initialized, initialized_at, initialization_job_id, version, metadata, last_processed_vocab_change_counter) VALUES ('builtin_vocabulary_embeddings', false, NULL, NULL, NULL, NULL, 0) ON CONFLICT DO NOTHING; + + +-- +-- Data for Name: vocabulary_audit; Type: TABLE DATA; Schema: kg_api; Owner: - +-- + + + +-- +-- Data for Name: vocabulary_config; Type: TABLE DATA; Schema: kg_api; Owner: - +-- + +INSERT INTO kg_api.vocabulary_config (key, value, description, updated_at, updated_by) VALUES ('vocab_min', '30', 'Minimum vocabulary size (protected core types)', '2026-07-02 03:56:38.535746+00', 'system') ON CONFLICT DO NOTHING; +INSERT INTO kg_api.vocabulary_config (key, value, description, updated_at, updated_by) VALUES ('vocab_max', '90', 'Maximum vocabulary size (soft limit)', '2026-07-02 03:56:38.535746+00', 'system') ON CONFLICT DO NOTHING; +INSERT INTO kg_api.vocabulary_config (key, value, description, updated_at, updated_by) VALUES ('aggressiveness_profile', 'aggressive', 'Bezier curve profile for pruning aggressiveness', '2026-07-02 03:56:38.535746+00', 'system') ON CONFLICT DO NOTHING; +INSERT INTO kg_api.vocabulary_config (key, value, description, updated_at, updated_by) VALUES ('pruning_mode', 'hitl', 'Decision mode: naive, hitl, aitl', '2026-07-02 03:56:38.535746+00', 'system') ON CONFLICT DO NOTHING; +INSERT INTO kg_api.vocabulary_config (key, value, description, updated_at, updated_by) VALUES ('auto_expand_enabled', 'false', 'Enable automatic vocabulary expansion', '2026-07-02 03:56:38.535746+00', 'system') ON CONFLICT DO NOTHING; +INSERT INTO kg_api.vocabulary_config (key, value, description, updated_at, updated_by) VALUES ('synonym_threshold_strong', '0.90', 'Strong synonym threshold (auto-merge)', '2026-07-02 03:56:38.535746+00', 'system') ON CONFLICT DO NOTHING; +INSERT INTO kg_api.vocabulary_config (key, value, description, updated_at, updated_by) VALUES ('synonym_threshold_moderate', '0.70', 'Moderate synonym threshold (review)', '2026-07-02 03:56:38.535746+00', 'system') ON CONFLICT DO NOTHING; +INSERT INTO kg_api.vocabulary_config (key, value, description, updated_at, updated_by) VALUES ('low_value_threshold', '1.0', 'Value score threshold for pruning consideration', '2026-07-02 03:56:38.535746+00', 'system') ON CONFLICT DO NOTHING; +INSERT INTO kg_api.vocabulary_config (key, value, description, updated_at, updated_by) VALUES ('vocab_emergency', '300', 'Hard limit - blocks new additions (EMERGENCY threshold, increased from 200)', '2026-07-02 03:56:38.535746+00', 'system') ON CONFLICT DO NOTHING; +INSERT INTO kg_api.vocabulary_config (key, value, description, updated_at, updated_by) VALUES ('consolidation_similarity_threshold', '0.90', 'Minimum similarity for auto-merge (0.0-1.0)', '2026-07-02 03:56:42.233625+00', 'system') ON CONFLICT DO NOTHING; + + +-- +-- Data for Name: vocabulary_history; Type: TABLE DATA; Schema: kg_api; Owner: - +-- + + + +-- +-- Data for Name: vocabulary_suggestions; Type: TABLE DATA; Schema: kg_api; Owner: - +-- + + + +-- +-- Data for Name: worker_lanes; Type: TABLE DATA; Schema: kg_api; Owner: - +-- + +INSERT INTO kg_api.worker_lanes (name, job_types, max_slots, poll_interval_ms, stale_timeout_minutes, enabled, updated_at) VALUES ('interactive', '{ingestion,ingest_image,polarity}', 2, 2000, 30, true, '2026-07-02 03:56:38.538293+00') ON CONFLICT DO NOTHING; +INSERT INTO kg_api.worker_lanes (name, job_types, max_slots, poll_interval_ms, stale_timeout_minutes, enabled, updated_at) VALUES ('maintenance', '{projection,vocab_refresh,epistemic_remeasurement,ontology_annealing,proposal_execution}', 1, 15000, 60, true, '2026-07-02 03:56:38.538293+00') ON CONFLICT DO NOTHING; +INSERT INTO kg_api.worker_lanes (name, job_types, max_slots, poll_interval_ms, stale_timeout_minutes, enabled, updated_at) VALUES ('system', '{restore,vocab_consolidate,artifact_cleanup,source_embedding}', 1, 30000, 120, true, '2026-07-02 03:56:38.538293+00') ON CONFLICT DO NOTHING; + + +-- +-- Data for Name: worker_status; Type: TABLE DATA; Schema: kg_api; Owner: - +-- + + + +-- +-- Data for Name: groups; Type: TABLE DATA; Schema: kg_auth; Owner: - +-- + +INSERT INTO kg_auth.groups (id, group_name, display_name, description, is_system, created_at, created_by) VALUES (1, 'public', 'All Users', 'All authenticated users - implicit membership', true, '2026-07-02 03:56:43.073979+00', 1) ON CONFLICT DO NOTHING; +INSERT INTO kg_auth.groups (id, group_name, display_name, description, is_system, created_at, created_by) VALUES (2, 'admins', 'Administrators', 'Platform administrators with elevated access', true, '2026-07-02 03:56:43.073979+00', 1) ON CONFLICT DO NOTHING; + + +-- +-- Data for Name: oauth_clients; Type: TABLE DATA; Schema: kg_auth; Owner: - +-- + +INSERT INTO kg_auth.oauth_clients (client_id, client_secret_hash, client_name, client_type, grant_types, redirect_uris, scopes, is_active, created_by, created_at, metadata) VALUES ('kg-cli', NULL, 'Knowledge Graph CLI', 'public', '{urn:ietf:params:oauth:grant-type:device_code,refresh_token}', NULL, '{read:*,write:*}', true, NULL, '2026-07-02 03:56:42.556666+00', '{"builtin": true, "description": "Official CLI tool for knowledge graph operations"}') ON CONFLICT DO NOTHING; +INSERT INTO kg_auth.oauth_clients (client_id, client_secret_hash, client_name, client_type, grant_types, redirect_uris, scopes, is_active, created_by, created_at, metadata) VALUES ('kg-web', NULL, 'Knowledge Graph Web', 'public', '{authorization_code,refresh_token}', '{http://localhost:3000/callback,https://viz.kg.example.com/callback}', '{read:*,write:*}', true, NULL, '2026-07-02 03:56:42.556666+00', '{"builtin": true, "description": "Web-based knowledge graph interface"}') ON CONFLICT DO NOTHING; +INSERT INTO kg_auth.oauth_clients (client_id, client_secret_hash, client_name, client_type, grant_types, redirect_uris, scopes, is_active, created_by, created_at, metadata) VALUES ('kg-mcp', NULL, 'Knowledge Graph MCP Server', 'confidential', '{client_credentials}', NULL, '{read:*,write:*}', true, NULL, '2026-07-02 03:56:42.556666+00', '{"builtin": true, "description": "Model Context Protocol server for AI assistants"}') ON CONFLICT DO NOTHING; + + +-- +-- Data for Name: oauth_access_tokens; Type: TABLE DATA; Schema: kg_auth; Owner: - +-- + + + +-- +-- Data for Name: oauth_authorization_codes; Type: TABLE DATA; Schema: kg_auth; Owner: - +-- + + + +-- +-- Data for Name: oauth_device_codes; Type: TABLE DATA; Schema: kg_auth; Owner: - +-- + + + +-- +-- Data for Name: oauth_external_provider_tokens; Type: TABLE DATA; Schema: kg_auth; Owner: - +-- + + + +-- +-- Data for Name: oauth_refresh_tokens; Type: TABLE DATA; Schema: kg_auth; Owner: - +-- + + + +-- +-- Data for Name: resource_grants; Type: TABLE DATA; Schema: kg_auth; Owner: - +-- + + + +-- +-- Data for Name: resources; Type: TABLE DATA; Schema: kg_auth; Owner: - +-- + +INSERT INTO kg_auth.resources (resource_type, description, parent_type, available_actions, supports_scoping, metadata, registered_at, registered_by) VALUES ('concepts', 'Knowledge graph concepts and nodes', NULL, '{read,write,delete}', false, '{}', '2026-07-02 03:56:38.523936+00', 'system') ON CONFLICT DO NOTHING; +INSERT INTO kg_auth.resources (resource_type, description, parent_type, available_actions, supports_scoping, metadata, registered_at, registered_by) VALUES ('vocabulary', 'Relationship vocabulary management', NULL, '{read,write,approve,delete}', false, '{}', '2026-07-02 03:56:38.523936+00', 'system') ON CONFLICT DO NOTHING; +INSERT INTO kg_auth.resources (resource_type, description, parent_type, available_actions, supports_scoping, metadata, registered_at, registered_by) VALUES ('jobs', 'Ingestion job management', NULL, '{read,write,approve,delete}', false, '{}', '2026-07-02 03:56:38.523936+00', 'system') ON CONFLICT DO NOTHING; +INSERT INTO kg_auth.resources (resource_type, description, parent_type, available_actions, supports_scoping, metadata, registered_at, registered_by) VALUES ('roles', 'Role and permission management', NULL, '{read,write,delete}', false, '{}', '2026-07-02 03:56:38.523936+00', 'system') ON CONFLICT DO NOTHING; +INSERT INTO kg_auth.resources (resource_type, description, parent_type, available_actions, supports_scoping, metadata, registered_at, registered_by) VALUES ('resources', 'Resource type registration', NULL, '{read,write,delete}', false, '{}', '2026-07-02 03:56:38.523936+00', 'system') ON CONFLICT DO NOTHING; +INSERT INTO kg_auth.resources (resource_type, description, parent_type, available_actions, supports_scoping, metadata, registered_at, registered_by) VALUES ('workers', 'Worker lane management and job control (ADR-100)', NULL, '{view,manage}', false, '{}', '2026-07-02 03:56:38.523936+00', 'system') ON CONFLICT DO NOTHING; +INSERT INTO kg_auth.resources (resource_type, description, parent_type, available_actions, supports_scoping, metadata, registered_at, registered_by) VALUES ('api_keys', 'API key management for AI providers', NULL, '{read,write,delete}', false, '{}', '2026-07-02 03:56:42.846364+00', 'system') ON CONFLICT DO NOTHING; +INSERT INTO kg_auth.resources (resource_type, description, parent_type, available_actions, supports_scoping, metadata, registered_at, registered_by) VALUES ('embedding_config', 'Embedding model configuration and operations', NULL, '{read,create,delete,activate,reload,regenerate}', false, '{}', '2026-07-02 03:56:42.846364+00', 'system') ON CONFLICT DO NOTHING; +INSERT INTO kg_auth.resources (resource_type, description, parent_type, available_actions, supports_scoping, metadata, registered_at, registered_by) VALUES ('extraction_config', 'AI extraction provider configuration', NULL, '{read,write}', false, '{}', '2026-07-02 03:56:42.846364+00', 'system') ON CONFLICT DO NOTHING; +INSERT INTO kg_auth.resources (resource_type, description, parent_type, available_actions, supports_scoping, metadata, registered_at, registered_by) VALUES ('backups', 'System backup and restore operations', NULL, '{read,create,restore}', false, '{}', '2026-07-02 03:56:42.846364+00', 'system') ON CONFLICT DO NOTHING; +INSERT INTO kg_auth.resources (resource_type, description, parent_type, available_actions, supports_scoping, metadata, registered_at, registered_by) VALUES ('admin', 'Admin dashboard and status', NULL, '{status}', false, '{}', '2026-07-02 03:56:42.846364+00', 'system') ON CONFLICT DO NOTHING; +INSERT INTO kg_auth.resources (resource_type, description, parent_type, available_actions, supports_scoping, metadata, registered_at, registered_by) VALUES ('graph', 'Knowledge graph queries', NULL, '{read,execute}', false, '{}', '2026-07-02 03:56:42.846364+00', 'system') ON CONFLICT DO NOTHING; +INSERT INTO kg_auth.resources (resource_type, description, parent_type, available_actions, supports_scoping, metadata, registered_at, registered_by) VALUES ('ingest', 'Document ingestion', NULL, '{create}', false, '{}', '2026-07-02 03:56:42.846364+00', 'system') ON CONFLICT DO NOTHING; +INSERT INTO kg_auth.resources (resource_type, description, parent_type, available_actions, supports_scoping, metadata, registered_at, registered_by) VALUES ('vocabulary_config', 'Vocabulary configuration and profiles', NULL, '{read,write,create,delete}', false, '{}', '2026-07-02 03:56:42.846364+00', 'system') ON CONFLICT DO NOTHING; +INSERT INTO kg_auth.resources (resource_type, description, parent_type, available_actions, supports_scoping, metadata, registered_at, registered_by) VALUES ('database', 'Database statistics and queries', NULL, '{read,execute}', false, '{}', '2026-07-02 03:56:42.846364+00', 'system') ON CONFLICT DO NOTHING; +INSERT INTO kg_auth.resources (resource_type, description, parent_type, available_actions, supports_scoping, metadata, registered_at, registered_by) VALUES ('oauth_clients', 'OAuth client management (all clients)', NULL, '{read,write,create,delete}', false, '{}', '2026-07-02 03:56:42.846364+00', 'system') ON CONFLICT DO NOTHING; +INSERT INTO kg_auth.resources (resource_type, description, parent_type, available_actions, supports_scoping, metadata, registered_at, registered_by) VALUES ('rbac', 'RBAC roles, resources, and permissions', NULL, '{read,write,create,delete}', false, '{}', '2026-07-02 03:56:42.846364+00', 'system') ON CONFLICT DO NOTHING; +INSERT INTO kg_auth.resources (resource_type, description, parent_type, available_actions, supports_scoping, metadata, registered_at, registered_by) VALUES ('users', 'User account management', NULL, '{read,write,delete,create}', false, '{}', '2026-07-02 03:56:38.523936+00', 'system') ON CONFLICT DO NOTHING; +INSERT INTO kg_auth.resources (resource_type, description, parent_type, available_actions, supports_scoping, metadata, registered_at, registered_by) VALUES ('storage', 'Object storage diagnostics and inspection (S3-compatible backend)', NULL, '{read}', false, '{}', '2026-07-02 03:56:43.384651+00', 'system') ON CONFLICT DO NOTHING; +INSERT INTO kg_auth.resources (resource_type, description, parent_type, available_actions, supports_scoping, metadata, registered_at, registered_by) VALUES ('ontologies', 'Ontology management including deletion', NULL, '{read,create,delete,write}', false, '{}', '2026-07-02 03:56:42.846364+00', 'system') ON CONFLICT DO NOTHING; +INSERT INTO kg_auth.resources (resource_type, description, parent_type, available_actions, supports_scoping, metadata, registered_at, registered_by) VALUES ('sources', 'Source document retrieval', NULL, '{read,delete}', false, '{}', '2026-07-02 03:56:42.846364+00', 'system') ON CONFLICT DO NOTHING; +INSERT INTO kg_auth.resources (resource_type, description, parent_type, available_actions, supports_scoping, metadata, registered_at, registered_by) VALUES ('catalog', 'Hierarchical browse of ontologies, documents, and concepts (ADR-501)', NULL, '{read}', false, '{}', '2026-07-02 03:56:44.846031+00', 'system') ON CONFLICT DO NOTHING; +INSERT INTO kg_auth.resources (resource_type, description, parent_type, available_actions, supports_scoping, metadata, registered_at, registered_by) VALUES ('search_config', 'Configurable semantic search settings (default similarity threshold, ADR-508)', NULL, '{read,write}', false, '{}', '2026-07-02 03:56:45.154151+00', 'system') ON CONFLICT DO NOTHING; + + +-- +-- Data for Name: role_permissions; Type: TABLE DATA; Schema: kg_auth; Owner: - +-- + +INSERT INTO kg_auth.role_permissions (id, role_name, resource_type, action, scope_type, scope_id, scope_filter, granted, inherited_from, created_at, created_by) VALUES (1, 'read_only', 'concepts', 'read', 'global', NULL, NULL, true, NULL, '2026-07-02 03:56:38.529444+00', NULL) ON CONFLICT DO NOTHING; +INSERT INTO kg_auth.role_permissions (id, role_name, resource_type, action, scope_type, scope_id, scope_filter, granted, inherited_from, created_at, created_by) VALUES (2, 'read_only', 'vocabulary', 'read', 'global', NULL, NULL, true, NULL, '2026-07-02 03:56:38.529444+00', NULL) ON CONFLICT DO NOTHING; +INSERT INTO kg_auth.role_permissions (id, role_name, resource_type, action, scope_type, scope_id, scope_filter, granted, inherited_from, created_at, created_by) VALUES (3, 'read_only', 'jobs', 'read', 'global', NULL, NULL, true, NULL, '2026-07-02 03:56:38.529444+00', NULL) ON CONFLICT DO NOTHING; +INSERT INTO kg_auth.role_permissions (id, role_name, resource_type, action, scope_type, scope_id, scope_filter, granted, inherited_from, created_at, created_by) VALUES (4, 'contributor', 'concepts', 'read', 'global', NULL, NULL, true, NULL, '2026-07-02 03:56:38.529444+00', NULL) ON CONFLICT DO NOTHING; +INSERT INTO kg_auth.role_permissions (id, role_name, resource_type, action, scope_type, scope_id, scope_filter, granted, inherited_from, created_at, created_by) VALUES (5, 'contributor', 'concepts', 'write', 'global', NULL, NULL, true, NULL, '2026-07-02 03:56:38.529444+00', NULL) ON CONFLICT DO NOTHING; +INSERT INTO kg_auth.role_permissions (id, role_name, resource_type, action, scope_type, scope_id, scope_filter, granted, inherited_from, created_at, created_by) VALUES (6, 'contributor', 'vocabulary', 'read', 'global', NULL, NULL, true, NULL, '2026-07-02 03:56:38.529444+00', NULL) ON CONFLICT DO NOTHING; +INSERT INTO kg_auth.role_permissions (id, role_name, resource_type, action, scope_type, scope_id, scope_filter, granted, inherited_from, created_at, created_by) VALUES (7, 'contributor', 'jobs', 'read', 'global', NULL, NULL, true, NULL, '2026-07-02 03:56:38.529444+00', NULL) ON CONFLICT DO NOTHING; +INSERT INTO kg_auth.role_permissions (id, role_name, resource_type, action, scope_type, scope_id, scope_filter, granted, inherited_from, created_at, created_by) VALUES (8, 'contributor', 'jobs', 'write', 'global', NULL, NULL, true, NULL, '2026-07-02 03:56:38.529444+00', NULL) ON CONFLICT DO NOTHING; +INSERT INTO kg_auth.role_permissions (id, role_name, resource_type, action, scope_type, scope_id, scope_filter, granted, inherited_from, created_at, created_by) VALUES (9, 'curator', 'concepts', 'read', 'global', NULL, NULL, true, NULL, '2026-07-02 03:56:38.529444+00', NULL) ON CONFLICT DO NOTHING; +INSERT INTO kg_auth.role_permissions (id, role_name, resource_type, action, scope_type, scope_id, scope_filter, granted, inherited_from, created_at, created_by) VALUES (10, 'curator', 'concepts', 'write', 'global', NULL, NULL, true, NULL, '2026-07-02 03:56:38.529444+00', NULL) ON CONFLICT DO NOTHING; +INSERT INTO kg_auth.role_permissions (id, role_name, resource_type, action, scope_type, scope_id, scope_filter, granted, inherited_from, created_at, created_by) VALUES (11, 'curator', 'vocabulary', 'read', 'global', NULL, NULL, true, NULL, '2026-07-02 03:56:38.529444+00', NULL) ON CONFLICT DO NOTHING; +INSERT INTO kg_auth.role_permissions (id, role_name, resource_type, action, scope_type, scope_id, scope_filter, granted, inherited_from, created_at, created_by) VALUES (12, 'curator', 'vocabulary', 'write', 'global', NULL, NULL, true, NULL, '2026-07-02 03:56:38.529444+00', NULL) ON CONFLICT DO NOTHING; +INSERT INTO kg_auth.role_permissions (id, role_name, resource_type, action, scope_type, scope_id, scope_filter, granted, inherited_from, created_at, created_by) VALUES (13, 'curator', 'vocabulary', 'approve', 'global', NULL, NULL, true, NULL, '2026-07-02 03:56:38.529444+00', NULL) ON CONFLICT DO NOTHING; +INSERT INTO kg_auth.role_permissions (id, role_name, resource_type, action, scope_type, scope_id, scope_filter, granted, inherited_from, created_at, created_by) VALUES (14, 'curator', 'jobs', 'read', 'global', NULL, NULL, true, NULL, '2026-07-02 03:56:38.529444+00', NULL) ON CONFLICT DO NOTHING; +INSERT INTO kg_auth.role_permissions (id, role_name, resource_type, action, scope_type, scope_id, scope_filter, granted, inherited_from, created_at, created_by) VALUES (15, 'curator', 'jobs', 'approve', 'global', NULL, NULL, true, NULL, '2026-07-02 03:56:38.529444+00', NULL) ON CONFLICT DO NOTHING; +INSERT INTO kg_auth.role_permissions (id, role_name, resource_type, action, scope_type, scope_id, scope_filter, granted, inherited_from, created_at, created_by) VALUES (16, 'curator', 'roles', 'read', 'global', NULL, NULL, true, NULL, '2026-07-02 03:56:38.529444+00', NULL) ON CONFLICT DO NOTHING; +INSERT INTO kg_auth.role_permissions (id, role_name, resource_type, action, scope_type, scope_id, scope_filter, granted, inherited_from, created_at, created_by) VALUES (17, 'curator', 'resources', 'read', 'global', NULL, NULL, true, NULL, '2026-07-02 03:56:38.529444+00', NULL) ON CONFLICT DO NOTHING; +INSERT INTO kg_auth.role_permissions (id, role_name, resource_type, action, scope_type, scope_id, scope_filter, granted, inherited_from, created_at, created_by) VALUES (18, 'admin', 'concepts', 'read', 'global', NULL, NULL, true, NULL, '2026-07-02 03:56:38.529444+00', NULL) ON CONFLICT DO NOTHING; +INSERT INTO kg_auth.role_permissions (id, role_name, resource_type, action, scope_type, scope_id, scope_filter, granted, inherited_from, created_at, created_by) VALUES (19, 'admin', 'concepts', 'write', 'global', NULL, NULL, true, NULL, '2026-07-02 03:56:38.529444+00', NULL) ON CONFLICT DO NOTHING; +INSERT INTO kg_auth.role_permissions (id, role_name, resource_type, action, scope_type, scope_id, scope_filter, granted, inherited_from, created_at, created_by) VALUES (20, 'admin', 'concepts', 'delete', 'global', NULL, NULL, true, NULL, '2026-07-02 03:56:38.529444+00', NULL) ON CONFLICT DO NOTHING; +INSERT INTO kg_auth.role_permissions (id, role_name, resource_type, action, scope_type, scope_id, scope_filter, granted, inherited_from, created_at, created_by) VALUES (21, 'admin', 'vocabulary', 'read', 'global', NULL, NULL, true, NULL, '2026-07-02 03:56:38.529444+00', NULL) ON CONFLICT DO NOTHING; +INSERT INTO kg_auth.role_permissions (id, role_name, resource_type, action, scope_type, scope_id, scope_filter, granted, inherited_from, created_at, created_by) VALUES (22, 'admin', 'vocabulary', 'write', 'global', NULL, NULL, true, NULL, '2026-07-02 03:56:38.529444+00', NULL) ON CONFLICT DO NOTHING; +INSERT INTO kg_auth.role_permissions (id, role_name, resource_type, action, scope_type, scope_id, scope_filter, granted, inherited_from, created_at, created_by) VALUES (23, 'admin', 'vocabulary', 'approve', 'global', NULL, NULL, true, NULL, '2026-07-02 03:56:38.529444+00', NULL) ON CONFLICT DO NOTHING; +INSERT INTO kg_auth.role_permissions (id, role_name, resource_type, action, scope_type, scope_id, scope_filter, granted, inherited_from, created_at, created_by) VALUES (24, 'admin', 'vocabulary', 'delete', 'global', NULL, NULL, true, NULL, '2026-07-02 03:56:38.529444+00', NULL) ON CONFLICT DO NOTHING; +INSERT INTO kg_auth.role_permissions (id, role_name, resource_type, action, scope_type, scope_id, scope_filter, granted, inherited_from, created_at, created_by) VALUES (25, 'admin', 'jobs', 'read', 'global', NULL, NULL, true, NULL, '2026-07-02 03:56:38.529444+00', NULL) ON CONFLICT DO NOTHING; +INSERT INTO kg_auth.role_permissions (id, role_name, resource_type, action, scope_type, scope_id, scope_filter, granted, inherited_from, created_at, created_by) VALUES (26, 'admin', 'jobs', 'write', 'global', NULL, NULL, true, NULL, '2026-07-02 03:56:38.529444+00', NULL) ON CONFLICT DO NOTHING; +INSERT INTO kg_auth.role_permissions (id, role_name, resource_type, action, scope_type, scope_id, scope_filter, granted, inherited_from, created_at, created_by) VALUES (27, 'admin', 'jobs', 'approve', 'global', NULL, NULL, true, NULL, '2026-07-02 03:56:38.529444+00', NULL) ON CONFLICT DO NOTHING; +INSERT INTO kg_auth.role_permissions (id, role_name, resource_type, action, scope_type, scope_id, scope_filter, granted, inherited_from, created_at, created_by) VALUES (28, 'admin', 'jobs', 'delete', 'global', NULL, NULL, true, NULL, '2026-07-02 03:56:38.529444+00', NULL) ON CONFLICT DO NOTHING; +INSERT INTO kg_auth.role_permissions (id, role_name, resource_type, action, scope_type, scope_id, scope_filter, granted, inherited_from, created_at, created_by) VALUES (29, 'admin', 'users', 'read', 'global', NULL, NULL, true, NULL, '2026-07-02 03:56:38.529444+00', NULL) ON CONFLICT DO NOTHING; +INSERT INTO kg_auth.role_permissions (id, role_name, resource_type, action, scope_type, scope_id, scope_filter, granted, inherited_from, created_at, created_by) VALUES (30, 'admin', 'users', 'write', 'global', NULL, NULL, true, NULL, '2026-07-02 03:56:38.529444+00', NULL) ON CONFLICT DO NOTHING; +INSERT INTO kg_auth.role_permissions (id, role_name, resource_type, action, scope_type, scope_id, scope_filter, granted, inherited_from, created_at, created_by) VALUES (31, 'admin', 'users', 'delete', 'global', NULL, NULL, true, NULL, '2026-07-02 03:56:38.529444+00', NULL) ON CONFLICT DO NOTHING; +INSERT INTO kg_auth.role_permissions (id, role_name, resource_type, action, scope_type, scope_id, scope_filter, granted, inherited_from, created_at, created_by) VALUES (32, 'admin', 'roles', 'read', 'global', NULL, NULL, true, NULL, '2026-07-02 03:56:38.529444+00', NULL) ON CONFLICT DO NOTHING; +INSERT INTO kg_auth.role_permissions (id, role_name, resource_type, action, scope_type, scope_id, scope_filter, granted, inherited_from, created_at, created_by) VALUES (33, 'admin', 'roles', 'write', 'global', NULL, NULL, true, NULL, '2026-07-02 03:56:38.529444+00', NULL) ON CONFLICT DO NOTHING; +INSERT INTO kg_auth.role_permissions (id, role_name, resource_type, action, scope_type, scope_id, scope_filter, granted, inherited_from, created_at, created_by) VALUES (34, 'admin', 'roles', 'delete', 'global', NULL, NULL, true, NULL, '2026-07-02 03:56:38.529444+00', NULL) ON CONFLICT DO NOTHING; +INSERT INTO kg_auth.role_permissions (id, role_name, resource_type, action, scope_type, scope_id, scope_filter, granted, inherited_from, created_at, created_by) VALUES (35, 'admin', 'resources', 'read', 'global', NULL, NULL, true, NULL, '2026-07-02 03:56:38.529444+00', NULL) ON CONFLICT DO NOTHING; +INSERT INTO kg_auth.role_permissions (id, role_name, resource_type, action, scope_type, scope_id, scope_filter, granted, inherited_from, created_at, created_by) VALUES (36, 'admin', 'resources', 'write', 'global', NULL, NULL, true, NULL, '2026-07-02 03:56:38.529444+00', NULL) ON CONFLICT DO NOTHING; +INSERT INTO kg_auth.role_permissions (id, role_name, resource_type, action, scope_type, scope_id, scope_filter, granted, inherited_from, created_at, created_by) VALUES (37, 'admin', 'resources', 'delete', 'global', NULL, NULL, true, NULL, '2026-07-02 03:56:38.529444+00', NULL) ON CONFLICT DO NOTHING; +INSERT INTO kg_auth.role_permissions (id, role_name, resource_type, action, scope_type, scope_id, scope_filter, granted, inherited_from, created_at, created_by) VALUES (38, 'admin', 'workers', 'view', 'global', NULL, NULL, true, NULL, '2026-07-02 03:56:38.529444+00', NULL) ON CONFLICT DO NOTHING; +INSERT INTO kg_auth.role_permissions (id, role_name, resource_type, action, scope_type, scope_id, scope_filter, granted, inherited_from, created_at, created_by) VALUES (39, 'admin', 'workers', 'manage', 'global', NULL, NULL, true, NULL, '2026-07-02 03:56:38.529444+00', NULL) ON CONFLICT DO NOTHING; +INSERT INTO kg_auth.role_permissions (id, role_name, resource_type, action, scope_type, scope_id, scope_filter, granted, inherited_from, created_at, created_by) VALUES (40, 'contributor', 'graph', 'read', 'global', NULL, NULL, true, NULL, '2026-07-02 03:56:42.846364+00', NULL) ON CONFLICT DO NOTHING; +INSERT INTO kg_auth.role_permissions (id, role_name, resource_type, action, scope_type, scope_id, scope_filter, granted, inherited_from, created_at, created_by) VALUES (41, 'contributor', 'ingest', 'create', 'global', NULL, NULL, true, NULL, '2026-07-02 03:56:42.846364+00', NULL) ON CONFLICT DO NOTHING; +INSERT INTO kg_auth.role_permissions (id, role_name, resource_type, action, scope_type, scope_id, scope_filter, granted, inherited_from, created_at, created_by) VALUES (42, 'contributor', 'sources', 'read', 'global', NULL, NULL, true, NULL, '2026-07-02 03:56:42.846364+00', NULL) ON CONFLICT DO NOTHING; +INSERT INTO kg_auth.role_permissions (id, role_name, resource_type, action, scope_type, scope_id, scope_filter, granted, inherited_from, created_at, created_by) VALUES (44, 'contributor', 'ontologies', 'read', 'global', NULL, NULL, true, NULL, '2026-07-02 03:56:42.846364+00', NULL) ON CONFLICT DO NOTHING; +INSERT INTO kg_auth.role_permissions (id, role_name, resource_type, action, scope_type, scope_id, scope_filter, granted, inherited_from, created_at, created_by) VALUES (46, 'curator', 'ontologies', 'create', 'global', NULL, NULL, true, NULL, '2026-07-02 03:56:42.846364+00', NULL) ON CONFLICT DO NOTHING; +INSERT INTO kg_auth.role_permissions (id, role_name, resource_type, action, scope_type, scope_id, scope_filter, granted, inherited_from, created_at, created_by) VALUES (47, 'admin', 'api_keys', 'read', 'global', NULL, NULL, true, NULL, '2026-07-02 03:56:42.846364+00', NULL) ON CONFLICT DO NOTHING; +INSERT INTO kg_auth.role_permissions (id, role_name, resource_type, action, scope_type, scope_id, scope_filter, granted, inherited_from, created_at, created_by) VALUES (48, 'admin', 'embedding_config', 'read', 'global', NULL, NULL, true, NULL, '2026-07-02 03:56:42.846364+00', NULL) ON CONFLICT DO NOTHING; +INSERT INTO kg_auth.role_permissions (id, role_name, resource_type, action, scope_type, scope_id, scope_filter, granted, inherited_from, created_at, created_by) VALUES (49, 'admin', 'extraction_config', 'read', 'global', NULL, NULL, true, NULL, '2026-07-02 03:56:42.846364+00', NULL) ON CONFLICT DO NOTHING; +INSERT INTO kg_auth.role_permissions (id, role_name, resource_type, action, scope_type, scope_id, scope_filter, granted, inherited_from, created_at, created_by) VALUES (50, 'admin', 'backups', 'read', 'global', NULL, NULL, true, NULL, '2026-07-02 03:56:42.846364+00', NULL) ON CONFLICT DO NOTHING; +INSERT INTO kg_auth.role_permissions (id, role_name, resource_type, action, scope_type, scope_id, scope_filter, granted, inherited_from, created_at, created_by) VALUES (51, 'admin', 'admin', 'status', 'global', NULL, NULL, true, NULL, '2026-07-02 03:56:42.846364+00', NULL) ON CONFLICT DO NOTHING; +INSERT INTO kg_auth.role_permissions (id, role_name, resource_type, action, scope_type, scope_id, scope_filter, granted, inherited_from, created_at, created_by) VALUES (52, 'admin', 'oauth_clients', 'read', 'global', NULL, NULL, true, NULL, '2026-07-02 03:56:42.846364+00', NULL) ON CONFLICT DO NOTHING; +INSERT INTO kg_auth.role_permissions (id, role_name, resource_type, action, scope_type, scope_id, scope_filter, granted, inherited_from, created_at, created_by) VALUES (53, 'admin', 'oauth_clients', 'create', 'global', NULL, NULL, true, NULL, '2026-07-02 03:56:42.846364+00', NULL) ON CONFLICT DO NOTHING; +INSERT INTO kg_auth.role_permissions (id, role_name, resource_type, action, scope_type, scope_id, scope_filter, granted, inherited_from, created_at, created_by) VALUES (54, 'admin', 'oauth_clients', 'delete', 'global', NULL, NULL, true, NULL, '2026-07-02 03:56:42.846364+00', NULL) ON CONFLICT DO NOTHING; +INSERT INTO kg_auth.role_permissions (id, role_name, resource_type, action, scope_type, scope_id, scope_filter, granted, inherited_from, created_at, created_by) VALUES (58, 'admin', 'rbac', 'read', 'global', NULL, NULL, true, NULL, '2026-07-02 03:56:42.846364+00', NULL) ON CONFLICT DO NOTHING; +INSERT INTO kg_auth.role_permissions (id, role_name, resource_type, action, scope_type, scope_id, scope_filter, granted, inherited_from, created_at, created_by) VALUES (59, 'admin', 'vocabulary_config', 'read', 'global', NULL, NULL, true, NULL, '2026-07-02 03:56:42.846364+00', NULL) ON CONFLICT DO NOTHING; +INSERT INTO kg_auth.role_permissions (id, role_name, resource_type, action, scope_type, scope_id, scope_filter, granted, inherited_from, created_at, created_by) VALUES (60, 'admin', 'database', 'read', 'global', NULL, NULL, true, NULL, '2026-07-02 03:56:42.846364+00', NULL) ON CONFLICT DO NOTHING; +INSERT INTO kg_auth.role_permissions (id, role_name, resource_type, action, scope_type, scope_id, scope_filter, granted, inherited_from, created_at, created_by) VALUES (61, 'admin', 'ontologies', 'delete', 'global', NULL, NULL, true, NULL, '2026-07-02 03:56:42.846364+00', NULL) ON CONFLICT DO NOTHING; +INSERT INTO kg_auth.role_permissions (id, role_name, resource_type, action, scope_type, scope_id, scope_filter, granted, inherited_from, created_at, created_by) VALUES (62, 'platform_admin', 'api_keys', 'read', 'global', NULL, NULL, true, NULL, '2026-07-02 03:56:42.846364+00', NULL) ON CONFLICT DO NOTHING; +INSERT INTO kg_auth.role_permissions (id, role_name, resource_type, action, scope_type, scope_id, scope_filter, granted, inherited_from, created_at, created_by) VALUES (63, 'platform_admin', 'api_keys', 'write', 'global', NULL, NULL, true, NULL, '2026-07-02 03:56:42.846364+00', NULL) ON CONFLICT DO NOTHING; +INSERT INTO kg_auth.role_permissions (id, role_name, resource_type, action, scope_type, scope_id, scope_filter, granted, inherited_from, created_at, created_by) VALUES (64, 'platform_admin', 'api_keys', 'delete', 'global', NULL, NULL, true, NULL, '2026-07-02 03:56:42.846364+00', NULL) ON CONFLICT DO NOTHING; +INSERT INTO kg_auth.role_permissions (id, role_name, resource_type, action, scope_type, scope_id, scope_filter, granted, inherited_from, created_at, created_by) VALUES (65, 'platform_admin', 'embedding_config', 'read', 'global', NULL, NULL, true, NULL, '2026-07-02 03:56:42.846364+00', NULL) ON CONFLICT DO NOTHING; +INSERT INTO kg_auth.role_permissions (id, role_name, resource_type, action, scope_type, scope_id, scope_filter, granted, inherited_from, created_at, created_by) VALUES (66, 'platform_admin', 'embedding_config', 'create', 'global', NULL, NULL, true, NULL, '2026-07-02 03:56:42.846364+00', NULL) ON CONFLICT DO NOTHING; +INSERT INTO kg_auth.role_permissions (id, role_name, resource_type, action, scope_type, scope_id, scope_filter, granted, inherited_from, created_at, created_by) VALUES (67, 'platform_admin', 'embedding_config', 'delete', 'global', NULL, NULL, true, NULL, '2026-07-02 03:56:42.846364+00', NULL) ON CONFLICT DO NOTHING; +INSERT INTO kg_auth.role_permissions (id, role_name, resource_type, action, scope_type, scope_id, scope_filter, granted, inherited_from, created_at, created_by) VALUES (68, 'platform_admin', 'embedding_config', 'activate', 'global', NULL, NULL, true, NULL, '2026-07-02 03:56:42.846364+00', NULL) ON CONFLICT DO NOTHING; +INSERT INTO kg_auth.role_permissions (id, role_name, resource_type, action, scope_type, scope_id, scope_filter, granted, inherited_from, created_at, created_by) VALUES (69, 'platform_admin', 'embedding_config', 'reload', 'global', NULL, NULL, true, NULL, '2026-07-02 03:56:42.846364+00', NULL) ON CONFLICT DO NOTHING; +INSERT INTO kg_auth.role_permissions (id, role_name, resource_type, action, scope_type, scope_id, scope_filter, granted, inherited_from, created_at, created_by) VALUES (70, 'platform_admin', 'embedding_config', 'regenerate', 'global', NULL, NULL, true, NULL, '2026-07-02 03:56:42.846364+00', NULL) ON CONFLICT DO NOTHING; +INSERT INTO kg_auth.role_permissions (id, role_name, resource_type, action, scope_type, scope_id, scope_filter, granted, inherited_from, created_at, created_by) VALUES (71, 'platform_admin', 'extraction_config', 'read', 'global', NULL, NULL, true, NULL, '2026-07-02 03:56:42.846364+00', NULL) ON CONFLICT DO NOTHING; +INSERT INTO kg_auth.role_permissions (id, role_name, resource_type, action, scope_type, scope_id, scope_filter, granted, inherited_from, created_at, created_by) VALUES (72, 'platform_admin', 'extraction_config', 'write', 'global', NULL, NULL, true, NULL, '2026-07-02 03:56:42.846364+00', NULL) ON CONFLICT DO NOTHING; +INSERT INTO kg_auth.role_permissions (id, role_name, resource_type, action, scope_type, scope_id, scope_filter, granted, inherited_from, created_at, created_by) VALUES (73, 'platform_admin', 'oauth_clients', 'read', 'global', NULL, NULL, true, NULL, '2026-07-02 03:56:42.846364+00', NULL) ON CONFLICT DO NOTHING; +INSERT INTO kg_auth.role_permissions (id, role_name, resource_type, action, scope_type, scope_id, scope_filter, granted, inherited_from, created_at, created_by) VALUES (74, 'platform_admin', 'oauth_clients', 'write', 'global', NULL, NULL, true, NULL, '2026-07-02 03:56:42.846364+00', NULL) ON CONFLICT DO NOTHING; +INSERT INTO kg_auth.role_permissions (id, role_name, resource_type, action, scope_type, scope_id, scope_filter, granted, inherited_from, created_at, created_by) VALUES (75, 'platform_admin', 'oauth_clients', 'create', 'global', NULL, NULL, true, NULL, '2026-07-02 03:56:42.846364+00', NULL) ON CONFLICT DO NOTHING; +INSERT INTO kg_auth.role_permissions (id, role_name, resource_type, action, scope_type, scope_id, scope_filter, granted, inherited_from, created_at, created_by) VALUES (76, 'platform_admin', 'oauth_clients', 'delete', 'global', NULL, NULL, true, NULL, '2026-07-02 03:56:42.846364+00', NULL) ON CONFLICT DO NOTHING; +INSERT INTO kg_auth.role_permissions (id, role_name, resource_type, action, scope_type, scope_id, scope_filter, granted, inherited_from, created_at, created_by) VALUES (77, 'platform_admin', 'ontologies', 'delete', 'global', NULL, NULL, true, NULL, '2026-07-02 03:56:42.846364+00', NULL) ON CONFLICT DO NOTHING; +INSERT INTO kg_auth.role_permissions (id, role_name, resource_type, action, scope_type, scope_id, scope_filter, granted, inherited_from, created_at, created_by) VALUES (78, 'platform_admin', 'backups', 'read', 'global', NULL, NULL, true, NULL, '2026-07-02 03:56:42.846364+00', NULL) ON CONFLICT DO NOTHING; +INSERT INTO kg_auth.role_permissions (id, role_name, resource_type, action, scope_type, scope_id, scope_filter, granted, inherited_from, created_at, created_by) VALUES (79, 'platform_admin', 'backups', 'create', 'global', NULL, NULL, true, NULL, '2026-07-02 03:56:42.846364+00', NULL) ON CONFLICT DO NOTHING; +INSERT INTO kg_auth.role_permissions (id, role_name, resource_type, action, scope_type, scope_id, scope_filter, granted, inherited_from, created_at, created_by) VALUES (80, 'platform_admin', 'backups', 'restore', 'global', NULL, NULL, true, NULL, '2026-07-02 03:56:42.846364+00', NULL) ON CONFLICT DO NOTHING; +INSERT INTO kg_auth.role_permissions (id, role_name, resource_type, action, scope_type, scope_id, scope_filter, granted, inherited_from, created_at, created_by) VALUES (81, 'platform_admin', 'rbac', 'read', 'global', NULL, NULL, true, NULL, '2026-07-02 03:56:42.846364+00', NULL) ON CONFLICT DO NOTHING; +INSERT INTO kg_auth.role_permissions (id, role_name, resource_type, action, scope_type, scope_id, scope_filter, granted, inherited_from, created_at, created_by) VALUES (82, 'platform_admin', 'rbac', 'write', 'global', NULL, NULL, true, NULL, '2026-07-02 03:56:42.846364+00', NULL) ON CONFLICT DO NOTHING; +INSERT INTO kg_auth.role_permissions (id, role_name, resource_type, action, scope_type, scope_id, scope_filter, granted, inherited_from, created_at, created_by) VALUES (83, 'platform_admin', 'rbac', 'create', 'global', NULL, NULL, true, NULL, '2026-07-02 03:56:42.846364+00', NULL) ON CONFLICT DO NOTHING; +INSERT INTO kg_auth.role_permissions (id, role_name, resource_type, action, scope_type, scope_id, scope_filter, granted, inherited_from, created_at, created_by) VALUES (84, 'platform_admin', 'rbac', 'delete', 'global', NULL, NULL, true, NULL, '2026-07-02 03:56:42.846364+00', NULL) ON CONFLICT DO NOTHING; +INSERT INTO kg_auth.role_permissions (id, role_name, resource_type, action, scope_type, scope_id, scope_filter, granted, inherited_from, created_at, created_by) VALUES (85, 'platform_admin', 'vocabulary_config', 'read', 'global', NULL, NULL, true, NULL, '2026-07-02 03:56:42.846364+00', NULL) ON CONFLICT DO NOTHING; +INSERT INTO kg_auth.role_permissions (id, role_name, resource_type, action, scope_type, scope_id, scope_filter, granted, inherited_from, created_at, created_by) VALUES (86, 'platform_admin', 'vocabulary_config', 'write', 'global', NULL, NULL, true, NULL, '2026-07-02 03:56:42.846364+00', NULL) ON CONFLICT DO NOTHING; +INSERT INTO kg_auth.role_permissions (id, role_name, resource_type, action, scope_type, scope_id, scope_filter, granted, inherited_from, created_at, created_by) VALUES (87, 'platform_admin', 'vocabulary_config', 'create', 'global', NULL, NULL, true, NULL, '2026-07-02 03:56:42.846364+00', NULL) ON CONFLICT DO NOTHING; +INSERT INTO kg_auth.role_permissions (id, role_name, resource_type, action, scope_type, scope_id, scope_filter, granted, inherited_from, created_at, created_by) VALUES (88, 'platform_admin', 'vocabulary_config', 'delete', 'global', NULL, NULL, true, NULL, '2026-07-02 03:56:42.846364+00', NULL) ON CONFLICT DO NOTHING; +INSERT INTO kg_auth.role_permissions (id, role_name, resource_type, action, scope_type, scope_id, scope_filter, granted, inherited_from, created_at, created_by) VALUES (89, 'platform_admin', 'database', 'read', 'global', NULL, NULL, true, NULL, '2026-07-02 03:56:42.846364+00', NULL) ON CONFLICT DO NOTHING; +INSERT INTO kg_auth.role_permissions (id, role_name, resource_type, action, scope_type, scope_id, scope_filter, granted, inherited_from, created_at, created_by) VALUES (90, 'platform_admin', 'database', 'execute', 'global', NULL, NULL, true, NULL, '2026-07-02 03:56:42.846364+00', NULL) ON CONFLICT DO NOTHING; +INSERT INTO kg_auth.role_permissions (id, role_name, resource_type, action, scope_type, scope_id, scope_filter, granted, inherited_from, created_at, created_by) VALUES (91, 'platform_admin', 'graph', 'execute', 'global', NULL, NULL, true, NULL, '2026-07-02 03:56:42.846364+00', NULL) ON CONFLICT DO NOTHING; +INSERT INTO kg_auth.role_permissions (id, role_name, resource_type, action, scope_type, scope_id, scope_filter, granted, inherited_from, created_at, created_by) VALUES (92, 'admin', 'users', 'create', 'global', NULL, NULL, true, NULL, '2026-07-02 03:56:42.921756+00', NULL) ON CONFLICT DO NOTHING; +INSERT INTO kg_auth.role_permissions (id, role_name, resource_type, action, scope_type, scope_id, scope_filter, granted, inherited_from, created_at, created_by) VALUES (93, 'admin', 'backups', 'create', 'global', NULL, NULL, true, NULL, '2026-07-02 03:56:43.194121+00', NULL) ON CONFLICT DO NOTHING; +INSERT INTO kg_auth.role_permissions (id, role_name, resource_type, action, scope_type, scope_id, scope_filter, granted, inherited_from, created_at, created_by) VALUES (94, 'admin', 'backups', 'restore', 'global', NULL, NULL, true, NULL, '2026-07-02 03:56:43.194121+00', NULL) ON CONFLICT DO NOTHING; +INSERT INTO kg_auth.role_permissions (id, role_name, resource_type, action, scope_type, scope_id, scope_filter, granted, inherited_from, created_at, created_by) VALUES (95, 'admin', 'graph', 'read', 'global', NULL, NULL, true, NULL, '2026-07-02 03:56:43.308187+00', NULL) ON CONFLICT DO NOTHING; +INSERT INTO kg_auth.role_permissions (id, role_name, resource_type, action, scope_type, scope_id, scope_filter, granted, inherited_from, created_at, created_by) VALUES (96, 'admin', 'graph', 'create', 'global', NULL, NULL, true, NULL, '2026-07-02 03:56:43.308187+00', NULL) ON CONFLICT DO NOTHING; +INSERT INTO kg_auth.role_permissions (id, role_name, resource_type, action, scope_type, scope_id, scope_filter, granted, inherited_from, created_at, created_by) VALUES (97, 'admin', 'graph', 'write', 'global', NULL, NULL, true, NULL, '2026-07-02 03:56:43.308187+00', NULL) ON CONFLICT DO NOTHING; +INSERT INTO kg_auth.role_permissions (id, role_name, resource_type, action, scope_type, scope_id, scope_filter, granted, inherited_from, created_at, created_by) VALUES (98, 'admin', 'graph', 'delete', 'global', NULL, NULL, true, NULL, '2026-07-02 03:56:43.308187+00', NULL) ON CONFLICT DO NOTHING; +INSERT INTO kg_auth.role_permissions (id, role_name, resource_type, action, scope_type, scope_id, scope_filter, granted, inherited_from, created_at, created_by) VALUES (99, 'admin', 'graph', 'import', 'global', NULL, NULL, true, NULL, '2026-07-02 03:56:43.308187+00', NULL) ON CONFLICT DO NOTHING; +INSERT INTO kg_auth.role_permissions (id, role_name, resource_type, action, scope_type, scope_id, scope_filter, granted, inherited_from, created_at, created_by) VALUES (100, 'platform_admin', 'graph', 'read', 'global', NULL, NULL, true, NULL, '2026-07-02 03:56:43.308187+00', NULL) ON CONFLICT DO NOTHING; +INSERT INTO kg_auth.role_permissions (id, role_name, resource_type, action, scope_type, scope_id, scope_filter, granted, inherited_from, created_at, created_by) VALUES (101, 'platform_admin', 'graph', 'create', 'global', NULL, NULL, true, NULL, '2026-07-02 03:56:43.308187+00', NULL) ON CONFLICT DO NOTHING; +INSERT INTO kg_auth.role_permissions (id, role_name, resource_type, action, scope_type, scope_id, scope_filter, granted, inherited_from, created_at, created_by) VALUES (102, 'platform_admin', 'graph', 'write', 'global', NULL, NULL, true, NULL, '2026-07-02 03:56:43.308187+00', NULL) ON CONFLICT DO NOTHING; +INSERT INTO kg_auth.role_permissions (id, role_name, resource_type, action, scope_type, scope_id, scope_filter, granted, inherited_from, created_at, created_by) VALUES (103, 'platform_admin', 'graph', 'delete', 'global', NULL, NULL, true, NULL, '2026-07-02 03:56:43.308187+00', NULL) ON CONFLICT DO NOTHING; +INSERT INTO kg_auth.role_permissions (id, role_name, resource_type, action, scope_type, scope_id, scope_filter, granted, inherited_from, created_at, created_by) VALUES (104, 'platform_admin', 'graph', 'import', 'global', NULL, NULL, true, NULL, '2026-07-02 03:56:43.308187+00', NULL) ON CONFLICT DO NOTHING; +INSERT INTO kg_auth.role_permissions (id, role_name, resource_type, action, scope_type, scope_id, scope_filter, granted, inherited_from, created_at, created_by) VALUES (105, 'contributor', 'graph', 'create', 'global', NULL, NULL, true, NULL, '2026-07-02 03:56:43.308187+00', NULL) ON CONFLICT DO NOTHING; +INSERT INTO kg_auth.role_permissions (id, role_name, resource_type, action, scope_type, scope_id, scope_filter, granted, inherited_from, created_at, created_by) VALUES (106, 'read_only', 'jobs', 'read', 'filter', NULL, '{"owner": "self"}', true, NULL, '2026-07-02 03:56:43.347364+00', NULL) ON CONFLICT DO NOTHING; +INSERT INTO kg_auth.role_permissions (id, role_name, resource_type, action, scope_type, scope_id, scope_filter, granted, inherited_from, created_at, created_by) VALUES (107, 'contributor', 'jobs', 'read', 'filter', NULL, '{"owner": "self"}', true, NULL, '2026-07-02 03:56:43.347364+00', NULL) ON CONFLICT DO NOTHING; +INSERT INTO kg_auth.role_permissions (id, role_name, resource_type, action, scope_type, scope_id, scope_filter, granted, inherited_from, created_at, created_by) VALUES (108, 'contributor', 'jobs', 'cancel', 'filter', NULL, '{"owner": "self"}', true, NULL, '2026-07-02 03:56:43.347364+00', NULL) ON CONFLICT DO NOTHING; +INSERT INTO kg_auth.role_permissions (id, role_name, resource_type, action, scope_type, scope_id, scope_filter, granted, inherited_from, created_at, created_by) VALUES (110, 'curator', 'jobs', 'cancel', 'filter', NULL, '{"owner": "self"}', true, NULL, '2026-07-02 03:56:43.347364+00', NULL) ON CONFLICT DO NOTHING; +INSERT INTO kg_auth.role_permissions (id, role_name, resource_type, action, scope_type, scope_id, scope_filter, granted, inherited_from, created_at, created_by) VALUES (112, 'admin', 'jobs', 'cancel', 'global', NULL, NULL, true, NULL, '2026-07-02 03:56:43.347364+00', NULL) ON CONFLICT DO NOTHING; +INSERT INTO kg_auth.role_permissions (id, role_name, resource_type, action, scope_type, scope_id, scope_filter, granted, inherited_from, created_at, created_by) VALUES (113, 'admin', 'jobs', 'delete', 'filter', NULL, '{"owner": "self"}', true, NULL, '2026-07-02 03:56:43.347364+00', NULL) ON CONFLICT DO NOTHING; +INSERT INTO kg_auth.role_permissions (id, role_name, resource_type, action, scope_type, scope_id, scope_filter, granted, inherited_from, created_at, created_by) VALUES (114, 'platform_admin', 'jobs', 'read', 'global', NULL, NULL, true, NULL, '2026-07-02 03:56:43.347364+00', NULL) ON CONFLICT DO NOTHING; +INSERT INTO kg_auth.role_permissions (id, role_name, resource_type, action, scope_type, scope_id, scope_filter, granted, inherited_from, created_at, created_by) VALUES (115, 'platform_admin', 'jobs', 'cancel', 'global', NULL, NULL, true, NULL, '2026-07-02 03:56:43.347364+00', NULL) ON CONFLICT DO NOTHING; +INSERT INTO kg_auth.role_permissions (id, role_name, resource_type, action, scope_type, scope_id, scope_filter, granted, inherited_from, created_at, created_by) VALUES (116, 'platform_admin', 'jobs', 'delete', 'global', NULL, NULL, true, NULL, '2026-07-02 03:56:43.347364+00', NULL) ON CONFLICT DO NOTHING; +INSERT INTO kg_auth.role_permissions (id, role_name, resource_type, action, scope_type, scope_id, scope_filter, granted, inherited_from, created_at, created_by) VALUES (117, 'platform_admin', 'jobs', 'read', 'filter', NULL, '{"is_system": true}', true, NULL, '2026-07-02 03:56:43.347364+00', NULL) ON CONFLICT DO NOTHING; +INSERT INTO kg_auth.role_permissions (id, role_name, resource_type, action, scope_type, scope_id, scope_filter, granted, inherited_from, created_at, created_by) VALUES (118, 'platform_admin', 'jobs', 'cancel', 'filter', NULL, '{"is_system": true}', true, NULL, '2026-07-02 03:56:43.347364+00', NULL) ON CONFLICT DO NOTHING; +INSERT INTO kg_auth.role_permissions (id, role_name, resource_type, action, scope_type, scope_id, scope_filter, granted, inherited_from, created_at, created_by) VALUES (119, 'platform_admin', 'jobs', 'delete', 'filter', NULL, '{"is_system": true}', true, NULL, '2026-07-02 03:56:43.347364+00', NULL) ON CONFLICT DO NOTHING; +INSERT INTO kg_auth.role_permissions (id, role_name, resource_type, action, scope_type, scope_id, scope_filter, granted, inherited_from, created_at, created_by) VALUES (120, 'admin', 'storage', 'read', 'global', NULL, NULL, true, NULL, '2026-07-02 03:56:43.384651+00', NULL) ON CONFLICT DO NOTHING; +INSERT INTO kg_auth.role_permissions (id, role_name, resource_type, action, scope_type, scope_id, scope_filter, granted, inherited_from, created_at, created_by) VALUES (121, 'platform_admin', 'storage', 'read', 'global', NULL, NULL, true, NULL, '2026-07-02 03:56:43.384651+00', NULL) ON CONFLICT DO NOTHING; +INSERT INTO kg_auth.role_permissions (id, role_name, resource_type, action, scope_type, scope_id, scope_filter, granted, inherited_from, created_at, created_by) VALUES (122, 'curator', 'ontologies', 'write', 'global', NULL, NULL, true, NULL, '2026-07-02 03:56:43.503083+00', NULL) ON CONFLICT DO NOTHING; +INSERT INTO kg_auth.role_permissions (id, role_name, resource_type, action, scope_type, scope_id, scope_filter, granted, inherited_from, created_at, created_by) VALUES (123, 'admin', 'ontologies', 'write', 'global', NULL, NULL, true, NULL, '2026-07-02 03:56:43.503083+00', NULL) ON CONFLICT DO NOTHING; +INSERT INTO kg_auth.role_permissions (id, role_name, resource_type, action, scope_type, scope_id, scope_filter, granted, inherited_from, created_at, created_by) VALUES (124, 'platform_admin', 'ontologies', 'write', 'global', NULL, NULL, true, NULL, '2026-07-02 03:56:43.503083+00', NULL) ON CONFLICT DO NOTHING; +INSERT INTO kg_auth.role_permissions (id, role_name, resource_type, action, scope_type, scope_id, scope_filter, granted, inherited_from, created_at, created_by) VALUES (125, 'admin', 'sources', 'delete', 'global', NULL, NULL, true, NULL, '2026-07-02 03:56:43.903089+00', NULL) ON CONFLICT DO NOTHING; +INSERT INTO kg_auth.role_permissions (id, role_name, resource_type, action, scope_type, scope_id, scope_filter, granted, inherited_from, created_at, created_by) VALUES (126, 'platform_admin', 'sources', 'delete', 'global', NULL, NULL, true, NULL, '2026-07-02 03:56:43.903089+00', NULL) ON CONFLICT DO NOTHING; +INSERT INTO kg_auth.role_permissions (id, role_name, resource_type, action, scope_type, scope_id, scope_filter, granted, inherited_from, created_at, created_by) VALUES (127, 'platform_admin', 'workers', 'view', 'global', NULL, NULL, true, NULL, '2026-07-02 03:56:44.064012+00', NULL) ON CONFLICT DO NOTHING; +INSERT INTO kg_auth.role_permissions (id, role_name, resource_type, action, scope_type, scope_id, scope_filter, granted, inherited_from, created_at, created_by) VALUES (128, 'platform_admin', 'workers', 'manage', 'global', NULL, NULL, true, NULL, '2026-07-02 03:56:44.064012+00', NULL) ON CONFLICT DO NOTHING; +INSERT INTO kg_auth.role_permissions (id, role_name, resource_type, action, scope_type, scope_id, scope_filter, granted, inherited_from, created_at, created_by) VALUES (129, 'admin', 'oauth_clients', 'write', 'global', NULL, NULL, true, NULL, '2026-07-02 03:56:44.80819+00', NULL) ON CONFLICT DO NOTHING; +INSERT INTO kg_auth.role_permissions (id, role_name, resource_type, action, scope_type, scope_id, scope_filter, granted, inherited_from, created_at, created_by) VALUES (130, 'read_only', 'catalog', 'read', 'global', NULL, NULL, true, NULL, '2026-07-02 03:56:44.846031+00', NULL) ON CONFLICT DO NOTHING; +INSERT INTO kg_auth.role_permissions (id, role_name, resource_type, action, scope_type, scope_id, scope_filter, granted, inherited_from, created_at, created_by) VALUES (131, 'contributor', 'catalog', 'read', 'global', NULL, NULL, true, NULL, '2026-07-02 03:56:44.846031+00', NULL) ON CONFLICT DO NOTHING; +INSERT INTO kg_auth.role_permissions (id, role_name, resource_type, action, scope_type, scope_id, scope_filter, granted, inherited_from, created_at, created_by) VALUES (132, 'curator', 'catalog', 'read', 'global', NULL, NULL, true, NULL, '2026-07-02 03:56:44.846031+00', NULL) ON CONFLICT DO NOTHING; +INSERT INTO kg_auth.role_permissions (id, role_name, resource_type, action, scope_type, scope_id, scope_filter, granted, inherited_from, created_at, created_by) VALUES (133, 'admin', 'catalog', 'read', 'global', NULL, NULL, true, NULL, '2026-07-02 03:56:44.846031+00', NULL) ON CONFLICT DO NOTHING; +INSERT INTO kg_auth.role_permissions (id, role_name, resource_type, action, scope_type, scope_id, scope_filter, granted, inherited_from, created_at, created_by) VALUES (134, 'platform_admin', 'catalog', 'read', 'global', NULL, NULL, true, NULL, '2026-07-02 03:56:44.846031+00', NULL) ON CONFLICT DO NOTHING; +INSERT INTO kg_auth.role_permissions (id, role_name, resource_type, action, scope_type, scope_id, scope_filter, granted, inherited_from, created_at, created_by) VALUES (135, 'admin', 'search_config', 'read', 'global', NULL, NULL, true, NULL, '2026-07-02 03:56:45.154151+00', NULL) ON CONFLICT DO NOTHING; +INSERT INTO kg_auth.role_permissions (id, role_name, resource_type, action, scope_type, scope_id, scope_filter, granted, inherited_from, created_at, created_by) VALUES (136, 'admin', 'search_config', 'write', 'global', NULL, NULL, true, NULL, '2026-07-02 03:56:45.154151+00', NULL) ON CONFLICT DO NOTHING; +INSERT INTO kg_auth.role_permissions (id, role_name, resource_type, action, scope_type, scope_id, scope_filter, granted, inherited_from, created_at, created_by) VALUES (137, 'platform_admin', 'search_config', 'read', 'global', NULL, NULL, true, NULL, '2026-07-02 03:56:45.154151+00', NULL) ON CONFLICT DO NOTHING; +INSERT INTO kg_auth.role_permissions (id, role_name, resource_type, action, scope_type, scope_id, scope_filter, granted, inherited_from, created_at, created_by) VALUES (138, 'platform_admin', 'search_config', 'write', 'global', NULL, NULL, true, NULL, '2026-07-02 03:56:45.154151+00', NULL) ON CONFLICT DO NOTHING; + + +-- +-- Data for Name: user_groups; Type: TABLE DATA; Schema: kg_auth; Owner: - +-- + +INSERT INTO kg_auth.user_groups (user_id, group_id, added_at, added_by) VALUES (1000, 2, '2026-07-02 03:56:43.073979+00', 1) ON CONFLICT DO NOTHING; + + +-- +-- Data for Name: user_roles; Type: TABLE DATA; Schema: kg_auth; Owner: - +-- + + + +-- +-- Data for Name: api_metrics; Type: TABLE DATA; Schema: kg_logs; Owner: - +-- + + + +-- +-- Data for Name: audit_trail; Type: TABLE DATA; Schema: kg_logs; Owner: - +-- + + + +-- +-- Data for Name: health_checks; Type: TABLE DATA; Schema: kg_logs; Owner: - +-- + + + +-- +-- Data for Name: job_events; Type: TABLE DATA; Schema: kg_logs; Owner: - +-- + + + +-- +-- Data for Name: graph_metrics; Type: TABLE DATA; Schema: public; Owner: - +-- + +INSERT INTO public.graph_metrics (metric_name, counter, last_measured_counter, last_measured_at, updated_at, notes) VALUES ('vocabulary_change_counter', 0, 0, NULL, '2026-07-02 03:56:42.696448', 'Increments on any vocabulary type create/delete/consolidate') ON CONFLICT DO NOTHING; +INSERT INTO public.graph_metrics (metric_name, counter, last_measured_counter, last_measured_at, updated_at, notes) VALUES ('vocabulary_creation_counter', 0, 0, NULL, '2026-07-02 03:56:42.696448', 'New vocabulary types created') ON CONFLICT DO NOTHING; +INSERT INTO public.graph_metrics (metric_name, counter, last_measured_counter, last_measured_at, updated_at, notes) VALUES ('vocabulary_deletion_counter', 0, 0, NULL, '2026-07-02 03:56:42.696448', 'Vocabulary types deleted') ON CONFLICT DO NOTHING; +INSERT INTO public.graph_metrics (metric_name, counter, last_measured_counter, last_measured_at, updated_at, notes) VALUES ('vocabulary_consolidation_counter', 0, 0, NULL, '2026-07-02 03:56:42.696448', 'Vocabulary synonym merges/consolidations') ON CONFLICT DO NOTHING; +INSERT INTO public.graph_metrics (metric_name, counter, last_measured_counter, last_measured_at, updated_at, notes) VALUES ('concept_creation_counter', 0, 0, NULL, '2026-07-02 03:56:42.696448', 'New concepts added to graph') ON CONFLICT DO NOTHING; +INSERT INTO public.graph_metrics (metric_name, counter, last_measured_counter, last_measured_at, updated_at, notes) VALUES ('concept_deletion_counter', 0, 0, NULL, '2026-07-02 03:56:42.696448', 'Concepts removed from graph') ON CONFLICT DO NOTHING; +INSERT INTO public.graph_metrics (metric_name, counter, last_measured_counter, last_measured_at, updated_at, notes) VALUES ('relationship_creation_counter', 0, 0, NULL, '2026-07-02 03:56:42.696448', 'New relationships/edges added') ON CONFLICT DO NOTHING; +INSERT INTO public.graph_metrics (metric_name, counter, last_measured_counter, last_measured_at, updated_at, notes) VALUES ('relationship_deletion_counter', 0, 0, NULL, '2026-07-02 03:56:42.696448', 'Relationships/edges removed') ON CONFLICT DO NOTHING; +INSERT INTO public.graph_metrics (metric_name, counter, last_measured_counter, last_measured_at, updated_at, notes) VALUES ('document_ingestion_counter', 0, 0, NULL, '2026-07-02 03:56:42.696448', 'Documents successfully ingested') ON CONFLICT DO NOTHING; +INSERT INTO public.graph_metrics (metric_name, counter, last_measured_counter, last_measured_at, updated_at, notes) VALUES ('chunk_processing_counter', 0, 0, NULL, '2026-07-02 03:56:42.696448', 'Document chunks processed') ON CONFLICT DO NOTHING; +INSERT INTO public.graph_metrics (metric_name, counter, last_measured_counter, last_measured_at, updated_at, notes) VALUES ('extraction_job_counter', 0, 0, NULL, '2026-07-02 03:56:42.696448', 'Extraction jobs completed') ON CONFLICT DO NOTHING; +INSERT INTO public.graph_metrics (metric_name, counter, last_measured_counter, last_measured_at, updated_at, notes) VALUES ('grounding_calculation_counter', 0, 0, NULL, '2026-07-02 03:56:42.696448', 'Concepts with grounding calculated/updated') ON CONFLICT DO NOTHING; +INSERT INTO public.graph_metrics (metric_name, counter, last_measured_counter, last_measured_at, updated_at, notes) VALUES ('epistemic_measurement_counter', 0, 0, NULL, '2026-07-02 03:56:42.696448', 'Epistemic status measurements completed') ON CONFLICT DO NOTHING; +INSERT INTO public.graph_metrics (metric_name, counter, last_measured_counter, last_measured_at, updated_at, notes) VALUES ('concept_count', 0, 0, NULL, '2026-07-02 03:56:42.696448', 'Current total concept count') ON CONFLICT DO NOTHING; +INSERT INTO public.graph_metrics (metric_name, counter, last_measured_counter, last_measured_at, updated_at, notes) VALUES ('vocabulary_type_count', 0, 0, NULL, '2026-07-02 03:56:42.696448', 'Current total vocabulary type count') ON CONFLICT DO NOTHING; +INSERT INTO public.graph_metrics (metric_name, counter, last_measured_counter, last_measured_at, updated_at, notes) VALUES ('total_edges', 0, 0, NULL, '2026-07-02 03:56:42.696448', 'Current total relationship count') ON CONFLICT DO NOTHING; +INSERT INTO public.graph_metrics (metric_name, counter, last_measured_counter, last_measured_at, updated_at, notes) VALUES ('source_count', 0, 0, NULL, '2026-07-02 03:56:42.696448', 'Current total source document count') ON CONFLICT DO NOTHING; +INSERT INTO public.graph_metrics (metric_name, counter, last_measured_counter, last_measured_at, updated_at, notes) VALUES ('instance_count', 0, 0, NULL, '2026-07-02 03:56:42.696448', 'Current total evidence instance count') ON CONFLICT DO NOTHING; +INSERT INTO public.graph_metrics (metric_name, counter, last_measured_counter, last_measured_at, updated_at, notes) VALUES ('graph_change_counter', 0, 0, NULL, '2026-07-02 03:56:43.036511', 'Composite counter for cache invalidation - sum of all graph objects (ADR-079)') ON CONFLICT DO NOTHING; +INSERT INTO public.graph_metrics (metric_name, counter, last_measured_counter, last_measured_at, updated_at, notes) VALUES ('ontology_creation_counter', 0, 0, NULL, '2026-07-02 03:56:43.461138', 'Ontologies created (promoted or explicit)') ON CONFLICT DO NOTHING; +INSERT INTO public.graph_metrics (metric_name, counter, last_measured_counter, last_measured_at, updated_at, notes) VALUES ('ontology_deletion_counter', 0, 0, NULL, '2026-07-02 03:56:43.461138', 'Ontologies deleted or demoted') ON CONFLICT DO NOTHING; +INSERT INTO public.graph_metrics (metric_name, counter, last_measured_counter, last_measured_at, updated_at, notes) VALUES ('ontology_count', 0, 0, NULL, '2026-07-02 03:56:43.464288', 'Current total ontology node count') ON CONFLICT DO NOTHING; +INSERT INTO public.graph_metrics (metric_name, counter, last_measured_counter, last_measured_at, updated_at, notes) VALUES ('last_annealing_epoch', 0, 0, NULL, '2026-07-02 03:56:43.59544', 'Epoch when ontology annealing cycle last executed') ON CONFLICT DO NOTHING; +INSERT INTO public.graph_metrics (metric_name, counter, last_measured_counter, last_measured_at, updated_at, notes) VALUES ('ontology_edge_refresh_counter', 0, 0, NULL, '2026-07-02 03:56:43.640135', 'Number of ontology edge refresh cycles completed') ON CONFLICT DO NOTHING; +INSERT INTO public.graph_metrics (metric_name, counter, last_measured_counter, last_measured_at, updated_at, notes) VALUES ('vocabulary_embedding_generation_counter', 0, 0, NULL, '2026-07-02 03:56:44.697023', 'Increments each time a vocabulary type embedding is generated or regenerated. The polarity-axis cache (GroundingMixin) keys against this counter — bumping it invalidates the cache without requiring a process restart. Distinct from vocabulary_change_counter, which tracks row membership rather than embedding contents.') ON CONFLICT DO NOTHING; + + +-- +-- Name: ai_extraction_config_id_seq; Type: SEQUENCE SET; Schema: kg_api; Owner: - +-- + +SELECT pg_catalog.setval('kg_api.ai_extraction_config_id_seq', 1, true); + + +-- +-- Name: ai_vision_config_id_seq; Type: SEQUENCE SET; Schema: kg_api; Owner: - +-- + +SELECT pg_catalog.setval('kg_api.ai_vision_config_id_seq', 1, false); + + +-- +-- Name: annealing_pressure_history_id_seq; Type: SEQUENCE SET; Schema: kg_api; Owner: - +-- + +SELECT pg_catalog.setval('kg_api.annealing_pressure_history_id_seq', 2, true); + + +-- +-- Name: annealing_proposals_id_seq; Type: SEQUENCE SET; Schema: kg_api; Owner: - +-- + +SELECT pg_catalog.setval('kg_api.annealing_proposals_id_seq', 4, true); + + +-- +-- Name: artifacts_id_seq; Type: SEQUENCE SET; Schema: kg_api; Owner: - +-- + +SELECT pg_catalog.setval('kg_api.artifacts_id_seq', 1, false); + + +-- +-- Name: embedding_config_id_seq; Type: SEQUENCE SET; Schema: kg_api; Owner: - +-- + +SELECT pg_catalog.setval('kg_api.embedding_config_id_seq', 2, true); + + +-- +-- Name: embedding_profile_id_seq; Type: SEQUENCE SET; Schema: kg_api; Owner: - +-- + +SELECT pg_catalog.setval('kg_api.embedding_profile_id_seq', 2, true); + + +-- +-- Name: graph_epochs_event_id_seq; Type: SEQUENCE SET; Schema: kg_api; Owner: - +-- + +SELECT pg_catalog.setval('kg_api.graph_epochs_event_id_seq', 1, false); + + +-- +-- Name: ontology_versions_version_id_seq; Type: SEQUENCE SET; Schema: kg_api; Owner: - +-- + +SELECT pg_catalog.setval('kg_api.ontology_versions_version_id_seq', 1, true); + + +-- +-- Name: provider_model_catalog_id_seq; Type: SEQUENCE SET; Schema: kg_api; Owner: - +-- + +SELECT pg_catalog.setval('kg_api.provider_model_catalog_id_seq', 27, true); + + +-- +-- Name: pruning_recommendations_id_seq; Type: SEQUENCE SET; Schema: kg_api; Owner: - +-- + +SELECT pg_catalog.setval('kg_api.pruning_recommendations_id_seq', 1, false); + + +-- +-- Name: query_definitions_id_seq; Type: SEQUENCE SET; Schema: kg_api; Owner: - +-- + +SELECT pg_catalog.setval('kg_api.query_definitions_id_seq', 1, false); + + +-- +-- Name: scheduled_jobs_id_seq; Type: SEQUENCE SET; Schema: kg_api; Owner: - +-- + +SELECT pg_catalog.setval('kg_api.scheduled_jobs_id_seq', 7, true); + + +-- +-- Name: skipped_relationships_id_seq; Type: SEQUENCE SET; Schema: kg_api; Owner: - +-- + +SELECT pg_catalog.setval('kg_api.skipped_relationships_id_seq', 1, false); + + +-- +-- Name: source_embeddings_embedding_id_seq; Type: SEQUENCE SET; Schema: kg_api; Owner: - +-- + +SELECT pg_catalog.setval('kg_api.source_embeddings_embedding_id_seq', 1, false); + + +-- +-- Name: vocabulary_audit_id_seq; Type: SEQUENCE SET; Schema: kg_api; Owner: - +-- + +SELECT pg_catalog.setval('kg_api.vocabulary_audit_id_seq', 1, false); + + +-- +-- Name: vocabulary_history_id_seq; Type: SEQUENCE SET; Schema: kg_api; Owner: - +-- + +SELECT pg_catalog.setval('kg_api.vocabulary_history_id_seq', 1, false); + + +-- +-- Name: vocabulary_suggestions_id_seq; Type: SEQUENCE SET; Schema: kg_api; Owner: - +-- + +SELECT pg_catalog.setval('kg_api.vocabulary_suggestions_id_seq', 1, false); + + +-- +-- Name: groups_id_seq; Type: SEQUENCE SET; Schema: kg_auth; Owner: - +-- + +SELECT pg_catalog.setval('kg_auth.groups_id_seq', 1000, true); + + +-- +-- Name: resource_grants_id_seq; Type: SEQUENCE SET; Schema: kg_auth; Owner: - +-- + +SELECT pg_catalog.setval('kg_auth.resource_grants_id_seq', 1, false); + + +-- +-- Name: role_permissions_id_seq; Type: SEQUENCE SET; Schema: kg_auth; Owner: - +-- + +SELECT pg_catalog.setval('kg_auth.role_permissions_id_seq', 138, true); + + +-- +-- Name: user_roles_id_seq; Type: SEQUENCE SET; Schema: kg_auth; Owner: - +-- + +SELECT pg_catalog.setval('kg_auth.user_roles_id_seq', 1, false); + + +-- +-- Name: users_id_seq; Type: SEQUENCE SET; Schema: kg_auth; Owner: - +-- + +SELECT pg_catalog.setval('kg_auth.users_id_seq', 1001, true); + + +-- +-- Name: api_metrics_id_seq; Type: SEQUENCE SET; Schema: kg_logs; Owner: - +-- + +SELECT pg_catalog.setval('kg_logs.api_metrics_id_seq', 1, false); + + +-- +-- Name: audit_trail_id_seq; Type: SEQUENCE SET; Schema: kg_logs; Owner: - +-- + +SELECT pg_catalog.setval('kg_logs.audit_trail_id_seq', 1, false); + + +-- +-- Name: health_checks_id_seq; Type: SEQUENCE SET; Schema: kg_logs; Owner: - +-- + +SELECT pg_catalog.setval('kg_logs.health_checks_id_seq', 1, false); + + +-- +-- Name: job_events_id_seq; Type: SEQUENCE SET; Schema: kg_logs; Owner: - +-- + +SELECT pg_catalog.setval('kg_logs.job_events_id_seq', 1, false); + + +-- +-- PostgreSQL database dump complete +-- + + + +SET session_replication_role = DEFAULT; + +-- ---------------------------------------------------------------------------- +-- 4. Graph seed (carried migrations, idempotent) +-- ---------------------------------------------------------------------------- + +-- ======== carried from migrations/014_vocabulary_as_graph.sql ======== +-- =========================================================================== +-- Migration 014: Vocabulary as Graph Nodes (ADR-048 Phase 3) +-- =========================================================================== +-- Date: 2025-10-27 +-- Related: ADR-048 (Vocabulary Metadata as First-Class Graph), ADR-047 +-- +-- Creates :VocabType and :VocabCategory nodes in Apache AGE graph from +-- existing kg_api.relationship_vocabulary table data. +-- +-- Strategy: Direct cypher queries to create nodes from SQL data +-- =========================================================================== + +-- Set search path +LOAD 'age'; +SET search_path = ag_catalog, kg_api, public; + +-- --------------------------------------------------------------------------- +-- STEP 1: Create VocabCategory Nodes +-- --------------------------------------------------------------------------- + +DO $migration$ +DECLARE + category_rec RECORD; + cat_count INTEGER := 0; +BEGIN + RAISE NOTICE 'Creating VocabCategory nodes...'; + + FOR category_rec IN + SELECT DISTINCT + category, + COUNT(*) as type_count + FROM kg_api.relationship_vocabulary + WHERE is_active = true AND category IS NOT NULL + GROUP BY category + ORDER BY category + LOOP + -- Build and execute cypher query with EXECUTE (using MERGE for idempotency) + EXECUTE format( + 'SELECT * FROM cypher(''knowledge_graph'', $cypher$ MERGE (c:VocabCategory {name: ''%s''}) SET c.type_count = %s $cypher$) as (c agtype)', + replace(category_rec.category, '''', ''''''), + category_rec.type_count + ); + + cat_count := cat_count + 1; + END LOOP; + + RAISE NOTICE 'Created % VocabCategory nodes', cat_count; +END $migration$; + +-- --------------------------------------------------------------------------- +-- STEP 2: Create VocabType Nodes +-- --------------------------------------------------------------------------- + +DO $migration$ +DECLARE + vocab_rec RECORD; + node_count INTEGER := 0; +BEGIN + RAISE NOTICE 'Creating VocabType nodes...'; + + FOR vocab_rec IN + SELECT + relationship_type, + category, + is_active, + is_builtin, + COALESCE(usage_count, 0) as usage_count + FROM kg_api.relationship_vocabulary + WHERE relationship_type IS NOT NULL + ORDER BY relationship_type + LOOP + -- Build and execute cypher query with EXECUTE (using MERGE for idempotency) + EXECUTE format( + 'SELECT * FROM cypher(''knowledge_graph'', $cypher$ MERGE (v:VocabType {name: ''%s''}) SET v.is_active = %L, v.is_builtin = %L, v.usage_count = %s $cypher$) as (v agtype)', + replace(vocab_rec.relationship_type, '''', ''''''), + vocab_rec.is_active, + vocab_rec.is_builtin, + vocab_rec.usage_count + ); + + node_count := node_count + 1; + + -- Show progress every 10 nodes + IF node_count % 10 = 0 THEN + RAISE NOTICE ' Created % VocabType nodes...', node_count; + END IF; + END LOOP; + + RAISE NOTICE 'Created % VocabType nodes total', node_count; +END $migration$; + +-- --------------------------------------------------------------------------- +-- STEP 3: Create IN_CATEGORY Relationships +-- --------------------------------------------------------------------------- + +DO $migration$ +DECLARE + vocab_rec RECORD; + rel_count INTEGER := 0; +BEGIN + RAISE NOTICE 'Creating IN_CATEGORY relationships...'; + + FOR vocab_rec IN + SELECT + relationship_type, + category + FROM kg_api.relationship_vocabulary + WHERE relationship_type IS NOT NULL + AND category IS NOT NULL + ORDER BY relationship_type + LOOP + -- Build and execute cypher query with EXECUTE (using MERGE for idempotency) + EXECUTE format( + 'SELECT * FROM cypher(''knowledge_graph'', $cypher$ MATCH (v:VocabType {name: ''%s''}), (c:VocabCategory {name: ''%s''}) MERGE (v)-[:IN_CATEGORY]->(c) $cypher$) as (result agtype)', + replace(vocab_rec.relationship_type, '''', ''''''), + replace(vocab_rec.category, '''', '''''') + ); + + rel_count := rel_count + 1; + + -- Show progress every 10 relationships + IF rel_count % 10 = 0 THEN + RAISE NOTICE ' Created % IN_CATEGORY relationships...', rel_count; + END IF; + END LOOP; + + RAISE NOTICE 'Created % IN_CATEGORY relationships total', rel_count; +END $migration$; + +-- --------------------------------------------------------------------------- +-- STEP 4: Verification +-- --------------------------------------------------------------------------- + +DO $migration$ +DECLARE + vocab_count INTEGER; + cat_count INTEGER; + rel_count INTEGER; + sql_vocab_count INTEGER; +BEGIN + RAISE NOTICE ''; + RAISE NOTICE 'Verifying vocabulary graph...'; + RAISE NOTICE '====================================='; + + -- Count VocabType nodes in graph + EXECUTE 'SELECT total::int FROM cypher(''knowledge_graph'', $cypher$ MATCH (v:VocabType) RETURN count(v) as total $cypher$) as (total agtype)' + INTO vocab_count; + + -- Count vocabulary types in SQL + SELECT COUNT(*) INTO sql_vocab_count + FROM kg_api.relationship_vocabulary; + + RAISE NOTICE ' VocabType nodes in graph: %', vocab_count; + RAISE NOTICE ' Vocabulary types in SQL: %', sql_vocab_count; + + -- Count VocabCategory nodes + EXECUTE 'SELECT total::int FROM cypher(''knowledge_graph'', $cypher$ MATCH (c:VocabCategory) RETURN count(c) as total $cypher$) as (total agtype)' + INTO cat_count; + RAISE NOTICE ' VocabCategory nodes: %', cat_count; + + -- Count relationships + EXECUTE 'SELECT total::int FROM cypher(''knowledge_graph'', $cypher$ MATCH ()-[r:IN_CATEGORY]->() RETURN count(r) as total $cypher$) as (total agtype)' + INTO rel_count; + RAISE NOTICE ' IN_CATEGORY relationships: %', rel_count; + + RAISE NOTICE '====================================='; + RAISE NOTICE 'Vocabulary graph sync complete!'; + RAISE NOTICE ''; +END $migration$; + +-- --------------------------------------------------------------------------- +-- Migration Tracking +-- --------------------------------------------------------------------------- + +INSERT INTO public.schema_migrations (version, name) +VALUES (14, 'vocabulary_as_graph') +ON CONFLICT (version) DO NOTHING; + +-- =========================================================================== +-- End of Migration 014 +-- =========================================================================== + +-- ======== carried from migrations/044_ontology_graph_nodes.sql ======== +-- =========================================================================== +-- Migration 044: Ontology Graph Nodes (ADR-200 Phase 1) +-- =========================================================================== +-- Date: 2026-01-29 +-- Related: ADR-200 (Annealing Ontologies) +-- +-- Promotes ontologies from string properties to first-class graph nodes. +-- Creates :Ontology nodes from existing s.document values on Source nodes, +-- and :SCOPED_BY edges linking each Source to its Ontology. +-- +-- Existing s.document strings are preserved as a denormalized cache. +-- :Ontology nodes and :SCOPED_BY edges become the source of truth for +-- new code paths. Existing queries that filter by s.document continue +-- working unchanged. +-- +-- This migration is idempotent — safe to run multiple times. +-- =========================================================================== + +-- Set search path +LOAD 'age'; +SET search_path = ag_catalog, kg_api, public; + +-- --------------------------------------------------------------------------- +-- STEP 1: Create :Ontology nodes from distinct s.document values +-- --------------------------------------------------------------------------- + +DO $migration$ +DECLARE + doc_rec RECORD; + node_count INTEGER := 0; + skip_count INTEGER := 0; + current_epoch BIGINT := 0; + new_id TEXT; + exists_count INTEGER; +BEGIN + RAISE NOTICE 'ADR-200 Phase 1: Creating :Ontology nodes...'; + + -- Get current ingestion epoch for creation_epoch + BEGIN + SELECT counter INTO current_epoch + FROM graph_metrics + WHERE metric_name = 'document_ingestion_counter'; + EXCEPTION WHEN undefined_table THEN + current_epoch := 0; + END; + current_epoch := COALESCE(current_epoch, 0); + + RAISE NOTICE ' Current ingestion epoch: %', current_epoch; + + -- Get distinct ontology names from Source nodes + FOR doc_rec IN + EXECUTE format( + 'SELECT trim(both ''"'' from name::text) as name + FROM cypher(''knowledge_graph'', $cypher$ + MATCH (s:Source) + WHERE s.document IS NOT NULL + RETURN DISTINCT s.document as name + $cypher$) as (name agtype)' + ) + LOOP + -- Check if Ontology node already exists (idempotency) + EXECUTE format( + 'SELECT c::int FROM ( + SELECT count(*) as c FROM cypher(''knowledge_graph'', $cypher$ + MATCH (o:Ontology {name: ''%s''}) + RETURN o + $cypher$) as (o agtype) + ) sub', + replace(doc_rec.name, '''', '''''') + ) INTO exists_count; + + IF exists_count = 0 THEN + new_id := 'ont_' || gen_random_uuid()::text; + + EXECUTE format( + 'SELECT * FROM cypher(''knowledge_graph'', $cypher$ + CREATE (o:Ontology { + ontology_id: ''%s'', + name: ''%s'', + lifecycle_state: ''active'', + creation_epoch: %s + }) + $cypher$) as (o agtype)', + new_id, + replace(doc_rec.name, '''', ''''''), + current_epoch + ); + + node_count := node_count + 1; + RAISE NOTICE ' Created Ontology: % (id: %)', doc_rec.name, new_id; + ELSE + skip_count := skip_count + 1; + RAISE NOTICE ' Already exists: %', doc_rec.name; + END IF; + END LOOP; + + RAISE NOTICE 'Created % Ontology nodes (% already existed)', node_count, skip_count; +END $migration$; + +-- --------------------------------------------------------------------------- +-- STEP 2: Create :SCOPED_BY edges from Source → Ontology +-- --------------------------------------------------------------------------- + +DO $migration$ +DECLARE + doc_rec RECORD; + linked INTEGER; + total_linked INTEGER := 0; +BEGIN + RAISE NOTICE ''; + RAISE NOTICE 'Creating :SCOPED_BY edges from Source → Ontology...'; + + -- Process one ontology at a time for predictable performance + FOR doc_rec IN + EXECUTE format( + 'SELECT trim(both ''"'' from name::text) as name + FROM cypher(''knowledge_graph'', $cypher$ + MATCH (o:Ontology) + RETURN o.name as name + $cypher$) as (name agtype)' + ) + LOOP + -- Create SCOPED_BY edges for all Source nodes in this ontology + -- MERGE is idempotent — existing edges are not duplicated + EXECUTE format( + 'SELECT count(*)::int FROM cypher(''knowledge_graph'', $cypher$ + MATCH (s:Source {document: ''%s''}) + MATCH (o:Ontology {name: ''%s''}) + MERGE (s)-[:SCOPED_BY]->(o) + RETURN s + $cypher$) as (s agtype)', + replace(doc_rec.name, '''', ''''''), + replace(doc_rec.name, '''', '''''') + ) INTO linked; + + total_linked := total_linked + COALESCE(linked, 0); + RAISE NOTICE ' Linked % sources → Ontology "%"', COALESCE(linked, 0), doc_rec.name; + END LOOP; + + RAISE NOTICE 'Created SCOPED_BY edges for % total sources', total_linked; +END $migration$; + +-- --------------------------------------------------------------------------- +-- STEP 3: Add graph_metrics entry for ontology tracking +-- --------------------------------------------------------------------------- + +INSERT INTO graph_metrics (metric_name, counter, last_measured_counter, notes) +VALUES + ('ontology_count', 0, 0, 'Current total ontology node count'), + ('ontology_creation_counter', 0, 0, 'Ontologies created (promoted or explicit)'), + ('ontology_deletion_counter', 0, 0, 'Ontologies deleted or demoted') +ON CONFLICT (metric_name) DO NOTHING; + +-- --------------------------------------------------------------------------- +-- STEP 4: Verification +-- --------------------------------------------------------------------------- + +DO $migration$ +DECLARE + ontology_count INTEGER; + scoped_count INTEGER; + source_count INTEGER; + unscoped_count INTEGER; +BEGIN + RAISE NOTICE ''; + RAISE NOTICE 'Verifying ontology graph structure...'; + RAISE NOTICE '====================================='; + + -- Count Ontology nodes + EXECUTE 'SELECT count(*)::int FROM cypher(''knowledge_graph'', $cypher$ + MATCH (o:Ontology) RETURN o + $cypher$) as (o agtype)' + INTO ontology_count; + RAISE NOTICE ' Ontology nodes: %', ontology_count; + + -- Count SCOPED_BY edges + EXECUTE 'SELECT count(*)::int FROM cypher(''knowledge_graph'', $cypher$ + MATCH ()-[r:SCOPED_BY]->() RETURN r + $cypher$) as (r agtype)' + INTO scoped_count; + RAISE NOTICE ' SCOPED_BY edges: %', scoped_count; + + -- Count total Source nodes + EXECUTE 'SELECT count(*)::int FROM cypher(''knowledge_graph'', $cypher$ + MATCH (s:Source) RETURN s + $cypher$) as (s agtype)' + INTO source_count; + RAISE NOTICE ' Total Source nodes: %', source_count; + + -- Count Source nodes WITHOUT SCOPED_BY edge (should be 0) + -- NOTE: NOT EXISTS { } subquery syntax may not be supported in all AGE + -- versions. Wrap in its own handler so a Cypher parse error here doesn't + -- roll back the verification block — the migration data is already committed. + BEGIN + EXECUTE 'SELECT count(*)::int FROM cypher(''knowledge_graph'', $cypher$ + MATCH (s:Source) + WHERE s.document IS NOT NULL AND NOT EXISTS { + MATCH (s)-[:SCOPED_BY]->(:Ontology) + } + RETURN s + $cypher$) as (s agtype)' + INTO unscoped_count; + + IF unscoped_count > 0 THEN + RAISE WARNING ' Unscoped sources: % (sources with document but no SCOPED_BY edge)', unscoped_count; + ELSE + RAISE NOTICE ' Unscoped sources: 0 (all sources linked)'; + END IF; + EXCEPTION WHEN OTHERS THEN + RAISE NOTICE ' Unscoped sources: (skipped — NOT EXISTS subquery not supported by this AGE version)'; + END; + + RAISE NOTICE '====================================='; + + -- Update ontology_count snapshot + UPDATE graph_metrics + SET counter = ontology_count, updated_at = CURRENT_TIMESTAMP + WHERE metric_name = 'ontology_count'; +END $migration$; + +-- --------------------------------------------------------------------------- +-- Migration Tracking +-- --------------------------------------------------------------------------- + +INSERT INTO public.schema_migrations (version, name) +VALUES (44, 'ontology_graph_nodes') +ON CONFLICT (version) DO NOTHING; + +-- =========================================================================== +-- End of Migration 044 +-- =========================================================================== + +-- ======== carried from migrations-warm/058_precreate_graph_labels.sql ======== +-- Migration 058: Pre-create all vertex and edge labels (prevents AGE race condition) +-- +-- Apache AGE auto-creates labels on first CREATE/MERGE, but this is a DDL operation +-- (CREATE TABLE) that is not concurrent-safe. On a fresh install, parallel ingestion +-- threads can race to create the same label, causing: +-- "relation 'Source' already exists" +-- +-- Fix: explicitly create all known vertex and edge labels at schema init time +-- via ag_catalog.create_vlabel()/create_elabel() (AGE's label API — cypher has +-- no CREATE VLABEL statement). Uses DO blocks to check ag_catalog.ag_label +-- before creating, since the functions error on existing labels. +-- +-- Note: Uses EXCEPTION handlers so label creation failures (e.g., AGE not fully +-- initialized on cold start) are logged as warnings rather than aborting the migration. +-- +-- History: the original version of this file EXECUTEd +-- `cypher(..., $$ CREATE VLABEL ... $$)` — the inner $$ terminated the outer +-- DO $$ quoting, so the file never parsed and version 58 was never recorded +-- anywhere. Rewritten 2026-07-01; safe to modify in place because it never +-- successfully applied. + +LOAD 'age'; +SET search_path = ag_catalog, "$user", public; + +-- ============================================================ +-- Vertex labels +-- ============================================================ + +DO $$ +DECLARE + labels text[] := ARRAY['Concept', 'Source', 'Instance', 'Ontology', 'DocumentMeta', 'VocabType', 'VocabCategory']; + lbl text; +BEGIN + FOREACH lbl IN ARRAY labels LOOP + IF NOT EXISTS ( + SELECT 1 FROM ag_catalog.ag_label l + JOIN ag_catalog.ag_graph g ON l.graph = g.graphid + WHERE g.name = 'knowledge_graph' AND l.name = lbl AND l.kind = 'v' + ) THEN + BEGIN + -- create_vlabel takes cstring args; text needs an explicit cast + PERFORM ag_catalog.create_vlabel('knowledge_graph', lbl::cstring); + RAISE NOTICE 'Created vertex label: %', lbl; + EXCEPTION WHEN OTHERS THEN + RAISE NOTICE 'Could not create vertex label % (will be created on first use): %', lbl, SQLERRM; + END; + END IF; + END LOOP; +END $$; + +-- ============================================================ +-- Static edge labels (infrastructure edges) +-- ============================================================ + +DO $$ +DECLARE + labels text[] := ARRAY[ + 'APPEARS', 'EVIDENCED_BY', 'FROM_SOURCE', 'SCOPED_BY', + 'HAS_SOURCE', 'IN_CATEGORY', 'ANCHORED_BY', + 'OVERLAPS', 'SPECIALIZES', 'GENERALIZES' + ]; + lbl text; +BEGIN + FOREACH lbl IN ARRAY labels LOOP + IF NOT EXISTS ( + SELECT 1 FROM ag_catalog.ag_label l + JOIN ag_catalog.ag_graph g ON l.graph = g.graphid + WHERE g.name = 'knowledge_graph' AND l.name = lbl AND l.kind = 'e' + ) THEN + BEGIN + PERFORM ag_catalog.create_elabel('knowledge_graph', lbl::cstring); + RAISE NOTICE 'Created edge label: %', lbl; + EXCEPTION WHEN OTHERS THEN + RAISE NOTICE 'Could not create edge label % (will be created on first use): %', lbl, SQLERRM; + END; + END IF; + END LOOP; +END $$; + +-- ============================================================ +-- Vocabulary edge labels (30 canonical relationship types) +-- These are dynamically created during ingestion, but pre-creating +-- them eliminates the concurrent DDL race on first use. +-- ============================================================ + +DO $$ +DECLARE + labels text[] := ARRAY[ + -- logical_truth + 'IMPLIES', 'CONTRADICTS', 'PRESUPPOSES', 'EQUIVALENT_TO', + -- causal + 'CAUSES', 'ENABLES', 'PREVENTS', 'INFLUENCES', 'RESULTS_FROM', + -- structural + 'PART_OF', 'CONTAINS', 'COMPOSED_OF', 'SUBSET_OF', 'INSTANCE_OF', + -- evidential + 'SUPPORTS', 'REFUTES', 'EXEMPLIFIES', 'MEASURED_BY', + -- similarity + 'SIMILAR_TO', 'ANALOGOUS_TO', 'CONTRASTS_WITH', 'OPPOSITE_OF', + -- temporal + 'PRECEDES', 'CONCURRENT_WITH', 'EVOLVES_INTO', + -- functional + 'USED_FOR', 'REQUIRES', 'PRODUCES', 'REGULATES', + -- meta + 'DEFINED_AS', 'CATEGORIZED_AS' + ]; + lbl text; +BEGIN + FOREACH lbl IN ARRAY labels LOOP + IF NOT EXISTS ( + SELECT 1 FROM ag_catalog.ag_label l + JOIN ag_catalog.ag_graph g ON l.graph = g.graphid + WHERE g.name = 'knowledge_graph' AND l.name = lbl AND l.kind = 'e' + ) THEN + BEGIN + PERFORM ag_catalog.create_elabel('knowledge_graph', lbl::cstring); + RAISE NOTICE 'Created edge label: %', lbl; + EXCEPTION WHEN OTHERS THEN + RAISE NOTICE 'Could not create edge label % (will be created on first use): %', lbl, SQLERRM; + END; + END IF; + END LOOP; +END $$; + +-- Record migration +INSERT INTO public.schema_migrations (version, name) +VALUES (58, 'precreate_graph_labels') +ON CONFLICT (version) DO NOTHING; + +-- ======== carried from migrations/078_seed_primordial_ontology.sql ======== +-- Migration: 078_seed_primordial_ontology +-- Description: Seed the reserved 'primordial' ontology pool on clean install +-- Issue: #505 +-- ADR: ADR-200 (Annealing Ontologies) +-- +-- The 'primordial' pool is the reserved default ontology that unroutable +-- sources fall into. It was only created LAZILY — the first time an annealing +-- cycle ran (proposal_executor._ensure_primordial_pool) — so a fresh instance +-- had no primordial pool until then. Seed it eagerly at install so it always +-- exists. Idempotent: creates the node only if absent. +-- +-- (Restore also ensures primordial via AGEClient.rehydrate_projection_layers, +-- since a clone-style restore can replace this freshly-seeded graph.) + +LOAD 'age'; +SET search_path = ag_catalog, kg_api, public; + +DO $migration$ +DECLARE + exists_count INTEGER; + new_id TEXT; +BEGIN + -- The :Ontology label is precreated by migration 058, so this MATCH is safe + -- on a fresh graph (empty, not missing). + EXECUTE 'SELECT count(*)::int FROM cypher(''knowledge_graph'', $cypher$ + MATCH (o:Ontology {name: ''primordial''}) + RETURN o + $cypher$) as (o agtype)' INTO exists_count; + + IF exists_count = 0 THEN + new_id := 'ont_' || gen_random_uuid()::text; + EXECUTE format( + 'SELECT * FROM cypher(''knowledge_graph'', $cypher$ + CREATE (o:Ontology { + ontology_id: ''%s'', + name: ''primordial'', + description: ''Default pool for unroutable sources'', + lifecycle_state: ''active'', + creation_epoch: 0, + created_by: ''system'' + }) + $cypher$) as (o agtype)', + new_id + ); + RAISE NOTICE 'Seeded primordial ontology (id: %)', new_id; + ELSE + RAISE NOTICE 'primordial ontology already exists, skipping'; + END IF; +END $migration$; + +-- --------------------------------------------------------------------------- +-- Migration Tracking +-- --------------------------------------------------------------------------- INSERT INTO public.schema_migrations (version, name) -VALUES (1, 'baseline') +VALUES (78, 'seed_primordial_ontology') ON CONFLICT (version) DO NOTHING; + +-- ---------------------------------------------------------------------------- +-- 5. Migration records (consolidated versions) +-- ---------------------------------------------------------------------------- +INSERT INTO public.schema_migrations (version, name) VALUES (1, 'baseline') ON CONFLICT (version) DO NOTHING; +INSERT INTO public.schema_migrations (version, name) VALUES (3, 'add_embedding_config') ON CONFLICT (version) DO NOTHING; +INSERT INTO public.schema_migrations (version, name) VALUES (4, 'add_ai_extraction_config') ON CONFLICT (version) DO NOTHING; +INSERT INTO public.schema_migrations (version, name) VALUES (5, 'add_api_key_validation') ON CONFLICT (version) DO NOTHING; +INSERT INTO public.schema_migrations (version, name) VALUES (6, 'add_embedding_config_protection') ON CONFLICT (version) DO NOTHING; +INSERT INTO public.schema_migrations (version, name) VALUES (7, 'add_local_extraction_providers') ON CONFLICT (version) DO NOTHING; +INSERT INTO public.schema_migrations (version, name) VALUES (8, 'add_nomic_embedding_preset') ON CONFLICT (version) DO NOTHING; +INSERT INTO public.schema_migrations (version, name) VALUES (9, 'add_thinking_mode') ON CONFLICT (version) DO NOTHING; +INSERT INTO public.schema_migrations (version, name) VALUES (11, 'add_grounding_metrics') ON CONFLICT (version) DO NOTHING; +INSERT INTO public.schema_migrations (version, name) VALUES (12, 'add_embedding_worker_support') ON CONFLICT (version) DO NOTHING; +INSERT INTO public.schema_migrations (version, name) VALUES (13, 'add_schema_version_tracking') ON CONFLICT (version) DO NOTHING; +INSERT INTO public.schema_migrations (version, name) VALUES (14, 'vocabulary_as_graph') ON CONFLICT (version) DO NOTHING; +INSERT INTO public.schema_migrations (version, name) VALUES (15, 'probabilistic_vocabulary_categorization') ON CONFLICT (version) DO NOTHING; +INSERT INTO public.schema_migrations (version, name) VALUES (16, 'llm_determined_relationship_direction') ON CONFLICT (version) DO NOTHING; +INSERT INTO public.schema_migrations (version, name) VALUES (17, 'vocabulary_configuration_system') ON CONFLICT (version) DO NOTHING; +INSERT INTO public.schema_migrations (version, name) VALUES (18, 'add_rate_limiting_config') ON CONFLICT (version) DO NOTHING; +INSERT INTO public.schema_migrations (version, name) VALUES (19, 'scheduled_jobs_system') ON CONFLICT (version) DO NOTHING; +INSERT INTO public.schema_migrations (version, name) VALUES (20, 'track_authenticated_users_in_jobs') ON CONFLICT (version) DO NOTHING; +INSERT INTO public.schema_migrations (version, name) VALUES (21, 'graph_provenance_tracking') ON CONFLICT (version) DO NOTHING; +INSERT INTO public.schema_migrations (version, name) VALUES (22, 'oauth_client_management') ON CONFLICT (version) DO NOTHING; +INSERT INTO public.schema_migrations (version, name) VALUES (23, 'image_storage_source_properties') ON CONFLICT (version) DO NOTHING; +INSERT INTO public.schema_migrations (version, name) VALUES (24, 'add_concept_descriptions') ON CONFLICT (version) DO NOTHING; +INSERT INTO public.schema_migrations (version, name) VALUES (25, 'graph_metrics_table') ON CONFLICT (version) DO NOTHING; +INSERT INTO public.schema_migrations (version, name) VALUES (26, 'epistemic_remeasurement_job') ON CONFLICT (version) DO NOTHING; +INSERT INTO public.schema_migrations (version, name) VALUES (27, 'source_text_embeddings') ON CONFLICT (version) DO NOTHING; +INSERT INTO public.schema_migrations (version, name) VALUES (28, 'platform_admin_resources') ON CONFLICT (version) DO NOTHING; +INSERT INTO public.schema_migrations (version, name) VALUES (29, 'fix_role_hierarchy') ON CONFLICT (version) DO NOTHING; +INSERT INTO public.schema_migrations (version, name) VALUES (30, 'users_create_permission') ON CONFLICT (version) DO NOTHING; +INSERT INTO public.schema_migrations (version, name) VALUES (31, 'platform_config') ON CONFLICT (version) DO NOTHING; +INSERT INTO public.schema_migrations (version, name) VALUES (32, 'projection_scheduled_job') ON CONFLICT (version) DO NOTHING; +INSERT INTO public.schema_migrations (version, name) VALUES (33, 'graph_change_triggers') ON CONFLICT (version) DO NOTHING; +INSERT INTO public.schema_migrations (version, name) VALUES (34, 'user_scoping_groups') ON CONFLICT (version) DO NOTHING; +INSERT INTO public.schema_migrations (version, name) VALUES (35, 'artifact_persistence') ON CONFLICT (version) DO NOTHING; +INSERT INTO public.schema_migrations (version, name) VALUES (36, 'job_artifact_linking') ON CONFLICT (version) DO NOTHING; +INSERT INTO public.schema_migrations (version, name) VALUES (37, 'admin_backup_permissions') ON CONFLICT (version) DO NOTHING; +INSERT INTO public.schema_migrations (version, name) VALUES (38, 'admin_to_platform_admin') ON CONFLICT (version) DO NOTHING; +INSERT INTO public.schema_migrations (version, name) VALUES (39, 'query_definition_metadata') ON CONFLICT (version) DO NOTHING; +INSERT INTO public.schema_migrations (version, name) VALUES (40, 'graph_crud_permissions') ON CONFLICT (version) DO NOTHING; +INSERT INTO public.schema_migrations (version, name) VALUES (41, 'job_permissions') ON CONFLICT (version) DO NOTHING; +INSERT INTO public.schema_migrations (version, name) VALUES (42, 'storage_admin_permissions') ON CONFLICT (version) DO NOTHING; +INSERT INTO public.schema_migrations (version, name) VALUES (43, 'backfill_document_meta_content_type') ON CONFLICT (version) DO NOTHING; +INSERT INTO public.schema_migrations (version, name) VALUES (44, 'ontology_graph_nodes') ON CONFLICT (version) DO NOTHING; +INSERT INTO public.schema_migrations (version, name) VALUES (45, 'ontology_lifecycle_permissions') ON CONFLICT (version) DO NOTHING; +INSERT INTO public.schema_migrations (version, name) VALUES (46, 'annealing_proposals') ON CONFLICT (version) DO NOTHING; +INSERT INTO public.schema_migrations (version, name) VALUES (47, 'annealing_scheduled_job') ON CONFLICT (version) DO NOTHING; +INSERT INTO public.schema_migrations (version, name) VALUES (48, 'ontology_edges') ON CONFLICT (version) DO NOTHING; +INSERT INTO public.schema_migrations (version, name) VALUES (49, 'proposal_execution') ON CONFLICT (version) DO NOTHING; +INSERT INTO public.schema_migrations (version, name) VALUES (50, 'exploration_definition_type') ON CONFLICT (version) DO NOTHING; +INSERT INTO public.schema_migrations (version, name) VALUES (51, 'graph_accel_extension') ON CONFLICT (version) DO NOTHING; +INSERT INTO public.schema_migrations (version, name) VALUES (52, 'program_definition_type') ON CONFLICT (version) DO NOTHING; +INSERT INTO public.schema_migrations (version, name) VALUES (53, 'annealing_autonomous_default') ON CONFLICT (version) DO NOTHING; +INSERT INTO public.schema_migrations (version, name) VALUES (54, 'sources_delete_permission') ON CONFLICT (version) DO NOTHING; +INSERT INTO public.schema_migrations (version, name) VALUES (55, 'embedding_profile') ON CONFLICT (version) DO NOTHING; +INSERT INTO public.schema_migrations (version, name) VALUES (56, 'embedding_task_prefix') ON CONFLICT (version) DO NOTHING; +INSERT INTO public.schema_migrations (version, name) VALUES (57, 'worker_lanes_and_job_dispatch') ON CONFLICT (version) DO NOTHING; +INSERT INTO public.schema_migrations (version, name) VALUES (58, 'precreate_graph_labels') ON CONFLICT (version) DO NOTHING; +INSERT INTO public.schema_migrations (version, name) VALUES (59, 'provider_model_catalog') ON CONFLICT (version) DO NOTHING; +INSERT INTO public.schema_migrations (version, name) VALUES (60, 'remove_ollama_catalog_seeds') ON CONFLICT (version) DO NOTHING; +INSERT INTO public.schema_migrations (version, name) VALUES (61, 'llamacpp_provider') ON CONFLICT (version) DO NOTHING; +INSERT INTO public.schema_migrations (version, name) VALUES (62, 'per_provider_config') ON CONFLICT (version) DO NOTHING; +INSERT INTO public.schema_migrations (version, name) VALUES (63, 'graph_epoch_events') ON CONFLICT (version) DO NOTHING; +INSERT INTO public.schema_migrations (version, name) VALUES (64, 'graph_epoch_kinds_lookup') ON CONFLICT (version) DO NOTHING; +INSERT INTO public.schema_migrations (version, name) VALUES (65, 'annealing_cadence_floors') ON CONFLICT (version) DO NOTHING; +INSERT INTO public.schema_migrations (version, name) VALUES (66, 'ontology_tombstones') ON CONFLICT (version) DO NOTHING; +INSERT INTO public.schema_migrations (version, name) VALUES (67, 'annealing_vocab_v2') ON CONFLICT (version) DO NOTHING; +INSERT INTO public.schema_migrations (version, name) VALUES (68, 'annealing_pressure_history') ON CONFLICT (version) DO NOTHING; +INSERT INTO public.schema_migrations (version, name) VALUES (69, 'vocab_embedding_lifecycle') ON CONFLICT (version) DO NOTHING; +INSERT INTO public.schema_migrations (version, name) VALUES (70, 'vocab_embedding_scheduled_job') ON CONFLICT (version) DO NOTHING; +INSERT INTO public.schema_migrations (version, name) VALUES (71, 'registration_enabled_flag') ON CONFLICT (version) DO NOTHING; +INSERT INTO public.schema_migrations (version, name) VALUES (72, 'admin_oauth_clients_write') ON CONFLICT (version) DO NOTHING; +INSERT INTO public.schema_migrations (version, name) VALUES (73, 'catalog_index') ON CONFLICT (version) DO NOTHING; +INSERT INTO public.schema_migrations (version, name) VALUES (74, 'ai_vision_config') ON CONFLICT (version) DO NOTHING; +INSERT INTO public.schema_migrations (version, name) VALUES (75, 'decouple_image_embedding_space') ON CONFLICT (version) DO NOTHING; +INSERT INTO public.schema_migrations (version, name) VALUES (76, 'freshness_clock') ON CONFLICT (version) DO NOTHING; +INSERT INTO public.schema_migrations (version, name) VALUES (77, 'restore_epoch_kind') ON CONFLICT (version) DO NOTHING; +INSERT INTO public.schema_migrations (version, name) VALUES (78, 'seed_primordial_ontology') ON CONFLICT (version) DO NOTHING; +INSERT INTO public.schema_migrations (version, name) VALUES (79, 'search_default_threshold') ON CONFLICT (version) DO NOTHING; +INSERT INTO public.schema_migrations (version, name) VALUES (80, 'search_config_permission') ON CONFLICT (version) DO NOTHING; diff --git a/schema/migrations/README.md b/schema/migrations/README.md index 8fd46334a..e7159ac43 100644 --- a/schema/migrations/README.md +++ b/schema/migrations/README.md @@ -6,6 +6,32 @@ This directory contains numbered migration files that evolve the database schema --- +## Checkpoint Baseline (2026-07-01) + +Migrations **001–080 are consolidated** into the checkpoint baseline +(`schema/00_baseline.sql`) and moved to `archived/`. This is the periodic +consolidation ADR-210 prescribes: + +- **Fresh installs** get the entire resting state from `00_baseline.sql` at + initdb; the baseline records versions 1–80 in `schema_migrations`, so + nothing in `archived/` re-runs. +- **Existing databases** created before the checkpoint still find their + pending versions: the migration runner scans `archived/` before this + directory, so a database at e.g. version 60 applies 61–80 from the archive, + then continues with 081+. +- **Never edit archived migrations** — they are history, kept only as the + upgrade path for pre-checkpoint databases. + +**The next migration is `081_*.sql`.** + +To generate a future checkpoint (e.g. after another long run of migrations), +use `schema/scripts/checkpoint.sh all` — it replays the current baseline plus +all migrations into a throwaway Apache AGE container, dumps the resting state, +and verifies the result against a second container built from the candidate +alone. See the script header for details. + +--- + ## Quick Start ### Apply All Pending Migrations @@ -330,9 +356,9 @@ COMMIT; **1. Determine next version number** ```bash -ls schema/migrations/ | sort | tail -1 -# Output: 001_baseline.sql -# Next version: 002 +ls schema/migrations/ schema/migrations/archived/ | sort | tail -1 +# Output: 080_search_config_permission.sql +# Next version: 081 ``` **2. Create migration file** @@ -536,5 +562,5 @@ See **ADR-210** for future migration system evolution. --- -**Last Updated:** 2025-10-20 -**Current Schema Version:** 001 (baseline) +**Last Updated:** 2026-07-01 +**Current Schema Version:** 080 (checkpoint baseline — migrations 001–080 consolidated into `schema/00_baseline.sql`) diff --git a/schema/migrations/001_baseline.sql b/schema/migrations/archived/001_baseline.sql similarity index 100% rename from schema/migrations/001_baseline.sql rename to schema/migrations/archived/001_baseline.sql diff --git a/schema/migrations/003_add_embedding_config.sql b/schema/migrations/archived/003_add_embedding_config.sql similarity index 100% rename from schema/migrations/003_add_embedding_config.sql rename to schema/migrations/archived/003_add_embedding_config.sql diff --git a/schema/migrations/004_add_ai_extraction_config.sql b/schema/migrations/archived/004_add_ai_extraction_config.sql similarity index 100% rename from schema/migrations/004_add_ai_extraction_config.sql rename to schema/migrations/archived/004_add_ai_extraction_config.sql diff --git a/schema/migrations/005_add_api_key_validation.sql b/schema/migrations/archived/005_add_api_key_validation.sql similarity index 100% rename from schema/migrations/005_add_api_key_validation.sql rename to schema/migrations/archived/005_add_api_key_validation.sql diff --git a/schema/migrations/006_add_embedding_config_protection.sql b/schema/migrations/archived/006_add_embedding_config_protection.sql similarity index 100% rename from schema/migrations/006_add_embedding_config_protection.sql rename to schema/migrations/archived/006_add_embedding_config_protection.sql diff --git a/schema/migrations/007_add_local_extraction_providers.sql b/schema/migrations/archived/007_add_local_extraction_providers.sql similarity index 100% rename from schema/migrations/007_add_local_extraction_providers.sql rename to schema/migrations/archived/007_add_local_extraction_providers.sql diff --git a/schema/migrations/008_add_nomic_embedding_preset.sql b/schema/migrations/archived/008_add_nomic_embedding_preset.sql similarity index 100% rename from schema/migrations/008_add_nomic_embedding_preset.sql rename to schema/migrations/archived/008_add_nomic_embedding_preset.sql diff --git a/schema/migrations/009_add_thinking_mode.sql b/schema/migrations/archived/009_add_thinking_mode.sql similarity index 100% rename from schema/migrations/009_add_thinking_mode.sql rename to schema/migrations/archived/009_add_thinking_mode.sql diff --git a/schema/migrations/011_add_grounding_metrics.sql b/schema/migrations/archived/011_add_grounding_metrics.sql similarity index 100% rename from schema/migrations/011_add_grounding_metrics.sql rename to schema/migrations/archived/011_add_grounding_metrics.sql diff --git a/schema/migrations/012_add_embedding_worker_support.sql b/schema/migrations/archived/012_add_embedding_worker_support.sql similarity index 100% rename from schema/migrations/012_add_embedding_worker_support.sql rename to schema/migrations/archived/012_add_embedding_worker_support.sql diff --git a/schema/migrations/013_add_schema_version_tracking.sql b/schema/migrations/archived/013_add_schema_version_tracking.sql similarity index 100% rename from schema/migrations/013_add_schema_version_tracking.sql rename to schema/migrations/archived/013_add_schema_version_tracking.sql diff --git a/schema/migrations/014_vocabulary_as_graph.sql b/schema/migrations/archived/014_vocabulary_as_graph.sql similarity index 100% rename from schema/migrations/014_vocabulary_as_graph.sql rename to schema/migrations/archived/014_vocabulary_as_graph.sql diff --git a/schema/migrations/015_probabilistic_vocabulary_categorization.sql b/schema/migrations/archived/015_probabilistic_vocabulary_categorization.sql similarity index 100% rename from schema/migrations/015_probabilistic_vocabulary_categorization.sql rename to schema/migrations/archived/015_probabilistic_vocabulary_categorization.sql diff --git a/schema/migrations/016_llm_determined_relationship_direction.sql b/schema/migrations/archived/016_llm_determined_relationship_direction.sql similarity index 100% rename from schema/migrations/016_llm_determined_relationship_direction.sql rename to schema/migrations/archived/016_llm_determined_relationship_direction.sql diff --git a/schema/migrations/017_vocabulary_configuration_system.sql b/schema/migrations/archived/017_vocabulary_configuration_system.sql similarity index 100% rename from schema/migrations/017_vocabulary_configuration_system.sql rename to schema/migrations/archived/017_vocabulary_configuration_system.sql diff --git a/schema/migrations/018_add_rate_limiting_config.sql b/schema/migrations/archived/018_add_rate_limiting_config.sql similarity index 100% rename from schema/migrations/018_add_rate_limiting_config.sql rename to schema/migrations/archived/018_add_rate_limiting_config.sql diff --git a/schema/migrations/019_scheduled_jobs_system.sql b/schema/migrations/archived/019_scheduled_jobs_system.sql similarity index 100% rename from schema/migrations/019_scheduled_jobs_system.sql rename to schema/migrations/archived/019_scheduled_jobs_system.sql diff --git a/schema/migrations/020_track_authenticated_users_in_jobs.sql b/schema/migrations/archived/020_track_authenticated_users_in_jobs.sql similarity index 100% rename from schema/migrations/020_track_authenticated_users_in_jobs.sql rename to schema/migrations/archived/020_track_authenticated_users_in_jobs.sql diff --git a/schema/migrations/021_graph_provenance_tracking.sql b/schema/migrations/archived/021_graph_provenance_tracking.sql similarity index 100% rename from schema/migrations/021_graph_provenance_tracking.sql rename to schema/migrations/archived/021_graph_provenance_tracking.sql diff --git a/schema/migrations/022_oauth_client_management.sql b/schema/migrations/archived/022_oauth_client_management.sql similarity index 100% rename from schema/migrations/022_oauth_client_management.sql rename to schema/migrations/archived/022_oauth_client_management.sql diff --git a/schema/migrations/023_image_storage_source_properties.sql b/schema/migrations/archived/023_image_storage_source_properties.sql similarity index 100% rename from schema/migrations/023_image_storage_source_properties.sql rename to schema/migrations/archived/023_image_storage_source_properties.sql diff --git a/schema/migrations/024_add_concept_descriptions.sql b/schema/migrations/archived/024_add_concept_descriptions.sql similarity index 100% rename from schema/migrations/024_add_concept_descriptions.sql rename to schema/migrations/archived/024_add_concept_descriptions.sql diff --git a/schema/migrations/025_graph_metrics_table.sql b/schema/migrations/archived/025_graph_metrics_table.sql similarity index 100% rename from schema/migrations/025_graph_metrics_table.sql rename to schema/migrations/archived/025_graph_metrics_table.sql diff --git a/schema/migrations/026_epistemic_remeasurement_job.sql b/schema/migrations/archived/026_epistemic_remeasurement_job.sql similarity index 100% rename from schema/migrations/026_epistemic_remeasurement_job.sql rename to schema/migrations/archived/026_epistemic_remeasurement_job.sql diff --git a/schema/migrations/027_source_text_embeddings.sql b/schema/migrations/archived/027_source_text_embeddings.sql similarity index 100% rename from schema/migrations/027_source_text_embeddings.sql rename to schema/migrations/archived/027_source_text_embeddings.sql diff --git a/schema/migrations/028_platform_admin_resources.sql b/schema/migrations/archived/028_platform_admin_resources.sql similarity index 100% rename from schema/migrations/028_platform_admin_resources.sql rename to schema/migrations/archived/028_platform_admin_resources.sql diff --git a/schema/migrations/029_fix_role_hierarchy.sql b/schema/migrations/archived/029_fix_role_hierarchy.sql similarity index 100% rename from schema/migrations/029_fix_role_hierarchy.sql rename to schema/migrations/archived/029_fix_role_hierarchy.sql diff --git a/schema/migrations/030_users_create_permission.sql b/schema/migrations/archived/030_users_create_permission.sql similarity index 100% rename from schema/migrations/030_users_create_permission.sql rename to schema/migrations/archived/030_users_create_permission.sql diff --git a/schema/migrations/031_platform_config.sql b/schema/migrations/archived/031_platform_config.sql similarity index 100% rename from schema/migrations/031_platform_config.sql rename to schema/migrations/archived/031_platform_config.sql diff --git a/schema/migrations/032_projection_scheduled_job.sql b/schema/migrations/archived/032_projection_scheduled_job.sql similarity index 100% rename from schema/migrations/032_projection_scheduled_job.sql rename to schema/migrations/archived/032_projection_scheduled_job.sql diff --git a/schema/migrations/033_graph_change_triggers.sql b/schema/migrations/archived/033_graph_change_triggers.sql similarity index 100% rename from schema/migrations/033_graph_change_triggers.sql rename to schema/migrations/archived/033_graph_change_triggers.sql diff --git a/schema/migrations/034_user_scoping_groups.sql b/schema/migrations/archived/034_user_scoping_groups.sql similarity index 100% rename from schema/migrations/034_user_scoping_groups.sql rename to schema/migrations/archived/034_user_scoping_groups.sql diff --git a/schema/migrations/035_artifact_persistence.sql b/schema/migrations/archived/035_artifact_persistence.sql similarity index 100% rename from schema/migrations/035_artifact_persistence.sql rename to schema/migrations/archived/035_artifact_persistence.sql diff --git a/schema/migrations/036_job_artifact_linking.sql b/schema/migrations/archived/036_job_artifact_linking.sql similarity index 100% rename from schema/migrations/036_job_artifact_linking.sql rename to schema/migrations/archived/036_job_artifact_linking.sql diff --git a/schema/migrations/037_admin_backup_permissions.sql b/schema/migrations/archived/037_admin_backup_permissions.sql similarity index 100% rename from schema/migrations/037_admin_backup_permissions.sql rename to schema/migrations/archived/037_admin_backup_permissions.sql diff --git a/schema/migrations/038_admin_to_platform_admin.sql b/schema/migrations/archived/038_admin_to_platform_admin.sql similarity index 100% rename from schema/migrations/038_admin_to_platform_admin.sql rename to schema/migrations/archived/038_admin_to_platform_admin.sql diff --git a/schema/migrations/039_query_definition_metadata.sql b/schema/migrations/archived/039_query_definition_metadata.sql similarity index 100% rename from schema/migrations/039_query_definition_metadata.sql rename to schema/migrations/archived/039_query_definition_metadata.sql diff --git a/schema/migrations/040_graph_crud_permissions.sql b/schema/migrations/archived/040_graph_crud_permissions.sql similarity index 100% rename from schema/migrations/040_graph_crud_permissions.sql rename to schema/migrations/archived/040_graph_crud_permissions.sql diff --git a/schema/migrations/041_job_permissions.sql b/schema/migrations/archived/041_job_permissions.sql similarity index 100% rename from schema/migrations/041_job_permissions.sql rename to schema/migrations/archived/041_job_permissions.sql diff --git a/schema/migrations/042_storage_admin_permissions.sql b/schema/migrations/archived/042_storage_admin_permissions.sql similarity index 100% rename from schema/migrations/042_storage_admin_permissions.sql rename to schema/migrations/archived/042_storage_admin_permissions.sql diff --git a/schema/migrations/043_backfill_document_meta_content_type.sql b/schema/migrations/archived/043_backfill_document_meta_content_type.sql similarity index 100% rename from schema/migrations/043_backfill_document_meta_content_type.sql rename to schema/migrations/archived/043_backfill_document_meta_content_type.sql diff --git a/schema/migrations/044_ontology_graph_nodes.sql b/schema/migrations/archived/044_ontology_graph_nodes.sql similarity index 100% rename from schema/migrations/044_ontology_graph_nodes.sql rename to schema/migrations/archived/044_ontology_graph_nodes.sql diff --git a/schema/migrations/045_ontology_lifecycle_permissions.sql b/schema/migrations/archived/045_ontology_lifecycle_permissions.sql similarity index 100% rename from schema/migrations/045_ontology_lifecycle_permissions.sql rename to schema/migrations/archived/045_ontology_lifecycle_permissions.sql diff --git a/schema/migrations/046_annealing_proposals.sql b/schema/migrations/archived/046_annealing_proposals.sql similarity index 100% rename from schema/migrations/046_annealing_proposals.sql rename to schema/migrations/archived/046_annealing_proposals.sql diff --git a/schema/migrations/047_annealing_scheduled_job.sql b/schema/migrations/archived/047_annealing_scheduled_job.sql similarity index 100% rename from schema/migrations/047_annealing_scheduled_job.sql rename to schema/migrations/archived/047_annealing_scheduled_job.sql diff --git a/schema/migrations/048_ontology_edges.sql b/schema/migrations/archived/048_ontology_edges.sql similarity index 100% rename from schema/migrations/048_ontology_edges.sql rename to schema/migrations/archived/048_ontology_edges.sql diff --git a/schema/migrations/049_proposal_execution.sql b/schema/migrations/archived/049_proposal_execution.sql similarity index 100% rename from schema/migrations/049_proposal_execution.sql rename to schema/migrations/archived/049_proposal_execution.sql diff --git a/schema/migrations/050_exploration_definition_type.sql b/schema/migrations/archived/050_exploration_definition_type.sql similarity index 100% rename from schema/migrations/050_exploration_definition_type.sql rename to schema/migrations/archived/050_exploration_definition_type.sql diff --git a/schema/migrations/051_graph_accel_extension.sql b/schema/migrations/archived/051_graph_accel_extension.sql similarity index 100% rename from schema/migrations/051_graph_accel_extension.sql rename to schema/migrations/archived/051_graph_accel_extension.sql diff --git a/schema/migrations/052_program_definition_type.sql b/schema/migrations/archived/052_program_definition_type.sql similarity index 100% rename from schema/migrations/052_program_definition_type.sql rename to schema/migrations/archived/052_program_definition_type.sql diff --git a/schema/migrations/053_annealing_autonomous_default.sql b/schema/migrations/archived/053_annealing_autonomous_default.sql similarity index 100% rename from schema/migrations/053_annealing_autonomous_default.sql rename to schema/migrations/archived/053_annealing_autonomous_default.sql diff --git a/schema/migrations/054_sources_delete_permission.sql b/schema/migrations/archived/054_sources_delete_permission.sql similarity index 100% rename from schema/migrations/054_sources_delete_permission.sql rename to schema/migrations/archived/054_sources_delete_permission.sql diff --git a/schema/migrations/055_embedding_profile.sql b/schema/migrations/archived/055_embedding_profile.sql similarity index 100% rename from schema/migrations/055_embedding_profile.sql rename to schema/migrations/archived/055_embedding_profile.sql diff --git a/schema/migrations/056_embedding_task_prefix.sql b/schema/migrations/archived/056_embedding_task_prefix.sql similarity index 100% rename from schema/migrations/056_embedding_task_prefix.sql rename to schema/migrations/archived/056_embedding_task_prefix.sql diff --git a/schema/migrations/057_worker_lanes_and_job_dispatch.sql b/schema/migrations/archived/057_worker_lanes_and_job_dispatch.sql similarity index 100% rename from schema/migrations/057_worker_lanes_and_job_dispatch.sql rename to schema/migrations/archived/057_worker_lanes_and_job_dispatch.sql diff --git a/schema/migrations/059_provider_model_catalog.sql b/schema/migrations/archived/059_provider_model_catalog.sql similarity index 100% rename from schema/migrations/059_provider_model_catalog.sql rename to schema/migrations/archived/059_provider_model_catalog.sql diff --git a/schema/migrations/060_remove_ollama_catalog_seeds.sql b/schema/migrations/archived/060_remove_ollama_catalog_seeds.sql similarity index 100% rename from schema/migrations/060_remove_ollama_catalog_seeds.sql rename to schema/migrations/archived/060_remove_ollama_catalog_seeds.sql diff --git a/schema/migrations/061_llamacpp_provider.sql b/schema/migrations/archived/061_llamacpp_provider.sql similarity index 100% rename from schema/migrations/061_llamacpp_provider.sql rename to schema/migrations/archived/061_llamacpp_provider.sql diff --git a/schema/migrations/062_per_provider_config.sql b/schema/migrations/archived/062_per_provider_config.sql similarity index 100% rename from schema/migrations/062_per_provider_config.sql rename to schema/migrations/archived/062_per_provider_config.sql diff --git a/schema/migrations/063_graph_epoch_events.sql b/schema/migrations/archived/063_graph_epoch_events.sql similarity index 100% rename from schema/migrations/063_graph_epoch_events.sql rename to schema/migrations/archived/063_graph_epoch_events.sql diff --git a/schema/migrations/064_graph_epoch_kinds_lookup.sql b/schema/migrations/archived/064_graph_epoch_kinds_lookup.sql similarity index 100% rename from schema/migrations/064_graph_epoch_kinds_lookup.sql rename to schema/migrations/archived/064_graph_epoch_kinds_lookup.sql diff --git a/schema/migrations/065_annealing_cadence_floors.sql b/schema/migrations/archived/065_annealing_cadence_floors.sql similarity index 100% rename from schema/migrations/065_annealing_cadence_floors.sql rename to schema/migrations/archived/065_annealing_cadence_floors.sql diff --git a/schema/migrations/066_ontology_tombstones.sql b/schema/migrations/archived/066_ontology_tombstones.sql similarity index 100% rename from schema/migrations/066_ontology_tombstones.sql rename to schema/migrations/archived/066_ontology_tombstones.sql diff --git a/schema/migrations/067_annealing_vocab_v2.sql b/schema/migrations/archived/067_annealing_vocab_v2.sql similarity index 100% rename from schema/migrations/067_annealing_vocab_v2.sql rename to schema/migrations/archived/067_annealing_vocab_v2.sql diff --git a/schema/migrations/068_annealing_pressure_history.sql b/schema/migrations/archived/068_annealing_pressure_history.sql similarity index 100% rename from schema/migrations/068_annealing_pressure_history.sql rename to schema/migrations/archived/068_annealing_pressure_history.sql diff --git a/schema/migrations/069_vocab_embedding_lifecycle.sql b/schema/migrations/archived/069_vocab_embedding_lifecycle.sql similarity index 100% rename from schema/migrations/069_vocab_embedding_lifecycle.sql rename to schema/migrations/archived/069_vocab_embedding_lifecycle.sql diff --git a/schema/migrations/070_vocab_embedding_scheduled_job.sql b/schema/migrations/archived/070_vocab_embedding_scheduled_job.sql similarity index 100% rename from schema/migrations/070_vocab_embedding_scheduled_job.sql rename to schema/migrations/archived/070_vocab_embedding_scheduled_job.sql diff --git a/schema/migrations/071_registration_enabled_flag.sql b/schema/migrations/archived/071_registration_enabled_flag.sql similarity index 100% rename from schema/migrations/071_registration_enabled_flag.sql rename to schema/migrations/archived/071_registration_enabled_flag.sql diff --git a/schema/migrations/072_admin_oauth_clients_write.sql b/schema/migrations/archived/072_admin_oauth_clients_write.sql similarity index 100% rename from schema/migrations/072_admin_oauth_clients_write.sql rename to schema/migrations/archived/072_admin_oauth_clients_write.sql diff --git a/schema/migrations/073_catalog_index.sql b/schema/migrations/archived/073_catalog_index.sql similarity index 100% rename from schema/migrations/073_catalog_index.sql rename to schema/migrations/archived/073_catalog_index.sql diff --git a/schema/migrations/074_ai_vision_config.sql b/schema/migrations/archived/074_ai_vision_config.sql similarity index 100% rename from schema/migrations/074_ai_vision_config.sql rename to schema/migrations/archived/074_ai_vision_config.sql diff --git a/schema/migrations/075_decouple_image_embedding_space.sql b/schema/migrations/archived/075_decouple_image_embedding_space.sql similarity index 100% rename from schema/migrations/075_decouple_image_embedding_space.sql rename to schema/migrations/archived/075_decouple_image_embedding_space.sql diff --git a/schema/migrations/076_freshness_clock.sql b/schema/migrations/archived/076_freshness_clock.sql similarity index 100% rename from schema/migrations/076_freshness_clock.sql rename to schema/migrations/archived/076_freshness_clock.sql diff --git a/schema/migrations/077_restore_epoch_kind.sql b/schema/migrations/archived/077_restore_epoch_kind.sql similarity index 100% rename from schema/migrations/077_restore_epoch_kind.sql rename to schema/migrations/archived/077_restore_epoch_kind.sql diff --git a/schema/migrations/078_seed_primordial_ontology.sql b/schema/migrations/archived/078_seed_primordial_ontology.sql similarity index 100% rename from schema/migrations/078_seed_primordial_ontology.sql rename to schema/migrations/archived/078_seed_primordial_ontology.sql diff --git a/schema/migrations/079_search_default_threshold.sql b/schema/migrations/archived/079_search_default_threshold.sql similarity index 100% rename from schema/migrations/079_search_default_threshold.sql rename to schema/migrations/archived/079_search_default_threshold.sql diff --git a/schema/migrations/080_search_config_permission.sql b/schema/migrations/archived/080_search_config_permission.sql similarity index 100% rename from schema/migrations/080_search_config_permission.sql rename to schema/migrations/archived/080_search_config_permission.sql diff --git a/schema/scripts/checkpoint.sh b/schema/scripts/checkpoint.sh new file mode 100755 index 000000000..f70fa62be --- /dev/null +++ b/schema/scripts/checkpoint.sh @@ -0,0 +1,381 @@ +#!/bin/bash +set -euo pipefail + +# ============================================================================ +# Migration Checkpoint Generator +# ============================================================================ +# Consolidates the baseline + all migrations into a single "checkpoint" +# baseline (ADR-210 "periodically consolidate"). +# +# Method: replay-and-dump against throwaway containers. Nothing here touches +# a running platform database — raw SQL via psql only, no operator tooling. +# +# replay Build container A the old way: current 00_baseline.sql + +# every migration (cold + warm) applied in numeric order. +# generate Dump container A's resting state and assemble a candidate +# consolidated baseline (relational pg_dump + seed data + +# graph-seeding migrations replayed verbatim). +# verify Build container B from the candidate baseline alone, then +# diff A vs B: full schema dump, AGE label catalog, per-table +# row counts, and normalized seed data. +# all replay + generate + verify +# clean Remove throwaway containers and work directory. +# +# Usage: +# schema/scripts/checkpoint.sh all +# CHECKPOINT_WORKDIR=/path/to/work schema/scripts/checkpoint.sh all +# +# Output: $WORKDIR/candidate_baseline.sql (plus dumps/diffs for inspection) +# @verified e0becd8 +# ============================================================================ + +SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" +PROJECT_ROOT="$( cd "$SCRIPT_DIR/../.." && pwd )" +SCHEMA_DIR="$PROJECT_ROOT/schema" + +# Same base image pinned by docker/Dockerfile.postgres (PG18 + AGE 1.7.0) +IMAGE="apache/age:release_PG18_1.7.0" + +DB_USER="admin" +DB_NAME="knowledge_graph" +DB_PASS="checkpoint-throwaway" + +CONTAINER_A="kg-checkpoint-a" # old path: baseline + all migrations +CONTAINER_B="kg-checkpoint-b" # new path: candidate baseline only + +WORKDIR="${CHECKPOINT_WORKDIR:-/tmp/kg-checkpoint}" + +# Migrations whose cypher sections seed graph state on a fresh install. +# These are carried into the checkpoint verbatim (they are idempotent). +# Backfill-only migrations (e.g. 024, 043) are no-ops on fresh databases +# and are intentionally not carried. verify proves this list is sufficient. +GRAPH_SEED_MIGRATIONS=( + "migrations/014_vocabulary_as_graph.sql" + "migrations/044_ontology_graph_nodes.sql" + "migrations-warm/058_precreate_graph_labels.sql" + "migrations/078_seed_primordial_ontology.sql" +) + +case "$(uname -m)" in + x86_64) ARCH=amd64 ;; + aarch64|arm64) ARCH=arm64 ;; + *) echo "Unsupported arch: $(uname -m)" >&2; exit 1 ;; +esac +ACCEL_DIST="$PROJECT_ROOT/graph-accel/dist/pg18/$ARCH" + +GREEN='\033[0;32m'; BLUE='\033[0;34m'; YELLOW='\033[1;33m'; RED='\033[0;31m'; NC='\033[0m' +info() { echo -e "${BLUE}→ $*${NC}"; } +ok() { echo -e "${GREEN}✓ $*${NC}"; } +warn() { echo -e "${YELLOW}⚠ $*${NC}"; } +fail() { echo -e "${RED}✗ $*${NC}" >&2; exit 1; } + +# ---------------------------------------------------------------------------- +# Container helpers +# ---------------------------------------------------------------------------- + +psql_exec() { # psql_exec [psql args...] + local c="$1"; shift + docker exec -i "$c" psql -U "$DB_USER" -d "$DB_NAME" -v ON_ERROR_STOP=1 "$@" +} + +start_container() { # start_container + local name="$1" baseline="$2" + docker rm -f "$name" >/dev/null 2>&1 || true + + local accel_mounts=() + if [ -d "$ACCEL_DIST" ]; then + local sql_file + sql_file=$(ls "$ACCEL_DIST"/graph_accel--*.sql | head -1) + accel_mounts=( + -v "$ACCEL_DIST/graph_accel.so":/usr/lib/postgresql/18/lib/graph_accel.so:ro + -v "$ACCEL_DIST/graph_accel.control":/usr/share/postgresql/18/extension/graph_accel.control:ro + -v "$sql_file":"/usr/share/postgresql/18/extension/$(basename "$sql_file")":ro + ) + else + warn "graph-accel dist not found ($ACCEL_DIST) — extension init will be skipped" + fi + + info "Starting $name from $IMAGE" + docker run -d --name "$name" \ + -e POSTGRES_DB="$DB_NAME" \ + -e POSTGRES_USER="$DB_USER" \ + -e POSTGRES_PASSWORD="$DB_PASS" \ + -v "$baseline":/docker-entrypoint-initdb.d/10-baseline.sql:ro \ + -v "$SCHEMA_DIR/11_graph_accel.sql":/docker-entrypoint-initdb.d/11-graph-accel.sql:ro \ + "${accel_mounts[@]}" \ + "$IMAGE" >/dev/null + + # TCP probe only passes once the real (post-initdb) server is listening + local i + for i in $(seq 1 60); do + if [ "$(docker inspect -f '{{.State.Running}}' "$name" 2>/dev/null)" != "true" ]; then + docker logs "$name" 2>&1 | tail -40 + fail "$name exited during init (baseline error?) — logs above" + fi + if docker exec "$name" pg_isready -h 127.0.0.1 -U "$DB_USER" -d "$DB_NAME" >/dev/null 2>&1; then + ok "$name ready" + return 0 + fi + sleep 2 + done + docker logs "$name" 2>&1 | tail -40 + fail "$name did not become ready in 120s" +} + +# Migrations in numeric order across cold + warm directories +migration_files() { + printf '%s\n' "$SCHEMA_DIR"/migrations/*.sql "$SCHEMA_DIR"/migrations-warm/*.sql \ + | awk -F/ '{print $NF "\t" $0}' | sort | cut -f2 +} + +# ---------------------------------------------------------------------------- +# replay — container A: old baseline + every migration, raw psql +# ---------------------------------------------------------------------------- + +cmd_replay() { + mkdir -p "$WORKDIR/logs" + start_container "$CONTAINER_A" "$SCHEMA_DIR/00_baseline.sql" + + local f base + while IFS= read -r f; do + base=$(basename "$f") + info "apply $base" + if ! psql_exec "$CONTAINER_A" < "$f" > "$WORKDIR/logs/$base.log" 2>&1; then + tail -20 "$WORKDIR/logs/$base.log" + fail "migration $base failed — log: $WORKDIR/logs/$base.log" + fi + done < <(migration_files) + + ok "replay complete: $(migration_files | wc -l) migrations applied to $CONTAINER_A" + psql_exec "$CONTAINER_A" -t -A -c \ + "SELECT 'schema_migrations rows: ' || count(*) FROM public.schema_migrations" +} + +# ---------------------------------------------------------------------------- +# generate — dump container A into a candidate consolidated baseline +# ---------------------------------------------------------------------------- + +# Everything except the graph itself. ag_catalog is included on purpose: +# AGE's own objects are extension members (pg_dump skips them), but the old +# baseline's search_path (ag_catalog first) parked a few helper functions +# there and the checkpoint must carry them where they live. +relational_schemas() { + psql_exec "$CONTAINER_A" -t -A -c \ + "SELECT nspname FROM pg_namespace + WHERE nspname NOT IN ('pg_catalog','information_schema','knowledge_graph') + AND nspname NOT LIKE 'pg_%' + ORDER BY nspname" +} + +cmd_generate() { + mkdir -p "$WORKDIR" + local candidate="$WORKDIR/candidate_baseline.sql" + local schemas n_flags=() + schemas=$(relational_schemas) + info "relational schemas: $(echo $schemas | tr '\n' ' ')" + local s + for s in $schemas; do n_flags+=(-n "$s"); done + + info "dumping schema (DDL)" + docker exec "$CONTAINER_A" pg_dump -U "$DB_USER" -d "$DB_NAME" \ + --schema-only --no-owner "${n_flags[@]}" \ + | sed -e '/^\\restrict /d' -e '/^\\unrestrict /d' \ + -e 's/^CREATE SCHEMA public;/-- CREATE SCHEMA public; (exists)/' \ + -e 's/^CREATE SCHEMA ag_catalog;/-- CREATE SCHEMA ag_catalog; (owned by age extension)/' \ + > "$WORKDIR/dump_schema.sql" + + info "dumping seed data" + docker exec "$CONTAINER_A" pg_dump -U "$DB_USER" -d "$DB_NAME" \ + --data-only --column-inserts --on-conflict-do-nothing --no-owner \ + "${n_flags[@]}" -T public.schema_migrations -T 'ag_catalog.*' \ + | sed -e '/^\\restrict /d' -e '/^\\unrestrict /d' \ + > "$WORKDIR/dump_data.sql" + + info "generating schema_migrations records" + psql_exec "$CONTAINER_A" -t -A -c \ + "SELECT format('INSERT INTO public.schema_migrations (version, name) VALUES (%s, %L) ON CONFLICT (version) DO NOTHING;', version, name) + FROM public.schema_migrations ORDER BY version" \ + > "$WORKDIR/dump_migrations.sql" + + # Extensions the migrations created (beyond age, plpgsql, and graph_accel — + # graph_accel is handled conditionally by 11_graph_accel.sql at initdb) + info "collecting extensions" + psql_exec "$CONTAINER_A" -t -A -c \ + "SELECT format('CREATE EXTENSION IF NOT EXISTS %I WITH SCHEMA %I;', e.extname, n.nspname) + FROM pg_extension e JOIN pg_namespace n ON e.extnamespace = n.oid + WHERE e.extname NOT IN ('age','plpgsql','graph_accel') ORDER BY 1" \ + > "$WORKDIR/dump_extensions.sql" + + local git_rev checkpoint_version + git_rev=$(git -C "$PROJECT_ROOT" rev-parse --short HEAD 2>/dev/null || echo unknown) + checkpoint_version=$(psql_exec "$CONTAINER_A" -t -A -c \ + "SELECT max(version) FROM public.schema_migrations") + + info "assembling candidate baseline" + { + cat <
"$candidate" + + ok "candidate baseline: $candidate ($(wc -l < "$candidate") lines)" +} + +# ---------------------------------------------------------------------------- +# verify — container B from candidate only; diff A vs B +# ---------------------------------------------------------------------------- + +collect_state() { # collect_state + local c="$1" out="$2" + mkdir -p "$out" + + # The two sed expressions after the header filters canonicalize varchar + # IN-list casts: PG stores `(ARRAY[...])::text[]` when parsed from the + # original migration SQL but `ARRAY[(x)::text, ...]` when re-parsed from + # pg_dump output — semantically identical round-trip noise. + docker exec "$c" pg_dump -U "$DB_USER" -d "$DB_NAME" --schema-only --no-owner \ + | sed -E -e '/^\\restrict /d' -e '/^\\unrestrict /d' -e '/^-- Dumped/d' \ + -e "s/\('([^']*)'::character varying\)::text/'\1'::character varying/g" \ + -e 's/\(ARRAY\[([^]]*)\]\)::text\[\]/ARRAY[\1]/g' \ + > "$out/schema_full.sql" + + psql_exec "$c" -t -A -c \ + "SELECT g.name || '.' || l.name || ' (' || l.kind::text || ')' + FROM ag_catalog.ag_label l JOIN ag_catalog.ag_graph g ON l.graph = g.graphid + ORDER BY 1" > "$out/age_labels.txt" + + # Per-table row counts across every non-system schema (incl. graph label tables) + local t + while IFS= read -r t; do + printf '%s\t%s\n' "$t" "$(psql_exec "$c" -t -A -c "SELECT count(*) FROM $t")" + done < <(psql_exec "$c" -t -A -c \ + "SELECT format('%I.%I', schemaname, tablename) FROM pg_tables + WHERE schemaname NOT IN ('pg_catalog','information_schema') + ORDER BY 1") > "$out/row_counts.txt" + + # Seed data with volatile values normalized (timestamps, uuids) and lines + # sorted: physical row order differs legitimately (carried migrations + # record their versions before section 5; upserts rewrite heap tuples) + docker exec "$c" pg_dump -U "$DB_USER" -d "$DB_NAME" \ + --data-only --column-inserts --no-owner \ + | sed -E \ + -e '/^\\restrict /d' -e '/^\\unrestrict /d' -e '/^-- Dumped/d' \ + -e "s/'[0-9]{4}-[0-9]{2}-[0-9]{2}[ T][0-9:.+-]+'/''/g" \ + -e "s/[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}//g" \ + | sort > "$out/data_normalized.sql" +} + +cmd_verify() { + local candidate="$WORKDIR/candidate_baseline.sql" + [ -f "$candidate" ] || fail "no candidate baseline — run generate first" + + start_container "$CONTAINER_B" "$candidate" + + info "collecting state from both containers" + collect_state "$CONTAINER_A" "$WORKDIR/state_a" + collect_state "$CONTAINER_B" "$WORKDIR/state_b" + + local status=0 check + for check in schema_full.sql age_labels.txt row_counts.txt data_normalized.sql; do + if diff -u "$WORKDIR/state_a/$check" "$WORKDIR/state_b/$check" \ + > "$WORKDIR/diff_${check%.*}.txt" 2>&1; then + ok "$check matches" + else + warn "$check DIFFERS — $WORKDIR/diff_${check%.*}.txt" + status=1 + fi + done + + if [ $status -eq 0 ]; then + ok "VERIFIED: candidate baseline is a faithful checkpoint" + else + fail "verification found differences — inspect diffs in $WORKDIR" + fi +} + +cmd_clean() { + docker rm -f "$CONTAINER_A" "$CONTAINER_B" >/dev/null 2>&1 || true + rm -rf "$WORKDIR" + ok "cleaned" +} + +case "${1:-}" in + replay) cmd_replay ;; + generate) cmd_generate ;; + verify) cmd_verify ;; + all) cmd_replay; cmd_generate; cmd_verify ;; + clean) cmd_clean ;; + *) echo "Usage: $0 {replay|generate|verify|all|clean}"; exit 1 ;; +esac From 62bf27000c35272047eb55f3adb913e3827a5cd2 Mon Sep 17 00:00:00 2001 From: Aaron Bockelie Date: Wed, 1 Jul 2026 23:11:16 -0500 Subject: [PATCH 3/4] feat(docs): schema reference parses pg_dump DDL and renders ER diagrams MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The checkpoint baseline is pg_dump output, which the docs generator did not fully understand: multi-word types (character varying, timestamp without time zone) were truncated to their first word, and constraints emitted as separate ALTER TABLE ... ADD CONSTRAINT statements (how pg_dump writes PKs, FKs, UNIQUEs) were dropped entirely. Both are now parsed and folded back into the column tables. With FK edges available, each schema section now opens with a mermaid erDiagram of its relationships — rendered natively by GitHub and by mkdocs-material (superfences already configured), published to GitHub Pages by the existing docs workflow, regenerated via make docs-schema. Table counts in the regenerated page drop (42->40 kg_api, 15->14 kg_auth) because the old textual parser counted tables that later migrations dropped or renamed; the checkpoint reflects the true resting state. Claude-Session: https://claude.ai/code/session_018VLKzWianGCKvY7CPbow8Z --- docs/reference/schema.md | 1446 +++++++++++++----------- schema/scripts/generate-schema-docs.py | 145 ++- 2 files changed, 934 insertions(+), 657 deletions(-) diff --git a/docs/reference/schema.md b/docs/reference/schema.md index c05bea611..0f31f5cc9 100644 --- a/docs/reference/schema.md +++ b/docs/reference/schema.md @@ -11,15 +11,15 @@ Relational schema for the Kappa Graph control plane. The knowledge graph itself Backed by PostgreSQL 18 with Apache AGE 1.7.0. This page is generated from `schema/00_baseline.sql` and `schema/migrations/*.sql`; do not edit it by hand. - + ## Schemas | Schema | Purpose | Tables | |---|---|---| | `public` | Cross-schema bookkeeping (migration tracking). | 2 | -| `kg_api` | API operational state: jobs, sessions, vocabulary, ontology. | 42 | -| `kg_auth` | Authentication and authorization (dynamic RBAC). | 15 | +| `kg_api` | API operational state: jobs, sessions, vocabulary, ontology. | 40 | +| `kg_auth` | Authentication and authorization (dynamic RBAC). | 14 | | `kg_logs` | Observability: audit trails, metrics, health. | 4 | ## `public` @@ -32,54 +32,95 @@ Change counters for triggering periodic epistemic status measurement | Column | Type | Constraints | Description | |---|---|---|---| -| `metric_name` | `VARCHAR(255)` | PK | Unique metric identifier (e.g., vocabulary_change_counter, concept_count) | -| `counter` | `BIGINT` | NOT NULL; DEFAULT 0 | Increments on every change (create/delete/consolidate) - never decrements | -| `last_measured_counter` | `BIGINT` | NOT NULL; DEFAULT 0 | Counter value when epistemic status was last measured | -| `last_measured_at` | `TIMESTAMP` | | Timestamp when epistemic status was last measured | -| `updated_at` | `TIMESTAMP` | DEFAULT CURRENT_TIMESTAMP | Timestamp of last counter increment | -| `notes` | `TEXT` | | | +| `metric_name` | `character varying(255)` | PK; NOT NULL | Unique metric identifier (e.g., vocabulary_change_counter, concept_count) | +| `counter` | `bigint` | NOT NULL; DEFAULT 0 | Increments on every change (create/delete/consolidate) - never decrements | +| `last_measured_counter` | `bigint` | NOT NULL; DEFAULT 0 | Counter value when epistemic status was last measured | +| `last_measured_at` | `timestamp without time zone` | | Timestamp when epistemic status was last measured | +| `updated_at` | `timestamp without time zone` | DEFAULT CURRENT_TIMESTAMP | Timestamp of last counter increment | +| `notes` | `text` | | | ### `schema_migrations` -Tracks applied schema migrations for safe schema evolution - ADR-210 +Tracks applied schema migrations for safe schema evolution - ADR-040 | Column | Type | Constraints | Description | |---|---|---|---| -| `version` | `INTEGER` | PK | Sequential migration number (001, 002, 003, ...) | -| `name` | `TEXT` | NOT NULL | Descriptive migration name (e.g., baseline, add_embedding_config) | -| `applied_at` | `TIMESTAMP` | NOT NULL; DEFAULT NOW() | Timestamp when migration was applied | +| `version` | `integer` | PK; NOT NULL | Sequential migration number (001, 002, 003, ...) | +| `name` | `text` | NOT NULL | Descriptive migration name (e.g., baseline, add_embedding_config) | +| `applied_at` | `timestamp without time zone` | NOT NULL; DEFAULT now() | Timestamp when migration was applied | ## `kg_api` API operational state: jobs, sessions, vocabulary, ontology. +#### Relationships + +```mermaid +erDiagram + query_definitions ||--o{ artifacts : "query_definition_id" + users ||--o{ artifacts : "owner_id" + ontology_versions ||--o{ concept_version_metadata : "created_in_version" + ontology_versions ||--o{ concept_version_metadata : "last_modified_version" + graph_epoch_kinds ||--o{ graph_epochs : "kind" + artifacts ||--o{ jobs : "artifact_id" + users ||--o{ jobs : "user_id" + users ||--o{ query_definitions : "owner_id" + relationship_vocabulary ||--o{ synonym_clusters : "representative_type" + embedding_generation_jobs ||--o{ system_initialization_status : "initialization_job_id" +``` + ### `aggressiveness_profiles` | Column | Type | Constraints | Description | |---|---|---|---| -| `profile_name` | `VARCHAR(50)` | PK | | -| `control_x1` | `FLOAT` | NOT NULL; CHECK (control_x1 >= 0.0 AND control_x1 <= 1.0) | | -| `control_y1` | `FLOAT` | NOT NULL | | -| `control_x2` | `FLOAT` | NOT NULL; CHECK (control_x2 >= 0.0 AND control_x2 <= 1.0) | | -| `control_y2` | `FLOAT` | NOT NULL | | -| `description` | `TEXT` | | | -| `is_builtin` | `BOOLEAN` | DEFAULT FALSE | | -| `created_at` | `TIMESTAMP` | DEFAULT NOW() | | -| `updated_at` | `TIMESTAMP` | DEFAULT NOW() | | +| `profile_name` | `character varying(50)` | PK; NOT NULL | | +| `control_x1` | `double precision` | NOT NULL | | +| `control_y1` | `double precision` | NOT NULL | | +| `control_x2` | `double precision` | NOT NULL | | +| `control_y2` | `double precision` | NOT NULL | | +| `description` | `text` | | | +| `is_builtin` | `boolean` | DEFAULT false | | +| `created_at` | `timestamp without time zone` | DEFAULT now() | | +| `updated_at` | `timestamp without time zone` | DEFAULT now() | | + +**Table constraints:** + +- `CONSTRAINT aggressiveness_profiles_control_x1_check CHECK (((control_x1 >= (0.0)::double precision) AND (control_x1 <= (1.0)::double precision)))` +- `CONSTRAINT aggressiveness_profiles_control_x2_check CHECK (((control_x2 >= (0.0)::double precision) AND (control_x2 <= (1.0)::double precision)))` ### `ai_extraction_config` -AI extraction provider configuration for runtime-switchable models - ADR-805 +AI extraction provider configuration for runtime-switchable models - ADR-041 | Column | Type | Constraints | Description | |---|---|---|---| -| `id` | `SERIAL` | PK | | -| `supports_vision` | `BOOLEAN` | DEFAULT FALSE | Whether the model supports vision/image inputs | -| `supports_json_mode` | `BOOLEAN` | DEFAULT TRUE | Whether the model supports JSON mode for structured outputs | -| `max_tokens` | `INTEGER` | | Maximum token limit for the model | -| `updated_at` | `TIMESTAMPTZ` | DEFAULT CURRENT_TIMESTAMP | | -| `updated_by` | `VARCHAR(100)` | | | -| `active` | `BOOLEAN` | DEFAULT TRUE | Only one config can be active at a time (enforced by unique index) | +| `id` | `integer` | PK; NOT NULL | | +| `provider` | `character varying(50)` | NOT NULL; UNIQUE | AI provider: openai, anthropic, ollama, or vllm | +| `model_name` | `character varying(200)` | NOT NULL | Model identifier (e.g., gpt-4o, claude-sonnet-4-20250514) | +| `supports_vision` | `boolean` | DEFAULT false | Whether the model supports vision/image inputs | +| `supports_json_mode` | `boolean` | DEFAULT true | Whether the model supports JSON mode for structured outputs | +| `max_tokens` | `integer` | | Maximum token limit for the model | +| `created_at` | `timestamp with time zone` | DEFAULT CURRENT_TIMESTAMP | | +| `updated_at` | `timestamp with time zone` | DEFAULT CURRENT_TIMESTAMP | | +| `updated_by` | `character varying(100)` | | | +| `active` | `boolean` | DEFAULT true | Only one config can be active at a time (enforced by unique index) | +| `base_url` | `character varying(255)` | | Base URL for local providers (e.g., http://localhost:11434 for Ollama) | +| `temperature` | `double precision` | DEFAULT 0.1 | Sampling temperature (0.0-1.0, lower = more consistent). Used by local providers. | +| `top_p` | `double precision` | DEFAULT 0.9 | Nucleus sampling threshold (0.0-1.0). Used by local providers. | +| `gpu_layers` | `integer` | DEFAULT '-1'::integer | GPU layers for inference: -1 = auto, 0 = CPU only, >0 = specific layer count (llama.cpp) | +| `num_threads` | `integer` | DEFAULT 4 | CPU threads for inference (used by local CPU-based providers) | +| `thinking_mode` | `character varying(20)` | DEFAULT 'off'::character varying | Thinking mode for reasoning models (Ollama 0.12.x+): off, low, medium, high. GPT-OSS: off=low, others pass through. Standard models: off=disabled, low/medium/high=enabled. | +| `max_concurrent_requests` | `integer` | DEFAULT 4 | Maximum number of concurrent API requests allowed for this provider. Limits parallelism to prevent rate limit errors and resource thrashing. Recommended: OpenAI=8, Anthropic=4, Ollama=1 | +| `max_retries` | `integer` | DEFAULT 8 | Maximum number of retry attempts for rate-limited requests (429 errors). Uses exponential backoff with jitter: 1s, 2s, 4s, 8s, 16s, 32s, 64s, ... Higher values provide more resilience with multiple workers. Recommended: 8 for cloud providers, 3 for local | + +**Table constraints:** + +- `CONSTRAINT ai_extraction_config_max_concurrent_requests_check CHECK (((max_concurrent_requests >= 1) AND (max_concurrent_requests <= 100)))` +- `CONSTRAINT ai_extraction_config_max_retries_check CHECK (((max_retries >= 0) AND (max_retries <= 20)))` +- `CONSTRAINT ai_extraction_config_provider_check CHECK (((provider)::text = ANY ((ARRAY['openai'::character varying, 'anthropic'::character varying, 'ollama'::character varying, 'openrouter'::character varying, 'llamacpp'::character varying])::text[])))` +- `CONSTRAINT ai_extraction_config_temperature_check CHECK (((temperature >= (0.0)::double precision) AND (temperature <= (1.0)::double precision)))` +- `CONSTRAINT ai_extraction_config_thinking_mode_check CHECK (((thinking_mode)::text = ANY ((ARRAY['off'::character varying, 'low'::character varying, 'medium'::character varying, 'high'::character varying])::text[])))` +- `CONSTRAINT ai_extraction_config_top_p_check CHECK (((top_p >= (0.0)::double precision) AND (top_p <= (1.0)::double precision)))` ### `ai_vision_config` @@ -87,84 +128,112 @@ Active vision (image->prose) provider selection — ADR-802 / #378. Selection-on | Column | Type | Constraints | Description | |---|---|---|---| -| `id` | `SERIAL` | PK | | -| `optional` | `` | | | -| `applied` | `to` | | | -| `temperature` | `DOUBLE` | CHECK (temperature IS NULL OR (temperature >= 0.0 AND temperature <= 1.0)) | | -| `updated_at` | `TIMESTAMPTZ` | DEFAULT CURRENT_TIMESTAMP | | -| `updated_by` | `VARCHAR(100)` | | | +| `id` | `integer` | PK; NOT NULL | | +| `provider` | `character varying(50)` | NOT NULL; UNIQUE | Provider performing image->prose description | +| `model_name` | `character varying(200)` | NOT NULL; DEFAULT ''::character varying | Vision model id; '' resolves from the catalog supports_vision rows | +| `max_tokens` | `integer` | | | +| `temperature` | `double precision` | | | +| `created_at` | `timestamp with time zone` | DEFAULT CURRENT_TIMESTAMP | | +| `updated_at` | `timestamp with time zone` | DEFAULT CURRENT_TIMESTAMP | | +| `updated_by` | `character varying(100)` | | | +| `active` | `boolean` | DEFAULT true | Only one vision config active at a time (enforced by partial unique index) | **Table constraints:** -- `CONSTRAINT ai_vision_config_provider_key UNIQUE (provider)` +- `CONSTRAINT ai_vision_config_provider_check CHECK (((provider)::text = ANY ((ARRAY['openai'::character varying, 'anthropic'::character varying, 'ollama'::character varying, 'openrouter'::character varying, 'llamacpp'::character varying])::text[])))` +- `CONSTRAINT ai_vision_config_temperature_check CHECK (((temperature IS NULL) OR ((temperature >= (0.0)::double precision) AND (temperature <= (1.0)::double precision))))` ### `annealing_options` -Tunable parameters for ontology annealing cycles (ADR-200 Phase 3b). +Tunable parameters for ontology annealing cycles (ADR-200 Phase 3b). Code defaults apply when a key is absent; database values override. | Column | Type | Constraints | Description | |---|---|---|---| -| `key` | `VARCHAR(100)` | PK | | -| `value` | `TEXT` | NOT NULL | | -| `description` | `TEXT` | | | -| `updated_at` | `TIMESTAMPTZ` | DEFAULT NOW() | | +| `key` | `character varying(100)` | PK; NOT NULL | | +| `value` | `text` | NOT NULL | | +| `description` | `text` | | | +| `updated_at` | `timestamp with time zone` | DEFAULT now() | | ### `annealing_pressure_history` -One row per annealing cycle: ecological snapshot + Bezier pressure +One row per annealing cycle: ecological snapshot + Bezier pressure read-out (#249, ADR-206 §Phase 3). Drives the web admin "pressure" panel and the future trend chart. | Column | Type | Constraints | Description | |---|---|---|---| -| `id` | `SERIAL` | PK | | -| `epoch` | `INT` | NOT NULL | | -| `total_ontologies` | `INT` | NOT NULL | | -| `total_concepts` | `INT` | NOT NULL | | -| `avg_concepts_per_ontology` | `DOUBLE` | NOT NULL | | -| `pressure_score` | `DOUBLE` | NOT NULL; CHECK (pressure_score >= 0.0 AND pressure_score <= 1.0) | | -| `pressure_zone` | `VARCHAR(20)` | NOT NULL | | -| `pressure_recommendation` | `JSONB` | NOT NULL; DEFAULT '{}'::jsonb | | -| `recorded_at` | `TIMESTAMPTZ` | NOT NULL; DEFAULT NOW() | | +| `id` | `integer` | PK; NOT NULL | | +| `epoch` | `integer` | NOT NULL | | +| `total_ontologies` | `integer` | NOT NULL | | +| `total_concepts` | `integer` | NOT NULL | | +| `avg_concepts_per_ontology` | `double precision` | NOT NULL | | +| `pressure_score` | `double precision` | NOT NULL | | +| `pressure_zone` | `character varying(20)` | NOT NULL | | +| `pressure_recommendation` | `jsonb` | NOT NULL; DEFAULT '{}'::jsonb | | +| `recorded_at` | `timestamp with time zone` | NOT NULL; DEFAULT now() | | + +**Table constraints:** + +- `CONSTRAINT annealing_pressure_history_pressure_score_check CHECK (((pressure_score >= (0.0)::double precision) AND (pressure_score <= (1.0)::double precision)))` ### `annealing_proposals` | Column | Type | Constraints | Description | |---|---|---|---| -| `id` | `SERIAL` | PK | | -| `proposal_type` | `VARCHAR(20)` | NOT NULL; CHECK (proposal_type IN ('promotion', 'demotion')) | | -| `ontology_name` | `VARCHAR(200)` | NOT NULL | | -| `anchor_concept_id` | `VARCHAR(100)` | | | -| `mass_score` | `NUMERIC(10,4)` | | | -| `coherence_score` | `NUMERIC(10,4)` | | | -| `protection_score` | `NUMERIC(10,4)` | | | -| `status` | `VARCHAR(20)` | NOT NULL; DEFAULT 'pending'; CHECK (status IN ('pending', 'approved', 'rejected', 'expired')) | | -| `created_at` | `TIMESTAMPTZ` | NOT NULL; DEFAULT NOW() | | -| `created_at_epoch` | `INT` | NOT NULL; DEFAULT 0 | | -| `reviewed_at` | `TIMESTAMPTZ` | | | -| `reviewed_by` | `VARCHAR(100)` | | | -| `reviewer_notes` | `TEXT` | | | -| `expires_at` | `TIMESTAMPTZ` | DEFAULT (NOW() + INTERVAL '7 days') | | +| `id` | `integer` | PK; NOT NULL | | +| `proposal_type` | `character varying(20)` | NOT NULL | | +| `ontology_name` | `character varying(200)` | NOT NULL | | +| `anchor_concept_id` | `character varying(100)` | | | +| `target_ontology` | `character varying(200)` | | | +| `reasoning` | `text` | NOT NULL | | +| `mass_score` | `numeric(10,4)` | | | +| `coherence_score` | `numeric(10,4)` | | | +| `protection_score` | `numeric(10,4)` | | | +| `status` | `character varying(20)` | NOT NULL; DEFAULT 'pending'::character varying | | +| `created_at` | `timestamp with time zone` | NOT NULL; DEFAULT now() | | +| `created_at_epoch` | `integer` | NOT NULL; DEFAULT 0 | | +| `reviewed_at` | `timestamp with time zone` | | | +| `reviewed_by` | `character varying(100)` | | | +| `reviewer_notes` | `text` | | | +| `expires_at` | `timestamp with time zone` | DEFAULT (now() + '7 days'::interval) | | +| `executed_at` | `timestamp with time zone` | | | +| `execution_result` | `jsonb` | | | +| `suggested_name` | `character varying(200)` | | | +| `suggested_description` | `text` | | | +| `proposal_kind` | `character varying(20)` | NOT NULL; DEFAULT 'ontology'::character varying | | +| `params` | `jsonb` | | | + +**Table constraints:** + +- `CONSTRAINT annealing_proposals_proposal_kind_check CHECK (((proposal_kind)::text = ANY ((ARRAY['ontology'::character varying, 'control'::character varying])::text[])))` +- `CONSTRAINT annealing_proposals_proposal_type_check CHECK (((proposal_type)::text = ANY ((ARRAY['CLEAVE'::character varying, 'DISSOLVE'::character varying, 'MERGE'::character varying, 'RENAME'::character varying, 'NO_ACTION'::character varying, 'ESCALATE'::character varying, 'ADJUST_CONTROL'::character varying, 'promotion'::character varying, 'demotion'::character varying])::text[])))` +- `CONSTRAINT annealing_proposals_status_check CHECK (((status)::text = ANY ((ARRAY['pending'::character varying, 'approved'::character varying, 'rejected'::character varying, 'expired'::character varying, 'executing'::character varying, 'executed'::character varying, 'failed'::character varying])::text[])))` ### `artifacts` -Computed artifact metadata with Garage blob pointers (ADR-116) +Computed artifact metadata with Garage blob pointers (ADR-083) | Column | Type | Constraints | Description | |---|---|---|---| -| `id` | `SERIAL` | PK | | -| `representation` | `VARCHAR(50)` | NOT NULL | Source UI/tool: polarity_explorer, cli, mcp_server, etc. | -| `name` | `VARCHAR(200)` | | | -| `created_at` | `TIMESTAMPTZ` | NOT NULL; DEFAULT NOW() | | -| `expires_at` | `TIMESTAMPTZ` | | | -| `metadata` | `JSONB` | | | -| `inline_result` | `JSONB` | | Small results (<10KB) stored inline | -| `garage_key` | `VARCHAR(200)` | | Pointer to Garage blob for large results | -| `ontology` | `VARCHAR(200)` | | | -| `concept_ids` | `TEXT[]` | | Concept IDs involved in this artifact | +| `id` | `integer` | PK; NOT NULL | | +| `artifact_type` | `character varying(50)` | NOT NULL | Type of computation: polarity_analysis, projection, etc. | +| `representation` | `character varying(50)` | NOT NULL | Source UI/tool: polarity_explorer, cli, mcp_server, etc. | +| `name` | `character varying(200)` | | | +| `owner_id` | `integer` | FK → kg_auth.users(id) | | +| `graph_epoch` | `bigint` | NOT NULL | graph_change_counter at creation for freshness validation | +| `created_at` | `timestamp with time zone` | NOT NULL; DEFAULT now() | | +| `expires_at` | `timestamp with time zone` | | | +| `parameters` | `jsonb` | NOT NULL | | +| `metadata` | `jsonb` | | | +| `inline_result` | `jsonb` | | Small results (<10KB) stored inline | +| `garage_key` | `character varying(200)` | | Pointer to Garage blob for large results | +| `query_definition_id` | `integer` | FK → kg_api.query_definitions(id) | | +| `ontology` | `character varying(200)` | | | +| `concept_ids` | `text[]` | | Concept IDs involved in this artifact | **Table constraints:** -- `CONSTRAINT valid_representation CHECK (representation IN ( 'polarity_explorer', 'embedding_landscape', 'block_builder', 'edge_explorer', 'vocabulary_chord', 'force_graph_2d', 'force_graph_3d', 'report_workspace', 'cli', 'mcp_server', 'api_direct' ))` -- `CONSTRAINT has_content CHECK ( inline_result IS NOT NULL OR garage_key IS NOT NULL )` +- `CONSTRAINT has_content CHECK (((inline_result IS NOT NULL) OR (garage_key IS NOT NULL)))` +- `CONSTRAINT valid_artifact_type CHECK (((artifact_type)::text = ANY ((ARRAY['polarity_analysis'::character varying, 'projection'::character varying, 'query_result'::character varying, 'graph_subgraph'::character varying, 'vocabulary_analysis'::character varying, 'epistemic_measurement'::character varying, 'consolidation_result'::character varying, 'search_result'::character varying, 'connection_path'::character varying, 'report'::character varying, 'stats_snapshot'::character varying])::text[])))` +- `CONSTRAINT valid_representation CHECK (((representation)::text = ANY ((ARRAY['polarity_explorer'::character varying, 'embedding_landscape'::character varying, 'block_builder'::character varying, 'edge_explorer'::character varying, 'vocabulary_chord'::character varying, 'force_graph_2d'::character varying, 'force_graph_3d'::character varying, 'report_workspace'::character varying, 'cli'::character varying, 'mcp_server'::character varying, 'api_direct'::character varying])::text[])))` ### `catalog_edge` @@ -172,13 +241,11 @@ ADR-501: parent->child membership edges projecting canonical :SCOPED_BY (ontolog | Column | Type | Constraints | Description | |---|---|---|---| -| `parent_kind` | `VARCHAR(16)` | NOT NULL | | -| `child_kind` | `VARCHAR(16)` | NOT NULL | | -| `graph_epoch` | `INTEGER` | NOT NULL | | - -**Table constraints:** - -- `PRIMARY KEY (parent_kind, parent_id, child_kind, child_id)` +| `parent_kind` | `character varying(16)` | PK; NOT NULL | | +| `parent_id` | `text` | PK; NOT NULL | | +| `child_kind` | `character varying(16)` | PK; NOT NULL | | +| `child_id` | `text` | PK; NOT NULL | | +| `graph_epoch` | `bigint` | NOT NULL | | ### `catalog_node` @@ -186,81 +253,106 @@ ADR-501: materialized identity/metadata for catalog nodes (ontology/document/con | Column | Type | Constraints | Description | |---|---|---|---| -| `kind` | `VARCHAR(16)` | NOT NULL | | - -**Table constraints:** - -- `PRIMARY KEY (kind, node_id)` +| `kind` | `character varying(16)` | PK; NOT NULL | | +| `node_id` | `text` | PK; NOT NULL | | +| `name` | `text` | NOT NULL | | +| `name_lower` | `text` | NOT NULL | | +| `child_count` | `integer` | NOT NULL; DEFAULT 0 | Number of direct children (documents-in-ontology, concepts-in-document); 0 for leaf concepts. | +| `content_type` | `character varying(32)` | | | +| `properties` | `jsonb` | NOT NULL; DEFAULT '{}'::jsonb | | +| `graph_epoch` | `bigint` | NOT NULL | graph_change_counter at build time; compared to kg_api.get_graph_epoch() for staleness. | +| `indexed_at` | `timestamp with time zone` | NOT NULL; DEFAULT now() | | ### `concept_access_stats` -Node-level access patterns for caching - ADR-601 +Node-level access patterns for caching - ADR-025 | Column | Type | Constraints | Description | |---|---|---|---| -| `concept_id` | `VARCHAR(100)` | PK | | -| `access_count` | `INTEGER` | DEFAULT 0 | | -| `last_accessed` | `TIMESTAMPTZ` | | | -| `avg_query_time_ms` | `NUMERIC(10,2)` | | | -| `queries_as_start` | `INTEGER` | DEFAULT 0 | | -| `queries_as_result` | `INTEGER` | DEFAULT 0 | | +| `concept_id` | `character varying(100)` | PK; NOT NULL | | +| `access_count` | `integer` | DEFAULT 0 | | +| `last_accessed` | `timestamp with time zone` | | | +| `avg_query_time_ms` | `numeric(10,2)` | | | +| `queries_as_start` | `integer` | DEFAULT 0 | | +| `queries_as_result` | `integer` | DEFAULT 0 | | ### `concept_version_metadata` | Column | Type | Constraints | Description | |---|---|---|---| -| `concept_id` | `VARCHAR(100)` | PK | | -| `created_in_version` | `INTEGER` | FK → kg_api.ontology_versions(version_id) | | -| `last_modified_version` | `INTEGER` | FK → kg_api.ontology_versions(version_id) | | +| `concept_id` | `character varying(100)` | PK; NOT NULL | | +| `created_in_version` | `integer` | FK → kg_api.ontology_versions(version_id) | | +| `last_modified_version` | `integer` | FK → kg_api.ontology_versions(version_id) | | ### `edge_usage_stats` -Performance tracking for graph traversals - ADR-601 +Performance tracking for graph traversals - ADR-025 | Column | Type | Constraints | Description | |---|---|---|---| -| `from_concept_id` | `VARCHAR(100)` | NOT NULL | | -| `to_concept_id` | `VARCHAR(100)` | NOT NULL | | -| `relationship_type` | `VARCHAR(100)` | NOT NULL | | -| `traversal_count` | `INTEGER` | DEFAULT 0 | | -| `last_traversed` | `TIMESTAMPTZ` | | | -| `avg_query_time_ms` | `NUMERIC(10,2)` | | | - -**Table constraints:** +| `from_concept_id` | `character varying(100)` | PK; NOT NULL | | +| `to_concept_id` | `character varying(100)` | PK; NOT NULL | | +| `relationship_type` | `character varying(100)` | PK; NOT NULL | | +| `traversal_count` | `integer` | DEFAULT 0 | | +| `last_traversed` | `timestamp with time zone` | | | +| `avg_query_time_ms` | `numeric(10,2)` | | | -- `PRIMARY KEY (from_concept_id, to_concept_id, relationship_type)` +### `embedding_config_legacy` -### `embedding_config` - -Resource-aware embedding configuration for local and remote models - ADR-804. Includes preset for nomic-embed-text-v1.5. +Resource-aware embedding configuration for local and remote models - ADR-039. Includes preset for nomic-embed-text-v1.5. | Column | Type | Constraints | Description | |---|---|---|---| -| `id` | `SERIAL` | PK | | -| `embedding_dimensions` | `INTEGER` | NOT NULL | | -| `precision` | `VARCHAR(20)` | NOT NULL; CHECK (precision IN ('float16', 'float32')) | | -| `num_threads` | `INTEGER` | | CPU threads for inference (local provider only) | -| `device` | `VARCHAR(20)` | CHECK (device IN ('cpu', 'cuda', 'mps')) | Compute device: cpu, cuda, or mps (local provider only) | -| `batch_size` | `INTEGER` | DEFAULT 8 | Batch size for embedding generation | -| `normalize_embeddings` | `BOOLEAN` | DEFAULT TRUE | | -| `updated_at` | `TIMESTAMPTZ` | DEFAULT CURRENT_TIMESTAMP | | -| `updated_by` | `VARCHAR(100)` | | | -| `active` | `BOOLEAN` | DEFAULT TRUE | Only one config can be active at a time (enforced by unique constraint) | +| `id` | `integer` | PK; NOT NULL | | +| `provider` | `character varying(50)` | NOT NULL | Embedding provider: local (sentence-transformers) or openai | +| `model_name` | `character varying(200)` | NOT NULL | Model identifier (HuggingFace ID for local, OpenAI model name for remote) | +| `embedding_dimensions` | `integer` | NOT NULL | | +| `precision` | `character varying(20)` | NOT NULL | | +| `max_memory_mb` | `integer` | | Maximum RAM allocation for local model (local provider only) | +| `num_threads` | `integer` | | CPU threads for inference (local provider only) | +| `device` | `character varying(20)` | | Compute device: cpu, cuda, or mps (local provider only) | +| `batch_size` | `integer` | DEFAULT 8 | Batch size for embedding generation | +| `max_seq_length` | `integer` | | | +| `normalize_embeddings` | `boolean` | DEFAULT true | | +| `created_at` | `timestamp with time zone` | DEFAULT CURRENT_TIMESTAMP | | +| `updated_at` | `timestamp with time zone` | DEFAULT CURRENT_TIMESTAMP | | +| `updated_by` | `character varying(100)` | | | +| `active` | `boolean` | DEFAULT true | Only one config can be active at a time (enforced by unique constraint) | +| `delete_protected` | `boolean` | DEFAULT false | Prevents deletion without first removing protection (default configs) | +| `change_protected` | `boolean` | DEFAULT false | Prevents changing provider/dimensions without explicit unlock (safety) | + +**Table constraints:** + +- `CONSTRAINT embedding_config_device_check CHECK (((device)::text = ANY ((ARRAY['cpu'::character varying, 'cuda'::character varying, 'mps'::character varying])::text[])))` +- `CONSTRAINT embedding_config_precision_check CHECK ((("precision")::text = ANY ((ARRAY['float16'::character varying, 'float32'::character varying])::text[])))` +- `CONSTRAINT embedding_config_provider_check CHECK (((provider)::text = ANY ((ARRAY['local'::character varying, 'openai'::character varying])::text[])))` ### `embedding_generation_jobs` -ADR-809: Tracks embedding generation jobs for audit trail and progress monitoring +ADR-045: Tracks embedding generation jobs for audit trail and progress monitoring | Column | Type | Constraints | Description | |---|---|---|---| -| `job_id` | `UUID` | PK; DEFAULT gen_random_uuid() | | -| `processed_count` | `INTEGER` | DEFAULT 0 | | -| `failed_count` | `INTEGER` | DEFAULT 0 | | -| `embedding_provider` | `VARCHAR(50)` | | | -| `started_at` | `TIMESTAMPTZ` | | | -| `completed_at` | `TIMESTAMPTZ` | | | -| `duration_ms` | `INTEGER` | | | -| `error_message` | `TEXT` | | | +| `job_id` | `uuid` | PK; NOT NULL; DEFAULT gen_random_uuid() | | +| `job_type` | `character varying(50)` | NOT NULL | | +| `target_types` | `character varying(100)[]` | | | +| `target_count` | `integer` | | | +| `status` | `character varying(20)` | NOT NULL; DEFAULT 'pending'::character varying | | +| `processed_count` | `integer` | DEFAULT 0 | | +| `failed_count` | `integer` | DEFAULT 0 | | +| `embedding_model` | `character varying(100)` | | | +| `embedding_provider` | `character varying(50)` | | | +| `created_at` | `timestamp with time zone` | DEFAULT CURRENT_TIMESTAMP | | +| `started_at` | `timestamp with time zone` | | | +| `completed_at` | `timestamp with time zone` | | | +| `duration_ms` | `integer` | | | +| `result_summary` | `jsonb` | | | +| `error_message` | `text` | | | + +**Table constraints:** + +- `CONSTRAINT embedding_generation_jobs_job_type_check CHECK (((job_type)::text = ANY ((ARRAY['cold_start'::character varying, 'vocabulary_update'::character varying, 'model_migration'::character varying, 'batch_regeneration'::character varying])::text[])))` +- `CONSTRAINT embedding_generation_jobs_status_check CHECK (((status)::text = ANY ((ARRAY['pending'::character varying, 'running'::character varying, 'completed'::character varying, 'failed'::character varying, 'cancelled'::character varying])::text[])))` ### `embedding_profile` @@ -268,37 +360,45 @@ Unified embedding profile with text + image model slots. Replaces embedding_conf | Column | Type | Constraints | Description | |---|---|---|---| -| `id` | `SERIAL` | PK | | -| `name` | `VARCHAR(200)` | NOT NULL | | -| `vector_space` | `VARCHAR(100)` | NOT NULL | Compatibility key for the universal TEXT/prose space (concepts, edges, docs, | -| `text_model_name` | `VARCHAR(200)` | NOT NULL | | -| `text_loader` | `VARCHAR(50)` | NOT NULL | How to load text model: sentence-transformers, transformers (AutoModel), or api | -| `'transformers'` | `` | | | -| `'api'` | `text_revision` | | | -| `text_precision` | `VARCHAR(20)` | DEFAULT 'float16' | | -| `text_trust_remote_code` | `BOOLEAN` | DEFAULT FALSE | | -| `image_model_name` | `VARCHAR(200)` | | | -| `image_loader` | `VARCHAR(50)` | | How to load image model: sentence-transformers, transformers (AutoModel), or api | -| `image_revision` | `VARCHAR(200)` | | | -| `image_dimensions` | `INTEGER` | | | -| `image_precision` | `VARCHAR(20)` | DEFAULT 'float16' | | -| `image_trust_remote_code` | `BOOLEAN` | DEFAULT FALSE | | -| `max_memory_mb` | `INTEGER` | | | -| `num_threads` | `INTEGER` | | | -| `batch_size` | `INTEGER` | DEFAULT 8 | | -| `max_seq_length` | `INTEGER` | | | -| `normalize_embeddings` | `BOOLEAN` | DEFAULT TRUE | | -| `delete_protected` | `BOOLEAN` | DEFAULT FALSE | | -| `change_protected` | `BOOLEAN` | DEFAULT FALSE | | -| `created_at` | `TIMESTAMPTZ` | DEFAULT CURRENT_TIMESTAMP | | -| `updated_at` | `TIMESTAMPTZ` | DEFAULT CURRENT_TIMESTAMP | | -| `updated_by` | `VARCHAR(100)` | | | +| `id` | `integer` | PK; NOT NULL | | +| `name` | `character varying(200)` | NOT NULL | | +| `vector_space` | `character varying(100)` | NOT NULL | Compatibility key for the universal TEXT/prose space (concepts, edges, docs, image-prose). Profiles with the same text vector_space produce comparable text embeddings. Image embeddings are independent — see image_vector_space (ADR-803). | +| `multimodal` | `boolean` | DEFAULT false | When true, the text model also handles image embeddings (e.g. SigLIP 2) | +| `text_provider` | `character varying(50)` | NOT NULL | | +| `text_model_name` | `character varying(200)` | NOT NULL | | +| `text_loader` | `character varying(50)` | NOT NULL | How to load text model: sentence-transformers, transformers (AutoModel), or api | +| `text_revision` | `character varying(200)` | | | +| `text_dimensions` | `integer` | NOT NULL | | +| `text_precision` | `character varying(20)` | DEFAULT 'float16'::character varying | | +| `text_trust_remote_code` | `boolean` | DEFAULT false | | +| `image_provider` | `character varying(50)` | | | +| `image_model_name` | `character varying(200)` | | | +| `image_loader` | `character varying(50)` | | How to load image model: sentence-transformers, transformers (AutoModel), or api | +| `image_revision` | `character varying(200)` | | | +| `image_dimensions` | `integer` | | | +| `image_precision` | `character varying(20)` | DEFAULT 'float16'::character varying | | +| `image_trust_remote_code` | `boolean` | DEFAULT false | | +| `device` | `character varying(20)` | DEFAULT 'cpu'::character varying | | +| `max_memory_mb` | `integer` | | | +| `num_threads` | `integer` | | | +| `batch_size` | `integer` | DEFAULT 8 | | +| `max_seq_length` | `integer` | | | +| `normalize_embeddings` | `boolean` | DEFAULT true | | +| `active` | `boolean` | DEFAULT false | | +| `delete_protected` | `boolean` | DEFAULT false | | +| `change_protected` | `boolean` | DEFAULT false | | +| `created_at` | `timestamp with time zone` | DEFAULT CURRENT_TIMESTAMP | | +| `updated_at` | `timestamp with time zone` | DEFAULT CURRENT_TIMESTAMP | | +| `updated_by` | `character varying(100)` | | | +| `text_query_prefix` | `character varying(200)` | | Prefix prepended for search queries (e.g. search_query: ) | +| `text_document_prefix` | `character varying(200)` | | Prefix prepended for stored documents (e.g. search_document: ) | +| `image_vector_space` | `character varying(100)` | | Independent vector_space of the image (modality) embedding index (ADR-803). NULL for text-only / multimodal profiles. Never compared to text vector_space. | **Table constraints:** -- `CONSTRAINT chk_image_loader CHECK ( image_loader IN ('sentence-transformers', 'transformers', 'api') OR image_loader IS NULL )` -- `CONSTRAINT chk_multimodal_no_image CHECK (` -- `CONSTRAINT chk_image_dimensions_match CHECK (` +- `CONSTRAINT chk_image_loader CHECK ((((image_loader)::text = ANY ((ARRAY['sentence-transformers'::character varying, 'transformers'::character varying, 'api'::character varying])::text[])) OR (image_loader IS NULL)))` +- `CONSTRAINT chk_multimodal_no_image CHECK (((NOT multimodal) OR ((image_provider IS NULL) AND (image_model_name IS NULL) AND (image_loader IS NULL) AND (image_dimensions IS NULL))))` +- `CONSTRAINT chk_text_loader CHECK (((text_loader)::text = ANY ((ARRAY['sentence-transformers'::character varying, 'transformers'::character varying, 'api'::character varying])::text[])))` ### `graph_epoch_kinds` @@ -306,44 +406,27 @@ ADR-203: Discriminator for graph_epochs.kind. semantic_wallclock distinguishes e | Column | Type | Constraints | Description | |---|---|---|---| -| `kind` | `TEXT` | PK | | -| `semantic_wallclock` | `BOOLEAN` | NOT NULL | When TRUE, occurred_at is the meaningful timestamp for downstream consumers. When FALSE, occurred_at is recorded for audit/forensics but should not drive time-based queries on the resulting graph state. | -| `description` | `TEXT` | | | +| `kind` | `text` | PK; NOT NULL | | +| `semantic_wallclock` | `boolean` | NOT NULL | When TRUE, occurred_at is the meaningful timestamp for downstream consumers. When FALSE, occurred_at is recorded for audit/forensics but should not drive time-based queries on the resulting graph state. | +| `description` | `text` | | | ### `graph_epochs` -ADR-203: Monotonic event log of graph mutations. Distinct from graph_change_counter (ADR-114) which is a composite cache-invalidation checksum. +ADR-203: Monotonic event log of graph mutations. Distinct from graph_change_counter (ADR-079) which is a composite cache-invalidation checksum. | Column | Type | Constraints | Description | |---|---|---|---| -| `event_id` | `BIGSERIAL` | PK | Monotonic logical-time id. Foreign-keyed by Instance.created_at_event_id. | -| `occurred_at` | `TIMESTAMPTZ` | NOT NULL; DEFAULT NOW() | | -| `kind` | `TEXT` | NOT NULL; CHECK (kind IN ('ingestion', 'reasoning', 'annealing', 'edit')) | ingestion \| reasoning \| annealing \| edit. Determines whether occurred_at is semantically meaningful for the rows attributable to this event. | -| `actor` | `TEXT` | | | -| `counter_after` | `BIGINT` | | | -| `metadata` | `JSONB` | NOT NULL; DEFAULT '{}'::jsonb | | - -### `ingestion_jobs` +| `event_id` | `bigint` | PK; NOT NULL | Monotonic logical-time id. Foreign-keyed by Instance.created_at_event_id. | +| `occurred_at` | `timestamp with time zone` | NOT NULL; DEFAULT now() | | +| `kind` | `text` | NOT NULL; FK → kg_api.graph_epoch_kinds(kind) | ingestion \| reasoning \| annealing \| edit. Determines whether occurred_at is semantically meaningful for the rows attributable to this event. | +| `actor` | `text` | | | +| `counter_after` | `bigint` | | | +| `metadata` | `jsonb` | NOT NULL; DEFAULT '{}'::jsonb | | +| `status` | `text` | NOT NULL; DEFAULT 'in_progress'::text | ADR-207/#384: in_progress (set at record_graph_epoch) \| completed \| failed. Only in_progress blocks the committed watermark — both completed and failed count toward it (per-chunk commits mean a failed job may have mutated the graph). The completed/failed split is for analytics (drop zero-instance jobs from hot/stale signals), not for freshness. | -Job queue (replaces SQLite jobs.db) - ADR-300, ADR-209, ADR-100 +**Table constraints:** -| Column | Type | Constraints | Description | -|---|---|---|---| -| `job_id` | `VARCHAR(50)` | PK | | -| `job_type` | `VARCHAR(50)` | NOT NULL | | -| `status` | `VARCHAR(50)` | NOT NULL; CHECK (status IN ( 'pending', 'awaiting_approval', 'approved', 'queued', 'running', 'completed', 'failed', 'cancelled' )) | | -| `ontology` | `VARCHAR(200)` | NOT NULL | | -| `client_id` | `VARCHAR(100)` | | | -| `content_hash` | `VARCHAR(80)` | | | -| `progress` | `JSONB` | | | -| `result` | `JSONB` | | | -| `analysis` | `JSONB` | | | -| `started_at` | `TIMESTAMPTZ` | | | -| `completed_at` | `TIMESTAMPTZ` | | | -| `approved_at` | `TIMESTAMPTZ` | | | -| `approved_by` | `VARCHAR(100)` | | | -| `expires_at` | `TIMESTAMPTZ` | | | -| `error_message` | `TEXT` | | | +- `CONSTRAINT graph_epochs_status_check CHECK ((status = ANY (ARRAY['in_progress'::text, 'completed'::text, 'failed'::text])))` ### `jobs` @@ -351,72 +434,84 @@ Unified job queue for all background tasks (ingestion, backup, vocab, scheduled) | Column | Type | Constraints | Description | |---|---|---|---| -| `job_id` | `TEXT` | PK | Unique job identifier (UUID) | -| `job_type` | `TEXT` | NOT NULL | Type of job: ingestion, restore, backup, vocab_refresh, vocab_consolidate | -| `content_hash` | `TEXT` | | SHA256 hash for deduplication (used with ontology to detect duplicates) | -| `ontology` | `TEXT` | | Target ontology for the job | -| `client_id` | `TEXT` | | Client identifier for SSE streaming | -| `status` | `TEXT` | NOT NULL | Job status: pending_approval, approved, running, completed, failed, cancelled | -| `progress` | `TEXT` | | Progress message for UI display | -| `result` | `TEXT` | | Final result data (JSON) | -| `error` | `TEXT` | | Error message if failed | -| `created_at` | `TIMESTAMP` | NOT NULL; DEFAULT NOW() | | -| `started_at` | `TIMESTAMP` | | | -| `completed_at` | `TIMESTAMP` | | | -| `job_data` | `JSONB` | NOT NULL | Job-specific parameters (JSON) | -| `approved_at` | `TIMESTAMP` | | When job was approved by user | -| `approved_by` | `TEXT` | | Who approved the job | -| `expires_at` | `TIMESTAMP` | | When pending approval expires | -| `processing_mode` | `TEXT` | DEFAULT 'serial' | Execution mode: serial or parallel | -| `created_by` | `VARCHAR(100)` | DEFAULT 'unknown' | User or system identifier that created the job | -| `is_system_job` | `BOOLEAN` | DEFAULT FALSE | True for system-scheduled jobs (cannot be deleted by users) | - -### `launcher_config` +| `job_id` | `text` | PK; NOT NULL | Unique job identifier (UUID) | +| `job_type` | `text` | NOT NULL | Type of job: ingestion, restore, backup, vocab_refresh, vocab_consolidate | +| `content_hash` | `text` | | SHA256 hash for deduplication (used with ontology to detect duplicates) | +| `ontology` | `text` | | Target ontology for the job | +| `status` | `text` | NOT NULL | Job status: pending_approval, approved, running, completed, failed, cancelled | +| `progress` | `text` | | Progress message for UI display | +| `result` | `text` | | Final result data (JSON) | +| `error` | `text` | | Error message if failed | +| `created_at` | `timestamp without time zone` | NOT NULL; DEFAULT now() | | +| `started_at` | `timestamp without time zone` | | | +| `completed_at` | `timestamp without time zone` | | | +| `job_data` | `jsonb` | NOT NULL | Job-specific parameters (JSON) | +| `analysis` | `text` | | Pre-approval analysis (cost/time estimates) | +| `approved_at` | `timestamp without time zone` | | When job was approved by user | +| `approved_by` | `text` | | Who approved the job | +| `expires_at` | `timestamp without time zone` | | When pending approval expires | +| `processing_mode` | `text` | DEFAULT 'serial'::text | Execution mode: serial or parallel | +| `job_source` | `character varying(50)` | DEFAULT 'user_cli'::character varying | Source of job creation: user_cli, user_api, scheduled_task, system | +| `created_by` | `character varying(100)` | DEFAULT 'unknown'::character varying | User or system identifier that created the job | +| `is_system_job` | `boolean` | DEFAULT false | True for system-scheduled jobs (cannot be deleted by users) | +| `user_id` | `integer` | NOT NULL; FK → kg_auth.users(id) | User who submitted the job (FK to kg_auth.users.id) | +| `source_filename` | `text` | | Display name for source: filename, "stdin", or MCP session ID (best-effort metadata) | +| `source_type` | `text` | | Ingestion method: file (CLI file), stdin (pipe), mcp (Claude), api (direct) - enables source-aware queries | +| `source_path` | `text` | | Full filesystem path for file ingestion (null for stdin/mcp/api) - helps identify exact source file | +| `source_hostname` | `text` | | Hostname where ingestion initiated (CLI only, null for MCP/API) - useful for distributed deployments | +| `artifact_id` | `integer` | FK → kg_api.artifacts(id) | Artifact created by this job (ADR-083). NULL for jobs that do not produce artifacts. | +| `priority` | `integer` | NOT NULL; DEFAULT 0 | | +| `claimed_by` | `text` | | | +| `claimed_at` | `timestamp with time zone` | | | +| `cancelled` | `boolean` | NOT NULL; DEFAULT false | | +| `retries` | `integer` | NOT NULL; DEFAULT 0 | | +| `max_retries` | `integer` | NOT NULL; DEFAULT 3 | | -| Column | Type | Constraints | Description | -|---|---|---|---| +**Table constraints:** + +- `CONSTRAINT chk_source_type CHECK (((source_type IS NULL) OR (source_type = ANY (ARRAY['file'::text, 'stdin'::text, 'mcp'::text, 'api'::text]))))` ### `ontology_tombstones` -Positive operator-intent signal that an ontology was deliberately +Positive operator-intent signal that an ontology was deliberately removed and must not be silently recreated by a subsequent ingest (#402 Defect B2). Operator-initiated delete writes a row; annealing dissolution does not. | Column | Type | Constraints | Description | |---|---|---|---| -| `name` | `VARCHAR(200)` | PK | | -| `removed_at` | `TIMESTAMPTZ` | NOT NULL; DEFAULT NOW() | | -| `removed_by` | `VARCHAR(100)` | | | -| `reason` | `TEXT` | | | +| `name` | `character varying(200)` | PK; NOT NULL | | +| `removed_at` | `timestamp with time zone` | NOT NULL; DEFAULT now() | | +| `removed_by` | `character varying(100)` | | | +| `reason` | `text` | | | ### `ontology_versions` -Formal ontology versioning with immutable snapshots - ADR-602 +Formal ontology versioning with immutable snapshots - ADR-026 | Column | Type | Constraints | Description | |---|---|---|---| -| `version_id` | `SERIAL` | PK | | -| `version_number` | `VARCHAR(20)` | UNIQUE; NOT NULL | | -| `created_at` | `TIMESTAMPTZ` | NOT NULL; DEFAULT NOW() | | -| `created_by` | `VARCHAR(100)` | | | -| `change_summary` | `TEXT` | | | -| `is_active` | `BOOLEAN` | DEFAULT TRUE | | -| `vocabulary_snapshot` | `JSONB` | NOT NULL | | -| `types_added` | `TEXT[]` | | | -| `types_aliased` | `JSONB` | | | -| `types_deprecated` | `TEXT[]` | | | -| `backward_compatible` | `BOOLEAN` | DEFAULT TRUE | | -| `migration_required` | `BOOLEAN` | DEFAULT FALSE | | +| `version_id` | `integer` | PK; NOT NULL | | +| `version_number` | `character varying(20)` | NOT NULL; UNIQUE | | +| `created_at` | `timestamp with time zone` | NOT NULL; DEFAULT now() | | +| `created_by` | `character varying(100)` | | | +| `change_summary` | `text` | | | +| `is_active` | `boolean` | DEFAULT true | | +| `vocabulary_snapshot` | `jsonb` | NOT NULL | | +| `types_added` | `text[]` | | | +| `types_aliased` | `jsonb` | | | +| `types_deprecated` | `text[]` | | | +| `backward_compatible` | `boolean` | DEFAULT true | | +| `migration_required` | `boolean` | DEFAULT false | | ### `platform_config` -Platform lifecycle configuration for operator control plane (ADR-211) +Platform lifecycle configuration for operator control plane (ADR-061) | Column | Type | Constraints | Description | |---|---|---|---| -| `key` | `VARCHAR(100)` | PK | | -| `value` | `TEXT` | NOT NULL | | -| `description` | `TEXT` | | | -| `updated_at` | `TIMESTAMPTZ` | DEFAULT NOW() | | -| `updated_by` | `VARCHAR(100)` | DEFAULT 'system' | | +| `key` | `character varying(100)` | PK; NOT NULL | | +| `value` | `text` | NOT NULL | | +| `description` | `text` | | | +| `updated_at` | `timestamp with time zone` | DEFAULT now() | | +| `updated_by` | `character varying(100)` | DEFAULT 'system'::character varying | | ### `provider_model_catalog` @@ -424,273 +519,345 @@ Cached model catalog per AI provider with curation and pricing (ADR-800) | Column | Type | Constraints | Description | |---|---|---|---| -| `id` | `SERIAL` | PK | | -| `provider` | `VARCHAR(50)` | NOT NULL | | -| `'anthropic'` | `` | | | -| `'ollama'` | `` | | | -| `'openrouter'` | `model_id` | NOT NULL | | -| `'embedding'` | `` | | | -| `'vision'` | `` | | | -| `'translation'` | `context_length` | | | -| `max_completion_tokens` | `INTEGER` | | | -| `supports_vision` | `BOOLEAN` | DEFAULT FALSE | | -| `supports_json_mode` | `BOOLEAN` | DEFAULT FALSE | | -| `supports_tool_use` | `BOOLEAN` | DEFAULT FALSE | | -| `supports_streaming` | `BOOLEAN` | DEFAULT TRUE | | -| `price_completion_per_m` | `NUMERIC` | | | -| `price_cache_read_per_m` | `NUMERIC` | | | -| `is_default` | `BOOLEAN` | DEFAULT FALSE | | -| `sort_order` | `INTEGER` | DEFAULT 0 | | -| `updated_at` | `TIMESTAMPTZ` | DEFAULT NOW() | | +| `id` | `integer` | PK; NOT NULL | | +| `provider` | `character varying(50)` | NOT NULL | | +| `model_id` | `character varying(300)` | NOT NULL | | +| `display_name` | `character varying(300)` | | | +| `category` | `character varying(50)` | NOT NULL | | +| `context_length` | `integer` | | | +| `max_completion_tokens` | `integer` | | | +| `supports_vision` | `boolean` | DEFAULT false | | +| `supports_json_mode` | `boolean` | DEFAULT false | | +| `supports_tool_use` | `boolean` | DEFAULT false | | +| `supports_streaming` | `boolean` | DEFAULT true | | +| `price_prompt_per_m` | `numeric` | | | +| `price_completion_per_m` | `numeric` | | | +| `price_cache_read_per_m` | `numeric` | | | +| `enabled` | `boolean` | DEFAULT false | | +| `is_default` | `boolean` | DEFAULT false | | +| `sort_order` | `integer` | DEFAULT 0 | | +| `upstream_provider` | `character varying(100)` | | | +| `raw_metadata` | `jsonb` | | | +| `fetched_at` | `timestamp with time zone` | | | +| `created_at` | `timestamp with time zone` | DEFAULT now() | | +| `updated_at` | `timestamp with time zone` | DEFAULT now() | | **Table constraints:** -- `UNIQUE(provider, model_id, category)` +- `UNIQUE (provider, model_id, category)` ### `pruning_recommendations` -Pending vocabulary management actions - ADR-603 +Pending vocabulary management actions - ADR-032 | Column | Type | Constraints | Description | |---|---|---|---| -| `id` | `SERIAL` | PK | | -| `relationship_type` | `VARCHAR(100)` | NOT NULL | | -| `target_type` | `VARCHAR(100)` | | | -| `action_type` | `VARCHAR(50)` | NOT NULL; CHECK (action_type IN ('merge', 'prune', 'deprecate', 'skip')) | | -| `review_level` | `VARCHAR(20)` | NOT NULL; CHECK (review_level IN ('none', 'ai', 'human')) | | -| `reasoning` | `TEXT` | NOT NULL | | -| `similarity` | `NUMERIC(4,3)` | | | -| `value_score` | `NUMERIC(10,2)` | | | -| `metadata` | `JSONB` | | | -| `status` | `VARCHAR(50)` | NOT NULL; DEFAULT 'pending'; CHECK (status IN ('pending', 'approved', 'rejected', 'executed', 'expired')) | | -| `created_at` | `TIMESTAMPTZ` | NOT NULL; DEFAULT NOW() | | -| `reviewed_at` | `TIMESTAMPTZ` | | | -| `reviewed_by` | `VARCHAR(100)` | | | -| `reviewer_notes` | `TEXT` | | | -| `executed_at` | `TIMESTAMPTZ` | | | -| `expires_at` | `TIMESTAMPTZ` | | | +| `id` | `integer` | PK; NOT NULL | | +| `relationship_type` | `character varying(100)` | NOT NULL | | +| `target_type` | `character varying(100)` | | | +| `action_type` | `character varying(50)` | NOT NULL | | +| `review_level` | `character varying(20)` | NOT NULL | | +| `reasoning` | `text` | NOT NULL | | +| `similarity` | `numeric(4,3)` | | | +| `value_score` | `numeric(10,2)` | | | +| `metadata` | `jsonb` | | | +| `status` | `character varying(50)` | NOT NULL; DEFAULT 'pending'::character varying | | +| `created_at` | `timestamp with time zone` | NOT NULL; DEFAULT now() | | +| `reviewed_at` | `timestamp with time zone` | | | +| `reviewed_by` | `character varying(100)` | | | +| `reviewer_notes` | `text` | | | +| `executed_at` | `timestamp with time zone` | | | +| `expires_at` | `timestamp with time zone` | | | + +**Table constraints:** + +- `CONSTRAINT pruning_recommendations_action_type_check CHECK (((action_type)::text = ANY ((ARRAY['merge'::character varying, 'prune'::character varying, 'deprecate'::character varying, 'skip'::character varying])::text[])))` +- `CONSTRAINT pruning_recommendations_review_level_check CHECK (((review_level)::text = ANY ((ARRAY['none'::character varying, 'ai'::character varying, 'human'::character varying])::text[])))` +- `CONSTRAINT pruning_recommendations_status_check CHECK (((status)::text = ANY ((ARRAY['pending'::character varying, 'approved'::character varying, 'rejected'::character varying, 'executed'::character varying, 'expired'::character varying])::text[])))` ### `query_definitions` -Saved query recipes that can be re-executed (ADR-116) +Saved query recipes that can be re-executed (ADR-083) | Column | Type | Constraints | Description | |---|---|---|---| -| `id` | `SERIAL` | PK | | -| `name` | `VARCHAR(200)` | NOT NULL | | -| `definition_type` | `VARCHAR(50)` | NOT NULL | Type of query: block_diagram, cypher, search, polarity, connection, exploration, program | -| `definition` | `JSONB` | NOT NULL | Query parameters/structure as JSON | -| `owner_id` | `INTEGER` | FK → kg_auth.users(id) | | -| `created_at` | `TIMESTAMPTZ` | NOT NULL; DEFAULT NOW() | | -| `updated_at` | `TIMESTAMPTZ` | NOT NULL; DEFAULT NOW() | | +| `id` | `integer` | PK; NOT NULL | | +| `name` | `character varying(200)` | NOT NULL | | +| `definition_type` | `character varying(50)` | NOT NULL | Type of query: block_diagram, cypher, search, polarity, connection, exploration, program | +| `definition` | `jsonb` | NOT NULL | Query parameters/structure as JSON | +| `owner_id` | `integer` | FK → kg_auth.users(id) | | +| `created_at` | `timestamp with time zone` | NOT NULL; DEFAULT now() | | +| `updated_at` | `timestamp with time zone` | NOT NULL; DEFAULT now() | | +| `metadata` | `jsonb` | DEFAULT '{}'::jsonb | Optional metadata (nodeCount, edgeCount, description, etc.) | **Table constraints:** -- `CONSTRAINT valid_definition_type CHECK (definition_type IN ( 'block_diagram', 'cypher', 'search', 'polarity', 'connection' ))` +- `CONSTRAINT valid_definition_type CHECK (((definition_type)::text = ANY ((ARRAY['block_diagram'::character varying, 'cypher'::character varying, 'search'::character varying, 'polarity'::character varying, 'connection'::character varying, 'exploration'::character varying, 'program'::character varying])::text[])))` ### `rate_limits` | Column | Type | Constraints | Description | |---|---|---|---| -| `client_id` | `VARCHAR(100)` | NOT NULL | | -| `endpoint` | `VARCHAR(200)` | NOT NULL | | -| `window_start` | `TIMESTAMPTZ` | NOT NULL | | -| `request_count` | `INTEGER` | NOT NULL; DEFAULT 0 | | - -**Table constraints:** - -- `PRIMARY KEY (client_id, endpoint, window_start)` +| `client_id` | `character varying(100)` | PK; NOT NULL | | +| `endpoint` | `character varying(200)` | PK; NOT NULL | | +| `window_start` | `timestamp with time zone` | PK; NOT NULL | | +| `request_count` | `integer` | NOT NULL; DEFAULT 0 | | ### `relationship_vocabulary` -Canonical relationship types with embeddings - ADR-601, ADR-603 +Canonical relationship types with embeddings - ADR-025, ADR-032 | Column | Type | Constraints | Description | |---|---|---|---| -| `relationship_type` | `VARCHAR(100)` | PK | | -| `description` | `TEXT` | | | -| `category` | `VARCHAR(50)` | | | -| `added_by` | `VARCHAR(100)` | | | -| `added_at` | `TIMESTAMPTZ` | NOT NULL; DEFAULT NOW() | | -| `usage_count` | `INTEGER` | DEFAULT 0 | | -| `is_active` | `BOOLEAN` | DEFAULT TRUE | | -| `is_builtin` | `BOOLEAN` | DEFAULT FALSE | | -| `synonyms` | `VARCHAR(100)[]` | | | -| `deprecation_reason` | `TEXT` | | | +| `relationship_type` | `character varying(100)` | PK; NOT NULL | | +| `description` | `text` | | | +| `category` | `character varying(50)` | | | +| `added_by` | `character varying(100)` | | | +| `added_at` | `timestamp with time zone` | NOT NULL; DEFAULT now() | | +| `usage_count` | `integer` | DEFAULT 0 | | +| `is_active` | `boolean` | DEFAULT true | | +| `is_builtin` | `boolean` | DEFAULT false | | +| `synonyms` | `character varying(100)[]` | | | +| `deprecation_reason` | `text` | | | +| `embedding` | `jsonb` | | Cached embedding vector (JSONB array) for synonym detection (ADR-032) | +| `embedding_model` | `character varying(100)` | | | +| `embedding_generated_at` | `timestamp with time zone` | | | +| `grounding_contribution` | `double precision` | | ADR-046: Measures impact on concept grounding strength (0.0-1.0). Higher values indicate this edge type significantly affects truth convergence. | +| `last_grounding_calculated` | `timestamp with time zone` | | ADR-046: Timestamp when grounding metrics were last recalculated. Enables staleness detection. | +| `avg_confidence` | `double precision` | | ADR-046: Average confidence score across all edges of this type. Helps identify low-quality edge types. | +| `semantic_diversity` | `double precision` | | ADR-046: Semantic diversity score (0.0-1.0). High diversity may indicate overly broad type; low diversity may indicate well-defined type. | +| `embedding_quality_score` | `double precision` | | ADR-045: Quality score for embedding (based on validation checks like magnitude, dimensionality) | +| `embedding_validation_status` | `character varying(20)` | | ADR-045: Validation status - stale indicates model changed since generation | +| `category_source` | `character varying(20)` | DEFAULT 'builtin'::character varying | Source of category assignment: builtin (hand-assigned) or computed (ADR-047) | +| `category_confidence` | `double precision` | | Confidence score (0.0-1.0) for computed categories based on max similarity to seed types | +| `category_scores` | `jsonb` | | Full category similarity breakdown as JSON: {"causation": 0.85, "composition": 0.45, ...} | +| `category_ambiguous` | `boolean` | DEFAULT false | True if runner-up category score > 0.70 (potential multi-category type) | +| `direction_semantics` | `character varying(20)` | DEFAULT NULL::character varying | LLM-determined direction: outward (from→to), inward (from←to), bidirectional (symmetric). NULL = not yet determined by LLM. | + +**Table constraints:** + +- `CONSTRAINT relationship_vocabulary_avg_confidence_check CHECK (((avg_confidence >= (0.0)::double precision) AND (avg_confidence <= (1.0)::double precision)))` +- `CONSTRAINT relationship_vocabulary_embedding_quality_score_check CHECK (((embedding_quality_score >= (0.0)::double precision) AND (embedding_quality_score <= (1.0)::double precision)))` +- `CONSTRAINT relationship_vocabulary_embedding_validation_status_check CHECK (((embedding_validation_status)::text = ANY ((ARRAY['pending'::character varying, 'valid'::character varying, 'invalid'::character varying, 'stale'::character varying])::text[])))` +- `CONSTRAINT relationship_vocabulary_grounding_contribution_check CHECK (((grounding_contribution >= (0.0)::double precision) AND (grounding_contribution <= (1.0)::double precision)))` +- `CONSTRAINT relationship_vocabulary_semantic_diversity_check CHECK (((semantic_diversity >= (0.0)::double precision) AND (semantic_diversity <= (1.0)::double precision)))` ### `scheduled_jobs` -Configuration for scheduled background jobs (ADR-111) +Scheduled background jobs: - category_refresh: Re-integrate LLM-generated vocabulary categories (every 6 hours) - vocab_consolidation: Auto-consolidate vocabulary based on hysteresis thresholds (every 12 hours) | Column | Type | Constraints | Description | |---|---|---|---| -| `id` | `SERIAL` | PK | | -| `name` | `VARCHAR(100)` | UNIQUE; NOT NULL | Unique identifier for the scheduled job | -| `launcher_class` | `VARCHAR(255)` | NOT NULL | Python class name in launcher registry (e.g., CategoryRefreshLauncher) | -| `schedule_cron` | `VARCHAR(100)` | NOT NULL | Cron expression for schedule (e.g., "0 */6 * * *" = every 6 hours) | -| `enabled` | `BOOLEAN` | DEFAULT TRUE | Whether this schedule is active (can be disabled on failure) | -| `max_retries` | `INTEGER` | DEFAULT 5 | Max consecutive failures before auto-disabling schedule | -| `retry_count` | `INTEGER` | DEFAULT 0 | Current consecutive failure count (reset on success or skip) | -| `last_run` | `TIMESTAMP` | | Last time the schedule was checked (success, skip, or failure) | -| `last_success` | `TIMESTAMP` | | Last time a job was successfully enqueued | -| `last_failure` | `TIMESTAMP` | | Last time the launcher failed with an exception | -| `next_run` | `TIMESTAMP` | | Calculated next run time (from cron expression or backoff) | -| `created_at` | `TIMESTAMP` | DEFAULT NOW() | | -| `updated_at` | `TIMESTAMP` | DEFAULT NOW() | | +| `id` | `integer` | PK; NOT NULL | | +| `name` | `character varying(100)` | NOT NULL; UNIQUE | Unique identifier for the scheduled job | +| `launcher_class` | `character varying(255)` | NOT NULL | Python class name in launcher registry (e.g., CategoryRefreshLauncher) | +| `schedule_cron` | `character varying(100)` | NOT NULL | Cron expression for schedule (e.g., "0 */6 * * *" = every 6 hours) | +| `enabled` | `boolean` | DEFAULT true | Whether this schedule is active (can be disabled on failure) | +| `max_retries` | `integer` | DEFAULT 5 | Max consecutive failures before auto-disabling schedule | +| `retry_count` | `integer` | DEFAULT 0 | Current consecutive failure count (reset on success or skip) | +| `last_run` | `timestamp without time zone` | | Last time the schedule was checked (success, skip, or failure) | +| `last_success` | `timestamp without time zone` | | Last time a job was successfully enqueued | +| `last_failure` | `timestamp without time zone` | | Last time the launcher failed with an exception | +| `next_run` | `timestamp without time zone` | | Calculated next run time (from cron expression or backoff) | +| `created_at` | `timestamp without time zone` | DEFAULT now() | | +| `updated_at` | `timestamp without time zone` | DEFAULT now() | | ### `schema_migrations` -Tracks applied database migrations for backup/restore compatibility. Schema version is included in backups to ensure restore compatibility when database schema evolves. See ADR-107 for details. +Tracks applied database migrations for backup/restore compatibility. Schema version is included in backups to ensure restore compatibility when database schema evolves. See ADR-015 for details. | Column | Type | Constraints | Description | |---|---|---|---| -| `version` | `INTEGER` | PK | Migration number matching schema/migrations/NNN_*.sql files | -| `description` | `TEXT` | NOT NULL | Human-readable description of what this migration does | -| `applied_at` | `TIMESTAMP` | NOT NULL; DEFAULT NOW() | When this migration was applied to the database | +| `version` | `integer` | PK; NOT NULL | Migration number matching schema/migrations/NNN_*.sql files | +| `description` | `text` | NOT NULL | Human-readable description of what this migration does | +| `applied_at` | `timestamp without time zone` | NOT NULL; DEFAULT now() | When this migration was applied to the database | ### `sessions` | Column | Type | Constraints | Description | |---|---|---|---| -| `session_id` | `VARCHAR(100)` | PK | | -| `user_id` | `INTEGER` | | | -| `created_at` | `TIMESTAMPTZ` | NOT NULL; DEFAULT NOW() | | -| `expires_at` | `TIMESTAMPTZ` | NOT NULL | | -| `last_activity` | `TIMESTAMPTZ` | NOT NULL; DEFAULT NOW() | | -| `metadata` | `JSONB` | | | +| `session_id` | `character varying(100)` | PK; NOT NULL | | +| `user_id` | `integer` | | | +| `created_at` | `timestamp with time zone` | NOT NULL; DEFAULT now() | | +| `expires_at` | `timestamp with time zone` | NOT NULL | | +| `last_activity` | `timestamp with time zone` | NOT NULL; DEFAULT now() | | +| `metadata` | `jsonb` | | | ### `skipped_relationships` -Capture layer for unmatched relationship types - ADR-601 +Capture layer for unmatched relationship types - ADR-025 | Column | Type | Constraints | Description | |---|---|---|---| -| `id` | `SERIAL` | PK | | -| `relationship_type` | `VARCHAR(100)` | NOT NULL | | -| `from_concept_label` | `VARCHAR(500)` | | | -| `to_concept_label` | `VARCHAR(500)` | | | -| `job_id` | `VARCHAR(50)` | | | -| `ontology` | `VARCHAR(200)` | | | -| `first_seen` | `TIMESTAMPTZ` | NOT NULL; DEFAULT NOW() | | -| `last_seen` | `TIMESTAMPTZ` | NOT NULL; DEFAULT NOW() | | -| `occurrence_count` | `INTEGER` | DEFAULT 1 | | -| `sample_context` | `JSONB` | | | +| `id` | `integer` | PK; NOT NULL | | +| `relationship_type` | `character varying(100)` | NOT NULL | | +| `from_concept_label` | `character varying(500)` | | | +| `to_concept_label` | `character varying(500)` | | | +| `job_id` | `character varying(50)` | | | +| `ontology` | `character varying(200)` | | | +| `first_seen` | `timestamp with time zone` | NOT NULL; DEFAULT now() | | +| `last_seen` | `timestamp with time zone` | NOT NULL; DEFAULT now() | | +| `occurrence_count` | `integer` | DEFAULT 1 | | +| `sample_context` | `jsonb` | | | **Table constraints:** -- `UNIQUE(relationship_type, from_concept_label, to_concept_label)` +- `UNIQUE (relationship_type, from_concept_label, to_concept_label)` ### `source_embeddings` -ADR-812: Embeddings for source text chunks with offset tracking and hash verification +ADR-068: Embeddings for source text chunks with offset tracking and hash verification | Column | Type | Constraints | Description | |---|---|---|---| -| `embedding_id` | `SERIAL` | PK | | -| `chunk_strategy` | `TEXT` | NOT NULL | Chunking strategy used: sentence, paragraph, semantic, or count | -| `end_offset` | `INTEGER` | NOT NULL; CHECK (end_offset > start_offset) | Character offset in Source.full_text where chunk ends (exclusive) | -| `chunk_text` | `TEXT` | NOT NULL | Actual chunk content stored for verification (should match Source.full_text[start_offset:end_offset]) | -| `embedding_provider` | `TEXT` | | | -| `updated_at` | `TIMESTAMPTZ` | DEFAULT CURRENT_TIMESTAMP | | +| `embedding_id` | `integer` | PK; NOT NULL | | +| `source_id` | `text` | NOT NULL | Reference to Source node in Apache AGE graph | +| `chunk_index` | `integer` | NOT NULL | 0-based chunk number within source (e.g., 0, 1, 2...) | +| `chunk_strategy` | `text` | NOT NULL | Chunking strategy used: sentence, paragraph, semantic, or count | +| `start_offset` | `integer` | NOT NULL | Character offset in Source.full_text where chunk starts (0-based) | +| `end_offset` | `integer` | NOT NULL | Character offset in Source.full_text where chunk ends (exclusive) | +| `chunk_text` | `text` | NOT NULL | Actual chunk content stored for verification (should match Source.full_text[start_offset:end_offset]) | +| `chunk_hash` | `text` | NOT NULL | SHA256 hash of chunk_text - verifies chunk integrity | +| `source_hash` | `text` | NOT NULL | SHA256 hash of Source.full_text - detects when source text changes (stale embedding indicator) | +| `embedding` | `bytea` | NOT NULL | Vector embedding bytes (float16 or float32 array, packed as bytea) | +| `embedding_model` | `text` | NOT NULL | Embedding model name (e.g., "nomic-ai/nomic-embed-text-v1.5", "text-embedding-3-small") | +| `embedding_dimension` | `integer` | NOT NULL | Embedding vector dimension (must match system embedding_config for cosine similarity) | +| `embedding_provider` | `text` | | | +| `created_at` | `timestamp with time zone` | DEFAULT CURRENT_TIMESTAMP | | +| `updated_at` | `timestamp with time zone` | DEFAULT CURRENT_TIMESTAMP | | + +**Table constraints:** + +- `CONSTRAINT source_embeddings_check CHECK ((end_offset > start_offset))` +- `CONSTRAINT source_embeddings_chunk_index_check CHECK ((chunk_index >= 0))` +- `CONSTRAINT source_embeddings_chunk_strategy_check CHECK ((chunk_strategy = ANY (ARRAY['sentence'::text, 'paragraph'::text, 'semantic'::text, 'count'::text])))` +- `CONSTRAINT source_embeddings_embedding_dimension_check CHECK ((embedding_dimension > 0))` +- `CONSTRAINT source_embeddings_start_offset_check CHECK ((start_offset >= 0))` +- `UNIQUE (source_id, chunk_index, chunk_strategy)` ### `synonym_clusters` -ADR-604: Tracks groups of synonymous edge types discovered through embedding-based semantic similarity (threshold > 0.85) +ADR-046: Tracks groups of synonymous edge types discovered through embedding-based semantic similarity (threshold > 0.85) | Column | Type | Constraints | Description | |---|---|---|---| -| `cluster_id` | `UUID` | PK; DEFAULT gen_random_uuid() | | -| `total_usage_count` | `INTEGER` | | | -| `detection_method` | `VARCHAR(50)` | DEFAULT 'embedding_similarity' | | -| `merge_recommended` | `BOOLEAN` | DEFAULT FALSE | | -| `merge_completed_at` | `TIMESTAMPTZ` | | | +| `cluster_id` | `uuid` | PK; NOT NULL; DEFAULT gen_random_uuid() | | +| `representative_type` | `character varying(100)` | FK → kg_api.relationship_vocabulary(relationship_type) | The canonical type to use when merging cluster members. Usually has highest usage_count or is builtin. | +| `member_types` | `character varying(100)[]` | | | +| `avg_similarity` | `double precision` | | Average cosine similarity between all pairs of member embeddings. Higher values indicate stronger synonym relationship. | +| `cluster_size` | `integer` | | | +| `total_usage_count` | `integer` | | | +| `detected_at` | `timestamp with time zone` | DEFAULT CURRENT_TIMESTAMP | | +| `detection_method` | `character varying(50)` | DEFAULT 'embedding_similarity'::character varying | | +| `is_active` | `boolean` | DEFAULT true | | +| `merge_recommended` | `boolean` | DEFAULT false | | +| `merge_completed_at` | `timestamp with time zone` | | | + +**Table constraints:** + +- `CONSTRAINT synonym_clusters_avg_similarity_check CHECK (((avg_similarity >= (0.0)::double precision) AND (avg_similarity <= (1.0)::double precision)))` ### `system_api_keys` -Encrypted system API keys for LLM providers (ADR-405, ADR-805) +Encrypted system API keys for LLM providers (ADR-031, ADR-041) | Column | Type | Constraints | Description | |---|---|---|---| -| `provider` | `VARCHAR(50)` | PK | Provider name: openai, anthropic | -| `encrypted_key` | `BYTEA` | NOT NULL | Fernet-encrypted API key (AES-128-CBC + HMAC-SHA256) | -| `updated_at` | `TIMESTAMPTZ` | DEFAULT NOW() | Last time key was updated | -| `last_validated_at` | `TIMESTAMPTZ` | | Timestamp of last validation check (typically at API startup) | -| `validation_error` | `TEXT` | | Error message from last failed validation attempt | +| `provider` | `character varying(50)` | PK; NOT NULL | Provider name: openai, anthropic | +| `encrypted_key` | `bytea` | NOT NULL | Fernet-encrypted API key (AES-128-CBC + HMAC-SHA256) | +| `updated_at` | `timestamp with time zone` | DEFAULT now() | Last time key was updated | +| `validation_status` | `character varying(20)` | DEFAULT 'untested'::character varying | API key validation state: valid, invalid, or untested | +| `last_validated_at` | `timestamp with time zone` | | Timestamp of last validation check (typically at API startup) | +| `validation_error` | `text` | | Error message from last failed validation attempt | + +**Table constraints:** + +- `CONSTRAINT system_api_keys_validation_status_check CHECK (((validation_status)::text = ANY ((ARRAY['valid'::character varying, 'invalid'::character varying, 'untested'::character varying])::text[])))` ### `system_initialization_status` -ADR-809: Tracks completion of system initialization tasks like cold start embedding generation +ADR-045: Tracks completion of system initialization tasks like cold start embedding generation | Column | Type | Constraints | Description | |---|---|---|---| -| `component` | `VARCHAR(50)` | PK | | -| `initialized` | `BOOLEAN` | DEFAULT FALSE | | -| `initialized_at` | `TIMESTAMPTZ` | | | -| `initialization_job_id` | `UUID` | FK → kg_api.embedding_generation_jobs(job_id) | | -| `version` | `VARCHAR(20)` | | | -| `metadata` | `JSONB` | | | +| `component` | `character varying(50)` | PK; NOT NULL | | +| `initialized` | `boolean` | DEFAULT false | | +| `initialized_at` | `timestamp with time zone` | | | +| `initialization_job_id` | `uuid` | FK → kg_api.embedding_generation_jobs(job_id) | | +| `version` | `character varying(20)` | | | +| `metadata` | `jsonb` | | | +| `last_processed_vocab_change_counter` | `bigint` | NOT NULL; DEFAULT 0 CONSTRAINT system_initialization_statu_last_processed_vocab_chang_not_null | Snapshot of vocabulary_change_counter at the time this initialization component last completed embedding work. The cold-start and VocabEmbeddingLauncher paths compare current counter vs. this value to detect new work since the last successful run. Replaces the binary `initialized` flag for embedding components (the flag stays for non-counter-driven components). Default 0 means "no work has completed yet" — correct initial state. | ### `vocabulary_audit` | Column | Type | Constraints | Description | |---|---|---|---| -| `id` | `SERIAL` | PK | | -| `relationship_type` | `VARCHAR(100)` | | | -| `action` | `VARCHAR(50)` | NOT NULL | | -| `performed_by` | `VARCHAR(100)` | | | -| `performed_at` | `TIMESTAMPTZ` | NOT NULL; DEFAULT NOW() | | -| `details` | `JSONB` | | | +| `id` | `integer` | PK; NOT NULL | | +| `relationship_type` | `character varying(100)` | | | +| `action` | `character varying(50)` | NOT NULL | | +| `performed_by` | `character varying(100)` | | | +| `performed_at` | `timestamp with time zone` | NOT NULL; DEFAULT now() | | +| `details` | `jsonb` | | | ### `vocabulary_config` -System configuration for automatic vocabulary management (ADR-603) +System configuration for automatic vocabulary management (ADR-032) | Column | Type | Constraints | Description | |---|---|---|---| -| `key` | `VARCHAR(100)` | PK | | -| `value` | `TEXT` | NOT NULL | | -| `description` | `TEXT` | | | -| `updated_at` | `TIMESTAMPTZ` | NOT NULL; DEFAULT NOW() | | -| `updated_by` | `VARCHAR(100)` | | | +| `key` | `character varying(100)` | PK; NOT NULL | | +| `value` | `text` | NOT NULL | | +| `description` | `text` | | | +| `updated_at` | `timestamp with time zone` | NOT NULL; DEFAULT now() | | +| `updated_by` | `character varying(100)` | | | ### `vocabulary_history` -Detailed vocabulary change tracking with context (ADR-603) +Detailed vocabulary change tracking with context (ADR-032) | Column | Type | Constraints | Description | |---|---|---|---| -| `id` | `SERIAL` | PK | | -| `relationship_type` | `VARCHAR(100)` | NOT NULL | | -| `action` | `VARCHAR(50)` | NOT NULL; CHECK (action IN ('added', 'merged', 'pruned', 'deprecated', 'reactivated')) | | -| `performed_by` | `VARCHAR(100)` | NOT NULL | | -| `performed_at` | `TIMESTAMPTZ` | NOT NULL; DEFAULT NOW() | | -| `target_type` | `VARCHAR(100)` | | | -| `reason` | `TEXT` | | | -| `metadata` | `JSONB` | | | -| `aggressiveness` | `NUMERIC(4,3)` | | | -| `zone` | `VARCHAR(20)` | | | -| `vocab_size_before` | `INTEGER` | | | -| `vocab_size_after` | `INTEGER` | | | +| `id` | `integer` | PK; NOT NULL | | +| `relationship_type` | `character varying(100)` | NOT NULL | | +| `action` | `character varying(50)` | NOT NULL | | +| `performed_by` | `character varying(100)` | NOT NULL | | +| `performed_at` | `timestamp with time zone` | NOT NULL; DEFAULT now() | | +| `target_type` | `character varying(100)` | | | +| `reason` | `text` | | | +| `metadata` | `jsonb` | | | +| `aggressiveness` | `numeric(4,3)` | | | +| `zone` | `character varying(20)` | | | +| `vocab_size_before` | `integer` | | | +| `vocab_size_after` | `integer` | | | + +**Table constraints:** + +- `CONSTRAINT vocabulary_history_action_check CHECK (((action)::text = ANY ((ARRAY['added'::character varying, 'merged'::character varying, 'pruned'::character varying, 'deprecated'::character varying, 'reactivated'::character varying])::text[])))` ### `vocabulary_suggestions` -LLM-assisted vocabulary curation suggestions - ADR-602 +LLM-assisted vocabulary curation suggestions - ADR-026 | Column | Type | Constraints | Description | |---|---|---|---| -| `id` | `SERIAL` | PK | | -| `relationship_type` | `VARCHAR(100)` | NOT NULL | | -| `suggestion_type` | `VARCHAR(50)` | NOT NULL; CHECK (suggestion_type IN ('alias', 'new_type')) | | -| `confidence` | `NUMERIC(3,2)` | NOT NULL; CHECK (confidence BETWEEN 0 AND 1) | | -| `suggested_canonical_type` | `VARCHAR(100)` | | | -| `suggested_category` | `VARCHAR(50)` | | | -| `suggested_description` | `TEXT` | | | -| `similar_types` | `JSONB` | | | -| `reasoning` | `TEXT` | | | -| `created_at` | `TIMESTAMPTZ` | NOT NULL; DEFAULT NOW() | | -| `reviewed` | `BOOLEAN` | DEFAULT FALSE | | -| `curator_decision` | `VARCHAR(50)` | | | -| `curator_notes` | `TEXT` | | | +| `id` | `integer` | PK; NOT NULL | | +| `relationship_type` | `character varying(100)` | NOT NULL | | +| `suggestion_type` | `character varying(50)` | NOT NULL | | +| `confidence` | `numeric(3,2)` | NOT NULL | | +| `suggested_canonical_type` | `character varying(100)` | | | +| `suggested_category` | `character varying(50)` | | | +| `suggested_description` | `text` | | | +| `similar_types` | `jsonb` | | | +| `reasoning` | `text` | | | +| `created_at` | `timestamp with time zone` | NOT NULL; DEFAULT now() | | +| `reviewed` | `boolean` | DEFAULT false | | +| `curator_decision` | `character varying(50)` | | | +| `curator_notes` | `text` | | | + +**Table constraints:** + +- `CONSTRAINT vocabulary_suggestions_confidence_check CHECK (((confidence >= (0)::numeric) AND (confidence <= (1)::numeric)))` +- `CONSTRAINT vocabulary_suggestions_suggestion_type_check CHECK (((suggestion_type)::text = ANY ((ARRAY['alias'::character varying, 'new_type'::character varying])::text[])))` ### `worker_lanes` @@ -698,54 +865,78 @@ Worker lane configuration for database-driven job dispatch (ADR-100) | Column | Type | Constraints | Description | |---|---|---|---| -| `name` | `TEXT` | PK | | -| `job_types` | `TEXT[]` | NOT NULL | | -| `max_slots` | `INTEGER` | NOT NULL; DEFAULT 1 | | -| `poll_interval_ms` | `INTEGER` | NOT NULL; DEFAULT 5000 | | -| `stale_timeout_minutes` | `INTEGER` | NOT NULL; DEFAULT 30 | | -| `enabled` | `BOOLEAN` | NOT NULL; DEFAULT TRUE | | -| `updated_at` | `TIMESTAMPTZ` | NOT NULL; DEFAULT NOW() | | +| `name` | `text` | PK; NOT NULL | | +| `job_types` | `text[]` | NOT NULL | | +| `max_slots` | `integer` | NOT NULL; DEFAULT 1 | | +| `poll_interval_ms` | `integer` | NOT NULL; DEFAULT 5000 | | +| `stale_timeout_minutes` | `integer` | NOT NULL; DEFAULT 30 | | +| `enabled` | `boolean` | NOT NULL; DEFAULT true | | +| `updated_at` | `timestamp with time zone` | NOT NULL; DEFAULT now() | | ### `worker_status` | Column | Type | Constraints | Description | |---|---|---|---| -| `worker_id` | `VARCHAR(100)` | PK | | -| `last_heartbeat` | `TIMESTAMPTZ` | NOT NULL; DEFAULT NOW() | | -| `current_job_id` | `VARCHAR(50)` | | | -| `status` | `VARCHAR(50)` | NOT NULL; CHECK (status IN ('idle', 'running', 'error', 'stopped')) | | -| `metadata` | `JSONB` | | | +| `worker_id` | `character varying(100)` | PK; NOT NULL | | +| `last_heartbeat` | `timestamp with time zone` | NOT NULL; DEFAULT now() | | +| `current_job_id` | `character varying(50)` | | | +| `status` | `character varying(50)` | NOT NULL | | +| `metadata` | `jsonb` | | | + +**Table constraints:** + +- `CONSTRAINT worker_status_status_check CHECK (((status)::text = ANY ((ARRAY['idle'::character varying, 'running'::character varying, 'error'::character varying, 'stopped'::character varying])::text[])))` ## `kg_auth` Authentication and authorization (dynamic RBAC). -### `api_keys` - -| Column | Type | Constraints | Description | -|---|---|---|---| -| `id` | `SERIAL` | PK | | -| `key_hash` | `VARCHAR(255)` | UNIQUE; NOT NULL | | -| `user_id` | `INTEGER` | FK → kg_auth.users(id) | | -| `name` | `VARCHAR(200)` | | | -| `scopes` | `TEXT[]` | | | -| `created_at` | `TIMESTAMPTZ` | NOT NULL; DEFAULT NOW() | | -| `last_used` | `TIMESTAMPTZ` | | | -| `expires_at` | `TIMESTAMPTZ` | | | +#### Relationships + +```mermaid +erDiagram + users ||--o{ groups : "created_by" + oauth_clients ||--o{ oauth_access_tokens : "client_id" + users ||--o{ oauth_access_tokens : "user_id" + oauth_clients ||--o{ oauth_authorization_codes : "client_id" + users ||--o{ oauth_authorization_codes : "user_id" + users ||--o{ oauth_clients : "created_by" + oauth_clients ||--o{ oauth_device_codes : "client_id" + users ||--o{ oauth_device_codes : "user_id" + users ||--o{ oauth_external_provider_tokens : "user_id" + oauth_access_tokens ||--o{ oauth_refresh_tokens : "access_token_hash" + oauth_clients ||--o{ oauth_refresh_tokens : "client_id" + users ||--o{ oauth_refresh_tokens : "user_id" + users ||--o{ resource_grants : "granted_by" + resources ||--o{ resources : "parent_type" + resources ||--o{ role_permissions : "resource_type" + roles ||--o{ role_permissions : "inherited_from" + roles ||--o{ role_permissions : "role_name" + users ||--o{ role_permissions : "created_by" + roles ||--o{ roles : "parent_role" + users ||--o{ roles : "created_by" + groups ||--o{ user_groups : "group_id" + users ||--o{ user_groups : "added_by" + users ||--o{ user_groups : "user_id" + roles ||--o{ user_roles : "role_name" + users ||--o{ user_roles : "assigned_by" + users ||--o{ user_roles : "user_id" + roles ||--o{ users : "primary_role" +``` ### `groups` -Group definitions for collaborative access control (ADR-410) +Group definitions for collaborative access control (ADR-082) | Column | Type | Constraints | Description | |---|---|---|---| -| `id` | `INTEGER` | PK | | -| `group_name` | `VARCHAR(100)` | UNIQUE; NOT NULL | | -| `display_name` | `VARCHAR(200)` | | | -| `description` | `TEXT` | | | -| `is_system` | `BOOLEAN` | DEFAULT FALSE | System groups (public, admins) cannot be deleted | -| `created_at` | `TIMESTAMPTZ` | NOT NULL; DEFAULT NOW() | | -| `created_by` | `INTEGER` | FK → kg_auth.users(id) | | +| `id` | `integer` | PK; NOT NULL; DEFAULT nextval('kg_auth.groups_id_seq'::regclass) | | +| `group_name` | `character varying(100)` | NOT NULL; UNIQUE | | +| `display_name` | `character varying(200)` | | | +| `description` | `text` | | | +| `is_system` | `boolean` | DEFAULT false | System groups (public, admins) cannot be deleted | +| `created_at` | `timestamp with time zone` | NOT NULL; DEFAULT now() | | +| `created_by` | `integer` | FK → kg_auth.users(id) | | ### `oauth_access_tokens` @@ -753,11 +944,13 @@ OAuth access tokens issued to clients | Column | Type | Constraints | Description | |---|---|---|---| -| `token_hash` | `VARCHAR(255)` | PK | SHA256 hash of the actual token (tokens are not stored in plaintext) | -| `client_id` | `VARCHAR(255)` | NOT NULL; FK → kg_auth.oauth_clients(client_id) | | -| `user_id` | `INTEGER` | FK → kg_auth.users(id) | NULL for client_credentials grant (machine-to-machine), set for user-delegated grants | -| `expires_at` | `TIMESTAMPTZ` | NOT NULL | Access tokens expire in 1 hour | -| `created_at` | `TIMESTAMPTZ` | DEFAULT NOW() | | +| `token_hash` | `character varying(255)` | PK; NOT NULL | SHA256 hash of the actual token (tokens are not stored in plaintext) | +| `client_id` | `character varying(255)` | NOT NULL; FK → kg_auth.oauth_clients(client_id) | | +| `user_id` | `integer` | FK → kg_auth.users(id) | NULL for client_credentials grant (machine-to-machine), set for user-delegated grants | +| `scopes` | `text[]` | | | +| `expires_at` | `timestamp with time zone` | NOT NULL | Access tokens expire in 1 hour | +| `revoked` | `boolean` | DEFAULT false | | +| `created_at` | `timestamp with time zone` | DEFAULT now() | | ### `oauth_authorization_codes` @@ -765,14 +958,20 @@ Temporary authorization codes for OAuth Authorization Code flow (web apps) | Column | Type | Constraints | Description | |---|---|---|---| -| `code` | `VARCHAR(255)` | PK | | -| `client_id` | `VARCHAR(255)` | NOT NULL; FK → kg_auth.oauth_clients(client_id) | | -| `user_id` | `INTEGER` | NOT NULL; FK → kg_auth.users(id) | | -| `redirect_uri` | `TEXT` | NOT NULL | | -| `scopes` | `TEXT[]` | | | -| `code_challenge` | `VARCHAR(255)` | | PKCE code challenge (hash of code verifier) | -| `expires_at` | `TIMESTAMPTZ` | NOT NULL | Authorization codes expire in 10 minutes | -| `created_at` | `TIMESTAMPTZ` | DEFAULT NOW() | | +| `code` | `character varying(255)` | PK; NOT NULL | | +| `client_id` | `character varying(255)` | NOT NULL; FK → kg_auth.oauth_clients(client_id) | | +| `user_id` | `integer` | NOT NULL; FK → kg_auth.users(id) | | +| `redirect_uri` | `text` | NOT NULL | | +| `scopes` | `text[]` | | | +| `code_challenge` | `character varying(255)` | | PKCE code challenge (hash of code verifier) | +| `code_challenge_method` | `character varying(10)` | | | +| `expires_at` | `timestamp with time zone` | NOT NULL | Authorization codes expire in 10 minutes | +| `used` | `boolean` | DEFAULT false | | +| `created_at` | `timestamp with time zone` | DEFAULT now() | | + +**Table constraints:** + +- `CONSTRAINT oauth_authorization_codes_code_challenge_method_check CHECK (((code_challenge_method)::text = ANY ((ARRAY['S256'::character varying, 'plain'::character varying])::text[])))` ### `oauth_clients` @@ -780,14 +979,21 @@ OAuth 2.0 client applications registered to use the API | Column | Type | Constraints | Description | |---|---|---|---| -| `client_id` | `VARCHAR(255)` | PK | | -| `client_secret_hash` | `VARCHAR(255)` | | | -| `NULL` | `for` | NOT NULL | | -| `client_type` | `VARCHAR(50)` | NOT NULL; CHECK (client_type IN ('public', 'confidential')) | public = no client secret (CLI, web apps), confidential = has client secret (MCP server) | -| `grant_types` | `TEXT[]` | NOT NULL | Allowed OAuth grant types: authorization_code, urn:ietf:params:oauth:grant-type:device_code, client_credentials, refresh_token | -| `created_by` | `INTEGER` | FK → kg_auth.users(id) | | -| `created_at` | `TIMESTAMPTZ` | DEFAULT NOW() | | -| `metadata` | `JSONB` | DEFAULT '{}'::jsonb | | +| `client_id` | `character varying(255)` | PK; NOT NULL | | +| `client_secret_hash` | `character varying(255)` | | | +| `client_name` | `character varying(255)` | NOT NULL | | +| `client_type` | `character varying(50)` | NOT NULL | public = no client secret (CLI, web apps), confidential = has client secret (MCP server) | +| `grant_types` | `text[]` | NOT NULL | Allowed OAuth grant types: authorization_code, urn:ietf:params:oauth:grant-type:device_code, client_credentials, refresh_token | +| `redirect_uris` | `text[]` | | | +| `scopes` | `text[]` | | | +| `is_active` | `boolean` | DEFAULT true | | +| `created_by` | `integer` | FK → kg_auth.users(id) | | +| `created_at` | `timestamp with time zone` | DEFAULT now() | | +| `metadata` | `jsonb` | DEFAULT '{}'::jsonb | | + +**Table constraints:** + +- `CONSTRAINT oauth_clients_client_type_check CHECK (((client_type)::text = ANY ((ARRAY['public'::character varying, 'confidential'::character varying])::text[])))` ### `oauth_device_codes` @@ -795,149 +1001,158 @@ Device authorization codes for OAuth Device Authorization Grant flow (CLI tools) | Column | Type | Constraints | Description | |---|---|---|---| -| `device_code` | `VARCHAR(255)` | PK | Long code used by device for polling | -| `user_code` | `VARCHAR(50)` | UNIQUE; NOT NULL | Human-friendly code displayed to user (e.g., ABCD-1234) | -| `user_id` | `INTEGER` | FK → kg_auth.users(id) | | -| `status` | `VARCHAR(50)` | DEFAULT 'pending'; CHECK (status IN ('pending', 'authorized', 'denied', 'expired')) | | -| `expires_at` | `TIMESTAMPTZ` | NOT NULL | Device codes expire in 10 minutes | +| `device_code` | `character varying(255)` | PK; NOT NULL | Long code used by device for polling | +| `user_code` | `character varying(50)` | NOT NULL; UNIQUE | Human-friendly code displayed to user (e.g., ABCD-1234) | +| `client_id` | `character varying(255)` | NOT NULL; FK → kg_auth.oauth_clients(client_id) | | +| `user_id` | `integer` | FK → kg_auth.users(id) | | +| `scopes` | `text[]` | | | +| `status` | `character varying(50)` | DEFAULT 'pending'::character varying | | +| `expires_at` | `timestamp with time zone` | NOT NULL | Device codes expire in 10 minutes | +| `created_at` | `timestamp with time zone` | DEFAULT now() | | -### `oauth_refresh_tokens` +**Table constraints:** -OAuth refresh tokens for long-lived sessions +- `CONSTRAINT oauth_device_codes_status_check CHECK (((status)::text = ANY ((ARRAY['pending'::character varying, 'authorized'::character varying, 'denied'::character varying, 'expired'::character varying])::text[])))` + +### `oauth_external_provider_tokens` + +OAuth tokens FROM external providers (Google, GitHub, etc.) - not tokens issued by our system | Column | Type | Constraints | Description | |---|---|---|---| -| `token_hash` | `VARCHAR(255)` | PK | | -| `client_id` | `VARCHAR(255)` | NOT NULL; FK → kg_auth.oauth_clients(client_id) | | -| `user_id` | `INTEGER` | NOT NULL; FK → kg_auth.users(id) | | -| `scopes` | `TEXT[]` | | | -| `access_token_hash` | `VARCHAR(255)` | FK → kg_auth.oauth_access_tokens(token_hash) | | -| `expires_at` | `TIMESTAMPTZ` | NOT NULL | Refresh tokens expire in 7 days (CLI) or 30 days (web) | -| `created_at` | `TIMESTAMPTZ` | DEFAULT NOW() | | -| `last_used` | `TIMESTAMPTZ` | | Updated when refresh token is used to obtain new access token | +| `token_hash` | `character varying(255)` | PK; NOT NULL | | +| `user_id` | `integer` | FK → kg_auth.users(id) | | +| `provider` | `character varying(50)` | | | +| `scopes` | `text[]` | | | +| `expires_at` | `timestamp with time zone` | NOT NULL | | -### `oauth_tokens` +### `oauth_refresh_tokens` + +OAuth refresh tokens for long-lived sessions | Column | Type | Constraints | Description | |---|---|---|---| -| `token_hash` | `VARCHAR(255)` | PK | | -| `user_id` | `INTEGER` | FK → kg_auth.users(id) | | -| `provider` | `VARCHAR(50)` | | | -| `scopes` | `TEXT[]` | | | -| `expires_at` | `TIMESTAMPTZ` | NOT NULL | | +| `token_hash` | `character varying(255)` | PK; NOT NULL | | +| `client_id` | `character varying(255)` | NOT NULL; FK → kg_auth.oauth_clients(client_id) | | +| `user_id` | `integer` | NOT NULL; FK → kg_auth.users(id) | | +| `scopes` | `text[]` | | | +| `access_token_hash` | `character varying(255)` | FK → kg_auth.oauth_access_tokens(token_hash) | | +| `expires_at` | `timestamp with time zone` | NOT NULL | Refresh tokens expire in 7 days (CLI) or 30 days (web) | +| `revoked` | `boolean` | DEFAULT false | | +| `created_at` | `timestamp with time zone` | DEFAULT now() | | +| `last_used` | `timestamp with time zone` | | Updated when refresh token is used to obtain new access token | ### `resource_grants` -Instance-level access grants for owned resources (ADR-410) +Instance-level access grants for owned resources (ADR-082) | Column | Type | Constraints | Description | |---|---|---|---| -| `id` | `SERIAL` | PK | | -| `resource_type` | `VARCHAR(50)` | NOT NULL | Type: ontology, artifact, report, etc. | -| `resource_id` | `VARCHAR(200)` | NOT NULL | Specific resource identifier | -| `principal_type` | `VARCHAR(20)` | NOT NULL; CHECK (principal_type IN ('user', 'group')) | Grant to user or group | -| `principal_id` | `INTEGER` | NOT NULL | | -| `permission` | `VARCHAR(20)` | NOT NULL; CHECK (permission IN ('read', 'write', 'admin')) | read, write, or admin access | -| `granted_at` | `TIMESTAMPTZ` | NOT NULL; DEFAULT NOW() | | -| `granted_by` | `INTEGER` | FK → kg_auth.users(id) | | +| `id` | `integer` | PK; NOT NULL | | +| `resource_type` | `character varying(50)` | NOT NULL | Type: ontology, artifact, report, etc. | +| `resource_id` | `character varying(200)` | NOT NULL | Specific resource identifier | +| `principal_type` | `character varying(20)` | NOT NULL | Grant to user or group | +| `principal_id` | `integer` | NOT NULL | | +| `permission` | `character varying(20)` | NOT NULL | read, write, or admin access | +| `granted_at` | `timestamp with time zone` | NOT NULL; DEFAULT now() | | +| `granted_by` | `integer` | FK → kg_auth.users(id) | | **Table constraints:** -- `UNIQUE(resource_type, resource_id, principal_type, principal_id, permission)` +- `CONSTRAINT resource_grants_permission_check CHECK (((permission)::text = ANY ((ARRAY['read'::character varying, 'write'::character varying, 'admin'::character varying])::text[])))` +- `CONSTRAINT resource_grants_principal_type_check CHECK (((principal_type)::text = ANY ((ARRAY['user'::character varying, 'group'::character varying])::text[])))` +- `UNIQUE (resource_type, resource_id, principal_type, principal_id, permission)` ### `resources` -Dynamic resource type registry (ADR-404) +Dynamic resource type registry (ADR-028) | Column | Type | Constraints | Description | |---|---|---|---| -| `resource_type` | `VARCHAR(100)` | PK | | -| `description` | `TEXT` | | | -| `parent_type` | `VARCHAR(100)` | FK → kg_auth.resources(resource_type) | | -| `available_actions` | `VARCHAR(50)[]` | NOT NULL | | -| `supports_scoping` | `BOOLEAN` | DEFAULT FALSE | | -| `metadata` | `JSONB` | DEFAULT '{}' | | -| `registered_at` | `TIMESTAMPTZ` | NOT NULL; DEFAULT NOW() | | -| `registered_by` | `VARCHAR(100)` | | | +| `resource_type` | `character varying(100)` | PK; NOT NULL | | +| `description` | `text` | | | +| `parent_type` | `character varying(100)` | FK → kg_auth.resources(resource_type) | | +| `available_actions` | `character varying(50)[]` | NOT NULL | | +| `supports_scoping` | `boolean` | DEFAULT false | | +| `metadata` | `jsonb` | DEFAULT '{}'::jsonb | | +| `registered_at` | `timestamp with time zone` | NOT NULL; DEFAULT now() | | +| `registered_by` | `character varying(100)` | | | ### `role_permissions` -Dynamic role permissions with scoping (ADR-404) +Dynamic role permissions with scoping (ADR-028) | Column | Type | Constraints | Description | |---|---|---|---| -| `id` | `SERIAL` | PK | | -| `role_name` | `VARCHAR(50)` | NOT NULL; FK → kg_auth.roles(role_name) | | -| `resource_type` | `VARCHAR(100)` | NOT NULL; FK → kg_auth.resources(resource_type) | | -| `action` | `VARCHAR(50)` | NOT NULL | | -| `scope_type` | `VARCHAR(50)` | | | -| `scope_id` | `VARCHAR(200)` | | | -| `scope_filter` | `JSONB` | | | -| `granted` | `BOOLEAN` | NOT NULL; DEFAULT TRUE | | -| `inherited_from` | `VARCHAR(50)` | FK → kg_auth.roles(role_name) | | -| `created_at` | `TIMESTAMPTZ` | NOT NULL; DEFAULT NOW() | | -| `created_by` | `INTEGER` | FK → kg_auth.users(id) | | +| `id` | `integer` | PK; NOT NULL | | +| `role_name` | `character varying(50)` | NOT NULL; FK → kg_auth.roles(role_name) | | +| `resource_type` | `character varying(100)` | NOT NULL; FK → kg_auth.resources(resource_type) | | +| `action` | `character varying(50)` | NOT NULL | | +| `scope_type` | `character varying(50)` | | | +| `scope_id` | `character varying(200)` | | | +| `scope_filter` | `jsonb` | | | +| `granted` | `boolean` | NOT NULL; DEFAULT true | | +| `inherited_from` | `character varying(50)` | FK → kg_auth.roles(role_name) | | +| `created_at` | `timestamp with time zone` | NOT NULL; DEFAULT now() | | +| `created_by` | `integer` | FK → kg_auth.users(id) | | ### `roles` -Dynamic role definitions with inheritance (ADR-404) +Dynamic role definitions with inheritance (ADR-028) | Column | Type | Constraints | Description | |---|---|---|---| -| `role_name` | `VARCHAR(50)` | PK | | -| `display_name` | `VARCHAR(100)` | NOT NULL | | -| `description` | `TEXT` | | | -| `is_builtin` | `BOOLEAN` | DEFAULT FALSE | | -| `is_active` | `BOOLEAN` | DEFAULT TRUE | | -| `parent_role` | `VARCHAR(50)` | FK → kg_auth.roles(role_name) | | -| `created_at` | `TIMESTAMPTZ` | NOT NULL; DEFAULT NOW() | | -| `created_by` | `INTEGER` | | | +| `role_name` | `character varying(50)` | PK; NOT NULL | | +| `display_name` | `character varying(100)` | NOT NULL | | +| `description` | `text` | | | +| `is_builtin` | `boolean` | DEFAULT false | | +| `is_active` | `boolean` | DEFAULT true | | +| `parent_role` | `character varying(50)` | FK → kg_auth.roles(role_name) | | +| `created_at` | `timestamp with time zone` | NOT NULL; DEFAULT now() | | +| `created_by` | `integer` | FK → kg_auth.users(id) | | +| `metadata` | `jsonb` | DEFAULT '{}'::jsonb | | ### `user_groups` -Group membership assignments (ADR-410) +Group membership assignments (ADR-082) | Column | Type | Constraints | Description | |---|---|---|---| -| `user_id` | `INTEGER` | NOT NULL; FK → kg_auth.users(id) | | -| `group_id` | `INTEGER` | NOT NULL; FK → kg_auth.groups(id) | | -| `added_at` | `TIMESTAMPTZ` | NOT NULL; DEFAULT NOW() | | -| `added_by` | `INTEGER` | FK → kg_auth.users(id) | | - -**Table constraints:** - -- `PRIMARY KEY (user_id, group_id)` +| `user_id` | `integer` | PK; NOT NULL; FK → kg_auth.users(id) | | +| `group_id` | `integer` | PK; NOT NULL; FK → kg_auth.groups(id) | | +| `added_at` | `timestamp with time zone` | NOT NULL; DEFAULT now() | | +| `added_by` | `integer` | FK → kg_auth.users(id) | | ### `user_roles` -User role assignments with optional scoping (ADR-404) +User role assignments with optional scoping (ADR-028) | Column | Type | Constraints | Description | |---|---|---|---| -| `id` | `SERIAL` | PK | | -| `user_id` | `INTEGER` | NOT NULL; FK → kg_auth.users(id) | | -| `role_name` | `VARCHAR(50)` | NOT NULL; FK → kg_auth.roles(role_name) | | -| `scope_type` | `VARCHAR(50)` | | | -| `scope_id` | `VARCHAR(200)` | | | -| `assigned_at` | `TIMESTAMPTZ` | NOT NULL; DEFAULT NOW() | | -| `assigned_by` | `INTEGER` | FK → kg_auth.users(id) | | -| `expires_at` | `TIMESTAMPTZ` | | | +| `id` | `integer` | PK; NOT NULL | | +| `user_id` | `integer` | NOT NULL; FK → kg_auth.users(id) | | +| `role_name` | `character varying(50)` | NOT NULL; FK → kg_auth.roles(role_name) | | +| `scope_type` | `character varying(50)` | | | +| `scope_id` | `character varying(200)` | | | +| `assigned_at` | `timestamp with time zone` | NOT NULL; DEFAULT now() | | +| `assigned_by` | `integer` | FK → kg_auth.users(id) | | +| `expires_at` | `timestamp with time zone` | | | **Table constraints:** -- `UNIQUE(user_id, role_name, scope_type, scope_id)` +- `UNIQUE (user_id, role_name, scope_type, scope_id)` ### `users` | Column | Type | Constraints | Description | |---|---|---|---| -| `id` | `SERIAL` | PK | | -| `username` | `VARCHAR(100)` | UNIQUE; NOT NULL | | -| `password_hash` | `VARCHAR(255)` | NOT NULL | | -| `primary_role` | `VARCHAR(50)` | NOT NULL; FK → kg_auth.roles(role_name) | Primary role (backwards compatibility) - user can have additional roles in user_roles table | -| `created_at` | `TIMESTAMPTZ` | NOT NULL; DEFAULT NOW() | | -| `last_login` | `TIMESTAMPTZ` | | | -| `disabled` | `BOOLEAN` | DEFAULT FALSE | | +| `id` | `integer` | PK; NOT NULL | | +| `username` | `character varying(100)` | NOT NULL; UNIQUE | | +| `password_hash` | `character varying(255)` | NOT NULL | | +| `primary_role` | `character varying(50)` | NOT NULL; FK → kg_auth.roles(role_name) | Primary role (backwards compatibility) - user can have additional roles in user_roles table | +| `created_at` | `timestamp with time zone` | NOT NULL; DEFAULT now() | | +| `last_login` | `timestamp with time zone` | | | +| `disabled` | `boolean` | DEFAULT false | | ## `kg_logs` @@ -947,129 +1162,62 @@ Observability: audit trails, metrics, health. | Column | Type | Constraints | Description | |---|---|---|---| -| `id` | `SERIAL` | PK | | -| `timestamp` | `TIMESTAMPTZ` | NOT NULL; DEFAULT NOW() | | -| `endpoint` | `VARCHAR(200)` | NOT NULL | | -| `method` | `VARCHAR(10)` | NOT NULL | | -| `status_code` | `INTEGER` | NOT NULL | | -| `duration_ms` | `NUMERIC(10,2)` | NOT NULL | | -| `client_id` | `VARCHAR(100)` | | | -| `error_message` | `TEXT` | | | +| `id` | `integer` | PK; NOT NULL | | +| `timestamp` | `timestamp with time zone` | NOT NULL; DEFAULT now() | | +| `endpoint` | `character varying(200)` | NOT NULL | | +| `method` | `character varying(10)` | NOT NULL | | +| `status_code` | `integer` | NOT NULL | | +| `duration_ms` | `numeric(10,2)` | NOT NULL | | +| `client_id` | `character varying(100)` | | | +| `error_message` | `text` | | | ### `audit_trail` | Column | Type | Constraints | Description | |---|---|---|---| -| `id` | `SERIAL` | PK | | -| `timestamp` | `TIMESTAMPTZ` | NOT NULL; DEFAULT NOW() | | -| `user_id` | `INTEGER` | | | -| `action` | `VARCHAR(100)` | NOT NULL | | -| `resource_type` | `VARCHAR(50)` | NOT NULL | | -| `resource_id` | `VARCHAR(200)` | | | -| `details` | `JSONB` | | | -| `ip_address` | `INET` | | | -| `user_agent` | `TEXT` | | | -| `outcome` | `VARCHAR(50)` | NOT NULL; CHECK (outcome IN ('success', 'denied', 'error')) | | +| `id` | `integer` | PK; NOT NULL | | +| `timestamp` | `timestamp with time zone` | NOT NULL; DEFAULT now() | | +| `user_id` | `integer` | | | +| `action` | `character varying(100)` | NOT NULL | | +| `resource_type` | `character varying(50)` | NOT NULL | | +| `resource_id` | `character varying(200)` | | | +| `details` | `jsonb` | | | +| `ip_address` | `inet` | | | +| `user_agent` | `text` | | | +| `outcome` | `character varying(50)` | NOT NULL | | + +**Table constraints:** + +- `CONSTRAINT audit_trail_outcome_check CHECK (((outcome)::text = ANY ((ARRAY['success'::character varying, 'denied'::character varying, 'error'::character varying])::text[])))` ### `health_checks` | Column | Type | Constraints | Description | |---|---|---|---| -| `id` | `SERIAL` | PK | | -| `timestamp` | `TIMESTAMPTZ` | NOT NULL; DEFAULT NOW() | | -| `service` | `VARCHAR(50)` | NOT NULL | | -| `status` | `VARCHAR(50)` | NOT NULL; CHECK (status IN ('healthy', 'degraded', 'down')) | | -| `metrics` | `JSONB` | | | +| `id` | `integer` | PK; NOT NULL | | +| `timestamp` | `timestamp with time zone` | NOT NULL; DEFAULT now() | | +| `service` | `character varying(50)` | NOT NULL | | +| `status` | `character varying(50)` | NOT NULL | | +| `metrics` | `jsonb` | | | + +**Table constraints:** + +- `CONSTRAINT health_checks_status_check CHECK (((status)::text = ANY ((ARRAY['healthy'::character varying, 'degraded'::character varying, 'down'::character varying])::text[])))` ### `job_events` | Column | Type | Constraints | Description | |---|---|---|---| -| `id` | `SERIAL` | PK | | -| `job_id` | `VARCHAR(50)` | NOT NULL | | -| `timestamp` | `TIMESTAMPTZ` | NOT NULL; DEFAULT NOW() | | -| `event_type` | `VARCHAR(50)` | NOT NULL | | -| `details` | `JSONB` | | | +| `id` | `integer` | PK; NOT NULL | | +| `job_id` | `character varying(50)` | NOT NULL | | +| `timestamp` | `timestamp with time zone` | NOT NULL; DEFAULT now() | | +| `event_type` | `character varying(50)` | NOT NULL | | +| `details` | `jsonb` | | | ## Migration history -Schema evolves through numbered migrations under `schema/migrations/`. Each is recorded in `public.schema_migrations` when applied. The baseline (`00_baseline.sql`) is the consolidated starting point; migrations after it are applied in order. +Schema evolves through numbered migrations under `schema/migrations/`. Each is recorded in `public.schema_migrations` when applied. The baseline (`00_baseline.sql`) is a generated checkpoint consolidating migrations 001–080 (see `schema/migrations/archived/`); migrations after it are applied in order. + +No post-checkpoint migrations yet — the baseline is the complete current schema. -| # | Migration | ADRs | Description | -|---|---|---|---| -| 1 | baseline | ADR-601, ADR-602, ADR-404, ADR-603, ADR-804, ADR-210 | Baseline schema snapshot (v2.0.0) | -| 3 | add embedding config | ADR-804 | Add resource-aware embedding configuration table for local/remote models | -| 4 | add ai extraction config | ADR-805 | Add AI extraction provider configuration table for runtime-switchable models | -| 5 | add api key validation | ADR-405, ADR-805 | Create system_api_keys table with validation state tracking | -| 6 | add embedding config protection | ADR-804 | Add delete and change protection flags to embedding configurations | -| 7 | add local extraction providers | ADR-806 | Extend AI extraction config to support local inference providers (Ollama, vLLM) | -| 8 | add nomic embedding preset | ADR-804 | Add nomic-embed-text-v1.5 as a pre-configured local embedding option | -| 9 | add thinking mode | ADR-806 | Add thinking_mode parameter for Ollama reasoning models | -| 11 | add grounding metrics | ADR-808, ADR-604 | Add grounding-aware metrics to vocabulary table for ADR-808/046 | -| 12 | add embedding worker support | ADR-809 | Add infrastructure for unified embedding generation (ADR-809) | -| 13 | Add Schema Version Tracking | ADR-107 | Adds schema_migrations table to track applied migrations | -| 14 | Vocabulary as Graph Nodes (ADR-606 Phase 3) | ADR-605, ADR-606 | | -| 15 | Probabilistic Vocabulary Categorization (ADR-605) | ADR-808, ADR-809, ADR-605 | Adds fields for probabilistic category assignment using embedding similarity. | -| 16 | LLM-Determined Relationship Direction Semantics (ADR-810) | ADR-605, ADR-606, ADR-810 | Adds direction_semantics field for LLM-determined relationship directionality. | -| 17 | Vocabulary Configuration System | | | -| 18 | add rate limiting config | | Add rate limiting and concurrency configuration for AI providers | -| 19 | Scheduled Jobs System | ADR-111 | | -| 20 | Track Authenticated Users in Jobs | ADR-403, ADR-404, ADR-410 | | -| 21 | Graph-Based Provenance Tracking | ADR-300, ADR-808, ADR-304 | | -| 22 | OAuth 2.0 Client Management | ADR-406 | Implements OAuth 2.0 flows (Authorization Code + PKCE, Device Authorization Grant, Client Credentials) | -| 23 | Image Storage Source Properties (ADR-305) | ADR-305 | Extends Source nodes to support multimodal image ingestion with S3-compatible | -| 24 | Add Description Field to Concept Nodes | | | -| 25 | Graph Metrics Table | | Track graph change counters to trigger periodic epistemic status measurement | -| 26 | Add epistemic re-measurement scheduled job | ADR-610 | | -| 27 | source text embeddings | ADR-812 | Add source text embeddings with offset tracking and hash verification (ADR-812) | -| 28 | Platform Admin Resources and Role (ADR-409) | ADR-409 | | -| 29 | Fix Role Hierarchy (ADR-409) | ADR-409 | | -| 30 | Add 'create' action to users resource type | ADR-409 | | -| 31 | platform config | | Platform lifecycle configuration for operator-as-control-plane pattern | -| 32 | Add projection refresh scheduled job | ADR-717 | | -| 33 | Graph Metrics Snapshot Refresh | ADR-114 | Provide reliable graph change detection for projection cache invalidation | -| 34 | User Scoping and Groups (ADR-410) | ADR-410 | | -| 35 | Artifact Persistence (ADR-116) | ADR-410, ADR-116 | | -| 36 | Job Artifact Linking (ADR-116 Phase 4) | ADR-116 | | -| 37 | Grant admin role backup create/restore permissions | | | -| 38 | Upgrade admin user to platform_admin | ADR-409 | | -| 39 | Add metadata column to query_definitions | | | -| 40 | Graph CRUD Permissions (ADR-308) | ADR-308 | | -| 41 | Job RBAC Permissions | | | -| 42 | Storage Admin RBAC Permissions | | | -| 43 | Backfill content_type and storage_key on DocumentMeta Nodes | | | -| 44 | Ontology Graph Nodes (ADR-200 Phase 1) | ADR-200 | | -| 45 | Ontology Lifecycle Permissions (ADR-200 Phase 2) | ADR-200 | | -| 46 | Annealing Proposals (ADR-200 Phase 3b) | ADR-200 | | -| 47 | Annealing cycle infrastructure | ADR-200 | | -| 48 | Ontology-to-Ontology Edge Types (ADR-200 Phase 5) | ADR-200 | | -| 49 | Proposal Execution Schema (ADR-200 Phase 4) | ADR-200 | | -| 50 | Add 'exploration' to query_definitions definition_type | | | -| 51 | Create graph_accel extension (if available) | ADR-201 | | -| 52 | Add 'program' to query_definitions definition_type | ADR-500 | | -| 53 | Change annealing automation_level default to autonomous | | | -| 54 | Add delete action to sources resource | | | -| 55 | embedding profile | ADR-804 | Unified embedding profile with text + image model slots | -| 56 | embedding task prefix | | Add task prefix columns to embedding_profile for purpose-aware embedding | -| 57 | Worker lanes and database-driven job dispatch (ADR-100) | ADR-100 | | -| 59 | Provider model catalog and OpenRouter support (ADR-800) | ADR-800 | | -| 60 | Remove hardcoded Ollama catalog seeds (ADR-800) | ADR-800 | | -| 61 | Allow llama.cpp as an extraction provider | ADR-800 | | -| 62 | ai_extraction_config becomes one row PER PROVIDER (ADR-800 / #8) | ADR-800 | | -| 63 | Graph epoch event log (ADR-203) | ADR-114, ADR-203 | | -| 64 | Replace graph_epochs.kind CHECK constraint with a lookup table. | ADR-203 | | -| 65 | Raise annealing cadence floors (#402 Defect C) | | | -| 66 | Ontology tombstones (#402 Defect B2) | | | -| 67 | Annealing Vocabulary v2 (ADR-206) | ADR-200, ADR-206 | | -| 68 | Annealing Pressure History (#249, ADR-206 §Phase 3) | ADR-200, ADR-206 | | -| 69 | Vocabulary embedding lifecycle (#420 follow-up) | | Track vocab embedding generation events independently from vocab membership, | -| 70 | Schedule the VocabEmbeddingLauncher | | | -| 71 | Open-registration feature flag (ADR-400, internet-hardening #431) | ADR-400 | | -| 72 | Grant admin oauth_clients:write (ADR-400, internet-hardening #441) | ADR-400 | | -| 73 | Catalog Browse Index + RBAC (ADR-501) | ADR-812, ADR-200, ADR-203, ADR-501 | | -| 74 | ai_vision_config — active vision provider selection (ADR-802 / #378) | ADR-801, ADR-802 | | -| 75 | decouple the image embedding slot from the text vector space | ADR-305, ADR-803 | | -| 76 | Trustworthy freshness clock (ADR-207, #384) | ADR-114, ADR-203, ADR-207 | | -| 77 | Add the 'restore' graph-epoch kind (ADR-102 P5) | ADR-102, ADR-207 | | -| 78 | seed primordial ontology | ADR-200 | Seed the reserved 'primordial' ontology pool on clean install (#505) | diff --git a/schema/scripts/generate-schema-docs.py b/schema/scripts/generate-schema-docs.py index 6174e7d89..69ef88cdd 100755 --- a/schema/scripts/generate-schema-docs.py +++ b/schema/scripts/generate-schema-docs.py @@ -115,6 +115,26 @@ def split_top_level_commas(body: str): re.IGNORECASE, ) +# Column type: multi-word SQL-standard names first (pg_dump spells these out — +# "character varying(255)", "timestamp without time zone"), then bare words. +COLUMN_TYPE = re.compile( + r"^((?:character\s+varying|bit\s+varying|double\s+precision|" + r"timestamp(?:\s*\(\d+\))?\s+(?:with|without)\s+time\s+zone|" + r"time(?:\s*\(\d+\))?\s+(?:with|without)\s+time\s+zone|" + r"[A-Za-z_][\w]*)" + r"(?:\s*\([\d,\s]*\))?(?:\[\])?)", + re.IGNORECASE, +) + +# ALTER TABLE ... ADD CONSTRAINT — how pg_dump emits PKs, FKs, and UNIQUEs +# (the checkpoint baseline is pg_dump output, so constraints live here, not +# inline in CREATE TABLE). +ALTER_CONSTRAINT = re.compile( + r"ALTER\s+TABLE\s+(?:ONLY\s+)?([A-Za-z_][\w.]*)\s+" + r"ADD\s+CONSTRAINT\s+[A-Za-z_]\w*\s+(.+?);", + re.IGNORECASE | re.DOTALL, +) + def parse_columns(body: str): """Parse a table body into a list of column dicts and a constraint list. @@ -141,10 +161,7 @@ def parse_columns(body: str): rest_norm = " ".join(rest.split()) # Column type is everything up to the first attribute keyword. - type_match = re.match( - r"^([A-Za-z_][\w]*(?:\s*\([\d,\s]*\))?(?:\[\])?)", - rest_norm, - ) + type_match = COLUMN_TYPE.match(rest_norm) col_type = type_match.group(1).strip() if type_match else rest_norm attrs = rest_norm[len(col_type):].strip() if type_match else "" @@ -187,6 +204,56 @@ def parse_columns(body: str): return columns, constraints +def apply_alter_constraints(sql: str, tables: dict): + """Fold ALTER TABLE ... ADD CONSTRAINT statements into *tables*. + + pg_dump emits primary keys, foreign keys, and unique constraints as + separate ALTER TABLE statements after the CREATE TABLEs. Single-column + constraints become column flags (PK / UNIQUE / FK → target); everything + else lands in the table's constraint list. + """ + for m in ALTER_CONSTRAINT.finditer(sql): + qualified = m.group(1).strip('"') + definition = " ".join(m.group(2).split()) + tbl = tables.get(qualified) + if tbl is None: + continue + cols_by_name = {c["name"]: c for c in tbl["columns"]} + + pk = re.match(r"PRIMARY\s+KEY\s*\(([^)]*)\)", definition, re.IGNORECASE) + if pk: + for col in [c.strip().strip('"') for c in pk.group(1).split(",")]: + if col in cols_by_name: + cols_by_name[col]["flags"].insert(0, "PK") + continue + + uq = re.match(r"UNIQUE\s*\(([^)]*)\)", definition, re.IGNORECASE) + if uq: + cols = [c.strip().strip('"') for c in uq.group(1).split(",")] + if len(cols) == 1 and cols[0] in cols_by_name: + cols_by_name[cols[0]]["flags"].append("UNIQUE") + else: + tbl["constraints"].append(definition) + continue + + fk = re.match( + r"FOREIGN\s+KEY\s*\(([^)]*)\)\s*REFERENCES\s+" + r"([A-Za-z_][\w.]*\s*(?:\([^)]*\))?)", + definition, + re.IGNORECASE, + ) + if fk: + cols = [c.strip().strip('"') for c in fk.group(1).split(",")] + target = " ".join(fk.group(2).split()) + if len(cols) == 1 and cols[0] in cols_by_name: + cols_by_name[cols[0]]["flags"].append(f"FK → {target}") + else: + tbl["constraints"].append(definition) + continue + + tbl["constraints"].append(definition) + + def find_comments(sql: str): """Extract COMMENT ON TABLE/COLUMN statements from *sql*. @@ -347,6 +414,8 @@ def build_table_index(): "source": label, } + apply_alter_constraints(sql, tables) + tcs, ccs = find_comments(sql) all_table_comments.update(tcs) all_column_comments.update(ccs) @@ -354,6 +423,54 @@ def build_table_index(): return tables, all_table_comments, all_column_comments +FK_FLAG = re.compile(r"FK → ([\w.]+)") + + +def collect_fk_edges(tables: dict): + """Extract (child_qualified, parent_qualified, column) FK edges. + + Parents referenced without a schema prefix resolve against the child's + schema first, then public. + """ + edges = [] + for qualified, tbl in tables.items(): + for col in tbl["columns"]: + for flag in col["flags"]: + m = FK_FLAG.match(flag) + if not m: + continue + target = m.group(1).split("(")[0] + if "." not in target: + for cand in (f"{tbl['schema']}.{target}", f"public.{target}"): + if cand in tables: + target = cand + break + edges.append((qualified, target, col["name"])) + return edges + + +def render_er_diagram(schema: str, edges, tables) -> list: + """Render a mermaid erDiagram for FK edges whose child is in *schema*. + + Table names are unique across schemas today; entities use bare names so + the diagram stays readable (mermaid renders natively on GitHub and in + mkdocs-material via superfences). + """ + schema_edges = [ + (c, p, col) for c, p, col in edges + if tables.get(c, {}).get("schema") == schema and p in tables + ] + if not schema_edges: + return [] + out = ["#### Relationships", "", "```mermaid", "erDiagram"] + for child, parent, col in sorted(set(schema_edges)): + child_name = tables[child]["name"] + parent_name = tables[parent]["name"] + out.append(f' {parent_name} ||--o{{ {child_name} : "{col}"') + out.extend(["```", ""]) + return out + + def render(tables, table_comments, column_comments, migrations): """Render the full markdown page as a string.""" today = date.today().isoformat() @@ -408,6 +525,8 @@ def render(tables, table_comments, column_comments, migrations): out.append(f"| `{schema}` | {blurb} | {count} |") out.append("") + fk_edges = collect_fk_edges(tables) + # Per-schema, per-table detail. for schema in ordered_schemas: out.append(f"## `{schema}`") @@ -416,6 +535,7 @@ def render(tables, table_comments, column_comments, migrations): if blurb: out.append(blurb) out.append("") + out.extend(render_er_diagram(schema, fk_edges, tables)) for qualified, tbl in sorted(by_schema[schema], key=lambda x: x[1]["name"]): out.append(f"### `{tbl['name']}`") @@ -451,12 +571,21 @@ def render(tables, table_comments, column_comments, migrations): out.append( "Schema evolves through numbered migrations under " "`schema/migrations/`. Each is recorded in `public.schema_migrations` " - "when applied. The baseline (`00_baseline.sql`) is the consolidated " - "starting point; migrations after it are applied in order." + "when applied. The baseline (`00_baseline.sql`) is a generated " + "checkpoint consolidating migrations 001–080 (see " + "`schema/migrations/archived/`); migrations after it are applied " + "in order." ) out.append("") - out.append("| # | Migration | ADRs | Description |") - out.append("|---|---|---|---|") + if not migrations: + out.append( + "No post-checkpoint migrations yet — the baseline is the " + "complete current schema." + ) + out.append("") + else: + out.append("| # | Migration | ADRs | Description |") + out.append("|---|---|---|---|") for info in migrations: ver = info["version"] if info["version"] is not None else "" adrs = ", ".join(info["adrs"]) From eea13103d0f4c2d317e6ff3cc770042bc430e469 Mon Sep 17 00:00:00 2001 From: Aaron Bockelie Date: Wed, 1 Jul 2026 23:28:54 -0500 Subject: [PATCH 4/4] fix: address code review findings on PR #542 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - checkpoint.sh: GRAPH_SEED_MIGRATIONS now points into migrations/archived/ (this PR moved the files it referenced), and migration_files() filters unexpanded globs so replay works with an empty migrations/ directory. Validated end-to-end from the post-checkpoint repo state: replay picks up only warm 058 and the regenerated candidate verifies faithful. - migrate-db.sh: the skip-and-retry path was dead code — under set -e a bare command-substitution assignment aborts the script on psql failure before the exit code is examined. Capture it with an || arm. Matters now that pre-checkpoint databases apply batches of archived migrations. - 058 + baseline: label-creation failures escalate NOTICE -> WARNING so a future signature drift is visible instead of swallowed. - baseline seed-data section: re-inject the CHANGE-IN-PRODUCTION warning for development-default credentials (lost from migration 020's comment during consolidation); generator emits it for future re-pins. - generate-schema-docs.py: parse ALTER COLUMN ... SET DEFAULT nextval(...) (27 serial defaults were missing from the reference); disambiguate ER diagram entities whose names collide across schemas. Claude-Session: https://claude.ai/code/session_018VLKzWianGCKvY7CPbow8Z --- docs/reference/schema.md | 52 +++++++++---------- operator/database/migrate-db.sh | 8 +-- schema/00_baseline.sql | 10 ++-- .../058_precreate_graph_labels.sql | 6 +-- schema/scripts/checkpoint.sh | 21 +++++--- schema/scripts/generate-schema-docs.py | 41 ++++++++++++--- 6 files changed, 91 insertions(+), 47 deletions(-) diff --git a/docs/reference/schema.md b/docs/reference/schema.md index 0f31f5cc9..a3323c531 100644 --- a/docs/reference/schema.md +++ b/docs/reference/schema.md @@ -94,7 +94,7 @@ AI extraction provider configuration for runtime-switchable models - ADR-041 | Column | Type | Constraints | Description | |---|---|---|---| -| `id` | `integer` | PK; NOT NULL | | +| `id` | `integer` | PK; NOT NULL; DEFAULT nextval('kg_api.ai_extraction_config_id_seq'::regclass) | | | `provider` | `character varying(50)` | NOT NULL; UNIQUE | AI provider: openai, anthropic, ollama, or vllm | | `model_name` | `character varying(200)` | NOT NULL | Model identifier (e.g., gpt-4o, claude-sonnet-4-20250514) | | `supports_vision` | `boolean` | DEFAULT false | Whether the model supports vision/image inputs | @@ -128,7 +128,7 @@ Active vision (image->prose) provider selection — ADR-802 / #378. Selection-on | Column | Type | Constraints | Description | |---|---|---|---| -| `id` | `integer` | PK; NOT NULL | | +| `id` | `integer` | PK; NOT NULL; DEFAULT nextval('kg_api.ai_vision_config_id_seq'::regclass) | | | `provider` | `character varying(50)` | NOT NULL; UNIQUE | Provider performing image->prose description | | `model_name` | `character varying(200)` | NOT NULL; DEFAULT ''::character varying | Vision model id; '' resolves from the catalog supports_vision rows | | `max_tokens` | `integer` | | | @@ -160,7 +160,7 @@ One row per annealing cycle: ecological snapshot + Bezier pressure read-out (#24 | Column | Type | Constraints | Description | |---|---|---|---| -| `id` | `integer` | PK; NOT NULL | | +| `id` | `integer` | PK; NOT NULL; DEFAULT nextval('kg_api.annealing_pressure_history_id_seq'::regclass) | | | `epoch` | `integer` | NOT NULL | | | `total_ontologies` | `integer` | NOT NULL | | | `total_concepts` | `integer` | NOT NULL | | @@ -178,7 +178,7 @@ One row per annealing cycle: ecological snapshot + Bezier pressure read-out (#24 | Column | Type | Constraints | Description | |---|---|---|---| -| `id` | `integer` | PK; NOT NULL | | +| `id` | `integer` | PK; NOT NULL; DEFAULT nextval('kg_api.annealing_proposals_id_seq'::regclass) | | | `proposal_type` | `character varying(20)` | NOT NULL | | | `ontology_name` | `character varying(200)` | NOT NULL | | | `anchor_concept_id` | `character varying(100)` | | | @@ -213,7 +213,7 @@ Computed artifact metadata with Garage blob pointers (ADR-083) | Column | Type | Constraints | Description | |---|---|---|---| -| `id` | `integer` | PK; NOT NULL | | +| `id` | `integer` | PK; NOT NULL; DEFAULT nextval('kg_api.artifacts_id_seq'::regclass) | | | `artifact_type` | `character varying(50)` | NOT NULL | Type of computation: polarity_analysis, projection, etc. | | `representation` | `character varying(50)` | NOT NULL | Source UI/tool: polarity_explorer, cli, mcp_server, etc. | | `name` | `character varying(200)` | | | @@ -303,7 +303,7 @@ Resource-aware embedding configuration for local and remote models - ADR-039. In | Column | Type | Constraints | Description | |---|---|---|---| -| `id` | `integer` | PK; NOT NULL | | +| `id` | `integer` | PK; NOT NULL; DEFAULT nextval('kg_api.embedding_config_id_seq'::regclass) | | | `provider` | `character varying(50)` | NOT NULL | Embedding provider: local (sentence-transformers) or openai | | `model_name` | `character varying(200)` | NOT NULL | Model identifier (HuggingFace ID for local, OpenAI model name for remote) | | `embedding_dimensions` | `integer` | NOT NULL | | @@ -360,7 +360,7 @@ Unified embedding profile with text + image model slots. Replaces embedding_conf | Column | Type | Constraints | Description | |---|---|---|---| -| `id` | `integer` | PK; NOT NULL | | +| `id` | `integer` | PK; NOT NULL; DEFAULT nextval('kg_api.embedding_profile_id_seq'::regclass) | | | `name` | `character varying(200)` | NOT NULL | | | `vector_space` | `character varying(100)` | NOT NULL | Compatibility key for the universal TEXT/prose space (concepts, edges, docs, image-prose). Profiles with the same text vector_space produce comparable text embeddings. Image embeddings are independent — see image_vector_space (ADR-803). | | `multimodal` | `boolean` | DEFAULT false | When true, the text model also handles image embeddings (e.g. SigLIP 2) | @@ -416,7 +416,7 @@ ADR-203: Monotonic event log of graph mutations. Distinct from graph_change_coun | Column | Type | Constraints | Description | |---|---|---|---| -| `event_id` | `bigint` | PK; NOT NULL | Monotonic logical-time id. Foreign-keyed by Instance.created_at_event_id. | +| `event_id` | `bigint` | PK; NOT NULL; DEFAULT nextval('kg_api.graph_epochs_event_id_seq'::regclass) | Monotonic logical-time id. Foreign-keyed by Instance.created_at_event_id. | | `occurred_at` | `timestamp with time zone` | NOT NULL; DEFAULT now() | | | `kind` | `text` | NOT NULL; FK → kg_api.graph_epoch_kinds(kind) | ingestion \| reasoning \| annealing \| edit. Determines whether occurred_at is semantically meaningful for the rows attributable to this event. | | `actor` | `text` | | | @@ -488,7 +488,7 @@ Formal ontology versioning with immutable snapshots - ADR-026 | Column | Type | Constraints | Description | |---|---|---|---| -| `version_id` | `integer` | PK; NOT NULL | | +| `version_id` | `integer` | PK; NOT NULL; DEFAULT nextval('kg_api.ontology_versions_version_id_seq'::regclass) | | | `version_number` | `character varying(20)` | NOT NULL; UNIQUE | | | `created_at` | `timestamp with time zone` | NOT NULL; DEFAULT now() | | | `created_by` | `character varying(100)` | | | @@ -519,7 +519,7 @@ Cached model catalog per AI provider with curation and pricing (ADR-800) | Column | Type | Constraints | Description | |---|---|---|---| -| `id` | `integer` | PK; NOT NULL | | +| `id` | `integer` | PK; NOT NULL; DEFAULT nextval('kg_api.provider_model_catalog_id_seq'::regclass) | | | `provider` | `character varying(50)` | NOT NULL | | | `model_id` | `character varying(300)` | NOT NULL | | | `display_name` | `character varying(300)` | | | @@ -552,7 +552,7 @@ Pending vocabulary management actions - ADR-032 | Column | Type | Constraints | Description | |---|---|---|---| -| `id` | `integer` | PK; NOT NULL | | +| `id` | `integer` | PK; NOT NULL; DEFAULT nextval('kg_api.pruning_recommendations_id_seq'::regclass) | | | `relationship_type` | `character varying(100)` | NOT NULL | | | `target_type` | `character varying(100)` | | | | `action_type` | `character varying(50)` | NOT NULL | | @@ -581,7 +581,7 @@ Saved query recipes that can be re-executed (ADR-083) | Column | Type | Constraints | Description | |---|---|---|---| -| `id` | `integer` | PK; NOT NULL | | +| `id` | `integer` | PK; NOT NULL; DEFAULT nextval('kg_api.query_definitions_id_seq'::regclass) | | | `name` | `character varying(200)` | NOT NULL | | | `definition_type` | `character varying(50)` | NOT NULL | Type of query: block_diagram, cypher, search, polarity, connection, exploration, program | | `definition` | `jsonb` | NOT NULL | Query parameters/structure as JSON | @@ -648,7 +648,7 @@ Scheduled background jobs: - category_refresh: Re-integrate LLM-generated vocabu | Column | Type | Constraints | Description | |---|---|---|---| -| `id` | `integer` | PK; NOT NULL | | +| `id` | `integer` | PK; NOT NULL; DEFAULT nextval('kg_api.scheduled_jobs_id_seq'::regclass) | | | `name` | `character varying(100)` | NOT NULL; UNIQUE | Unique identifier for the scheduled job | | `launcher_class` | `character varying(255)` | NOT NULL | Python class name in launcher registry (e.g., CategoryRefreshLauncher) | | `schedule_cron` | `character varying(100)` | NOT NULL | Cron expression for schedule (e.g., "0 */6 * * *" = every 6 hours) | @@ -689,7 +689,7 @@ Capture layer for unmatched relationship types - ADR-025 | Column | Type | Constraints | Description | |---|---|---|---| -| `id` | `integer` | PK; NOT NULL | | +| `id` | `integer` | PK; NOT NULL; DEFAULT nextval('kg_api.skipped_relationships_id_seq'::regclass) | | | `relationship_type` | `character varying(100)` | NOT NULL | | | `from_concept_label` | `character varying(500)` | | | | `to_concept_label` | `character varying(500)` | | | @@ -710,7 +710,7 @@ ADR-068: Embeddings for source text chunks with offset tracking and hash verific | Column | Type | Constraints | Description | |---|---|---|---| -| `embedding_id` | `integer` | PK; NOT NULL | | +| `embedding_id` | `integer` | PK; NOT NULL; DEFAULT nextval('kg_api.source_embeddings_embedding_id_seq'::regclass) | | | `source_id` | `text` | NOT NULL | Reference to Source node in Apache AGE graph | | `chunk_index` | `integer` | NOT NULL | 0-based chunk number within source (e.g., 0, 1, 2...) | | `chunk_strategy` | `text` | NOT NULL | Chunking strategy used: sentence, paragraph, semantic, or count | @@ -792,7 +792,7 @@ ADR-045: Tracks completion of system initialization tasks like cold start embedd | Column | Type | Constraints | Description | |---|---|---|---| -| `id` | `integer` | PK; NOT NULL | | +| `id` | `integer` | PK; NOT NULL; DEFAULT nextval('kg_api.vocabulary_audit_id_seq'::regclass) | | | `relationship_type` | `character varying(100)` | | | | `action` | `character varying(50)` | NOT NULL | | | `performed_by` | `character varying(100)` | | | @@ -817,7 +817,7 @@ Detailed vocabulary change tracking with context (ADR-032) | Column | Type | Constraints | Description | |---|---|---|---| -| `id` | `integer` | PK; NOT NULL | | +| `id` | `integer` | PK; NOT NULL; DEFAULT nextval('kg_api.vocabulary_history_id_seq'::regclass) | | | `relationship_type` | `character varying(100)` | NOT NULL | | | `action` | `character varying(50)` | NOT NULL | | | `performed_by` | `character varying(100)` | NOT NULL | | @@ -840,7 +840,7 @@ LLM-assisted vocabulary curation suggestions - ADR-026 | Column | Type | Constraints | Description | |---|---|---|---| -| `id` | `integer` | PK; NOT NULL | | +| `id` | `integer` | PK; NOT NULL; DEFAULT nextval('kg_api.vocabulary_suggestions_id_seq'::regclass) | | | `relationship_type` | `character varying(100)` | NOT NULL | | | `suggestion_type` | `character varying(50)` | NOT NULL | | | `confidence` | `numeric(3,2)` | NOT NULL | | @@ -1048,7 +1048,7 @@ Instance-level access grants for owned resources (ADR-082) | Column | Type | Constraints | Description | |---|---|---|---| -| `id` | `integer` | PK; NOT NULL | | +| `id` | `integer` | PK; NOT NULL; DEFAULT nextval('kg_auth.resource_grants_id_seq'::regclass) | | | `resource_type` | `character varying(50)` | NOT NULL | Type: ontology, artifact, report, etc. | | `resource_id` | `character varying(200)` | NOT NULL | Specific resource identifier | | `principal_type` | `character varying(20)` | NOT NULL | Grant to user or group | @@ -1084,7 +1084,7 @@ Dynamic role permissions with scoping (ADR-028) | Column | Type | Constraints | Description | |---|---|---|---| -| `id` | `integer` | PK; NOT NULL | | +| `id` | `integer` | PK; NOT NULL; DEFAULT nextval('kg_auth.role_permissions_id_seq'::regclass) | | | `role_name` | `character varying(50)` | NOT NULL; FK → kg_auth.roles(role_name) | | | `resource_type` | `character varying(100)` | NOT NULL; FK → kg_auth.resources(resource_type) | | | `action` | `character varying(50)` | NOT NULL | | @@ -1129,7 +1129,7 @@ User role assignments with optional scoping (ADR-028) | Column | Type | Constraints | Description | |---|---|---|---| -| `id` | `integer` | PK; NOT NULL | | +| `id` | `integer` | PK; NOT NULL; DEFAULT nextval('kg_auth.user_roles_id_seq'::regclass) | | | `user_id` | `integer` | NOT NULL; FK → kg_auth.users(id) | | | `role_name` | `character varying(50)` | NOT NULL; FK → kg_auth.roles(role_name) | | | `scope_type` | `character varying(50)` | | | @@ -1146,7 +1146,7 @@ User role assignments with optional scoping (ADR-028) | Column | Type | Constraints | Description | |---|---|---|---| -| `id` | `integer` | PK; NOT NULL | | +| `id` | `integer` | PK; NOT NULL; DEFAULT nextval('kg_auth.users_id_seq'::regclass) | | | `username` | `character varying(100)` | NOT NULL; UNIQUE | | | `password_hash` | `character varying(255)` | NOT NULL | | | `primary_role` | `character varying(50)` | NOT NULL; FK → kg_auth.roles(role_name) | Primary role (backwards compatibility) - user can have additional roles in user_roles table | @@ -1162,7 +1162,7 @@ Observability: audit trails, metrics, health. | Column | Type | Constraints | Description | |---|---|---|---| -| `id` | `integer` | PK; NOT NULL | | +| `id` | `integer` | PK; NOT NULL; DEFAULT nextval('kg_logs.api_metrics_id_seq'::regclass) | | | `timestamp` | `timestamp with time zone` | NOT NULL; DEFAULT now() | | | `endpoint` | `character varying(200)` | NOT NULL | | | `method` | `character varying(10)` | NOT NULL | | @@ -1175,7 +1175,7 @@ Observability: audit trails, metrics, health. | Column | Type | Constraints | Description | |---|---|---|---| -| `id` | `integer` | PK; NOT NULL | | +| `id` | `integer` | PK; NOT NULL; DEFAULT nextval('kg_logs.audit_trail_id_seq'::regclass) | | | `timestamp` | `timestamp with time zone` | NOT NULL; DEFAULT now() | | | `user_id` | `integer` | | | | `action` | `character varying(100)` | NOT NULL | | @@ -1194,7 +1194,7 @@ Observability: audit trails, metrics, health. | Column | Type | Constraints | Description | |---|---|---|---| -| `id` | `integer` | PK; NOT NULL | | +| `id` | `integer` | PK; NOT NULL; DEFAULT nextval('kg_logs.health_checks_id_seq'::regclass) | | | `timestamp` | `timestamp with time zone` | NOT NULL; DEFAULT now() | | | `service` | `character varying(50)` | NOT NULL | | | `status` | `character varying(50)` | NOT NULL | | @@ -1208,7 +1208,7 @@ Observability: audit trails, metrics, health. | Column | Type | Constraints | Description | |---|---|---|---| -| `id` | `integer` | PK; NOT NULL | | +| `id` | `integer` | PK; NOT NULL; DEFAULT nextval('kg_logs.job_events_id_seq'::regclass) | | | `job_id` | `character varying(50)` | NOT NULL | | | `timestamp` | `timestamp with time zone` | NOT NULL; DEFAULT now() | | | `event_type` | `character varying(50)` | NOT NULL | | diff --git a/operator/database/migrate-db.sh b/operator/database/migrate-db.sh index 988052dfe..872eda051 100755 --- a/operator/database/migrate-db.sh +++ b/operator/database/migrate-db.sh @@ -296,9 +296,11 @@ for pending in "${PENDING_MIGRATIONS[@]}"; do echo -e "${GRAY}----------------------------------------${NC}" fi - # Apply migration (capture output to detect errors) - MIGRATION_OUTPUT=$(run_psql_file "$FILE" 2>&1) - MIGRATION_EXIT_CODE=$? + # Apply migration (capture output to detect errors). The || arm is + # required: under set -e a bare command-substitution assignment aborts + # the whole script on psql failure, making the skip-and-retry branch + # below unreachable. + MIGRATION_OUTPUT=$(run_psql_file "$FILE" 2>&1) && MIGRATION_EXIT_CODE=0 || MIGRATION_EXIT_CODE=$? # Show output in verbose mode if [ "$VERBOSE" = true ]; then diff --git a/schema/00_baseline.sql b/schema/00_baseline.sql index 8908038b4..fd1ec6edf 100644 --- a/schema/00_baseline.sql +++ b/schema/00_baseline.sql @@ -6747,6 +6747,10 @@ GRANT SELECT ON TABLE kg_api.vocabulary_config TO PUBLIC; -- Rows come from a valid fully-migrated database; FK triggers are disabled -- during load because pg_dump cannot order rows of self-referencing tables -- (kg_auth.roles.parent_role). Requires superuser — initdb runs as one. +-- +-- NOTE: includes development-default credentials seeded by the migrations +-- (e.g. the default admin user's bcrypt hash). CHANGE THESE IN PRODUCTION — +-- operator init / configure.py handles rotation on managed deployments. SET session_replication_role = replica; -- -- PostgreSQL database dump @@ -8096,7 +8100,7 @@ BEGIN PERFORM ag_catalog.create_vlabel('knowledge_graph', lbl::cstring); RAISE NOTICE 'Created vertex label: %', lbl; EXCEPTION WHEN OTHERS THEN - RAISE NOTICE 'Could not create vertex label % (will be created on first use): %', lbl, SQLERRM; + RAISE WARNING 'Could not create vertex label % (will be created on first use): %', lbl, SQLERRM; END; END IF; END LOOP; @@ -8125,7 +8129,7 @@ BEGIN PERFORM ag_catalog.create_elabel('knowledge_graph', lbl::cstring); RAISE NOTICE 'Created edge label: %', lbl; EXCEPTION WHEN OTHERS THEN - RAISE NOTICE 'Could not create edge label % (will be created on first use): %', lbl, SQLERRM; + RAISE WARNING 'Could not create edge label % (will be created on first use): %', lbl, SQLERRM; END; END IF; END LOOP; @@ -8169,7 +8173,7 @@ BEGIN PERFORM ag_catalog.create_elabel('knowledge_graph', lbl::cstring); RAISE NOTICE 'Created edge label: %', lbl; EXCEPTION WHEN OTHERS THEN - RAISE NOTICE 'Could not create edge label % (will be created on first use): %', lbl, SQLERRM; + RAISE WARNING 'Could not create edge label % (will be created on first use): %', lbl, SQLERRM; END; END IF; END LOOP; diff --git a/schema/migrations-warm/058_precreate_graph_labels.sql b/schema/migrations-warm/058_precreate_graph_labels.sql index 76c82cfb7..ae7b23c55 100644 --- a/schema/migrations-warm/058_precreate_graph_labels.sql +++ b/schema/migrations-warm/058_precreate_graph_labels.sql @@ -42,7 +42,7 @@ BEGIN PERFORM ag_catalog.create_vlabel('knowledge_graph', lbl::cstring); RAISE NOTICE 'Created vertex label: %', lbl; EXCEPTION WHEN OTHERS THEN - RAISE NOTICE 'Could not create vertex label % (will be created on first use): %', lbl, SQLERRM; + RAISE WARNING 'Could not create vertex label % (will be created on first use): %', lbl, SQLERRM; END; END IF; END LOOP; @@ -71,7 +71,7 @@ BEGIN PERFORM ag_catalog.create_elabel('knowledge_graph', lbl::cstring); RAISE NOTICE 'Created edge label: %', lbl; EXCEPTION WHEN OTHERS THEN - RAISE NOTICE 'Could not create edge label % (will be created on first use): %', lbl, SQLERRM; + RAISE WARNING 'Could not create edge label % (will be created on first use): %', lbl, SQLERRM; END; END IF; END LOOP; @@ -115,7 +115,7 @@ BEGIN PERFORM ag_catalog.create_elabel('knowledge_graph', lbl::cstring); RAISE NOTICE 'Created edge label: %', lbl; EXCEPTION WHEN OTHERS THEN - RAISE NOTICE 'Could not create edge label % (will be created on first use): %', lbl, SQLERRM; + RAISE WARNING 'Could not create edge label % (will be created on first use): %', lbl, SQLERRM; END; END IF; END LOOP; diff --git a/schema/scripts/checkpoint.sh b/schema/scripts/checkpoint.sh index f70fa62be..b6450c771 100755 --- a/schema/scripts/checkpoint.sh +++ b/schema/scripts/checkpoint.sh @@ -50,10 +50,10 @@ WORKDIR="${CHECKPOINT_WORKDIR:-/tmp/kg-checkpoint}" # Backfill-only migrations (e.g. 024, 043) are no-ops on fresh databases # and are intentionally not carried. verify proves this list is sufficient. GRAPH_SEED_MIGRATIONS=( - "migrations/014_vocabulary_as_graph.sql" - "migrations/044_ontology_graph_nodes.sql" + "migrations/archived/014_vocabulary_as_graph.sql" + "migrations/archived/044_ontology_graph_nodes.sql" "migrations-warm/058_precreate_graph_labels.sql" - "migrations/078_seed_primordial_ontology.sql" + "migrations/archived/078_seed_primordial_ontology.sql" ) case "$(uname -m)" in @@ -122,10 +122,15 @@ start_container() { # start_container fail "$name did not become ready in 120s" } -# Migrations in numeric order across cold + warm directories +# Migrations in numeric order across cold + warm directories. archived/ is +# NOT included: post-checkpoint, replay = current baseline + migrations 081+. +# The existence filter keeps unexpanded globs out when a directory is empty. migration_files() { - printf '%s\n' "$SCHEMA_DIR"/migrations/*.sql "$SCHEMA_DIR"/migrations-warm/*.sql \ - | awk -F/ '{print $NF "\t" $0}' | sort | cut -f2 + local f + for f in "$SCHEMA_DIR"/migrations/*.sql "$SCHEMA_DIR"/migrations-warm/*.sql; do + [ -f "$f" ] || continue + printf '%s\n' "$f" + done | awk -F/ '{print $NF "\t" $0}' | sort | cut -f2 } # ---------------------------------------------------------------------------- @@ -264,6 +269,10 @@ HEADER -- Rows come from a valid fully-migrated database; FK triggers are disabled -- during load because pg_dump cannot order rows of self-referencing tables -- (kg_auth.roles.parent_role). Requires superuser — initdb runs as one. +-- +-- NOTE: includes development-default credentials seeded by the migrations +-- (e.g. the default admin user's bcrypt hash). CHANGE THESE IN PRODUCTION — +-- operator init / configure.py handles rotation on managed deployments. SET session_replication_role = replica; SECTION cat "$WORKDIR/dump_data.sql" diff --git a/schema/scripts/generate-schema-docs.py b/schema/scripts/generate-schema-docs.py index 69ef88cdd..f94733229 100755 --- a/schema/scripts/generate-schema-docs.py +++ b/schema/scripts/generate-schema-docs.py @@ -135,6 +135,14 @@ def split_top_level_commas(body: str): re.IGNORECASE | re.DOTALL, ) +# ALTER TABLE ... ALTER COLUMN ... SET DEFAULT — how pg_dump emits serial +# column defaults (nextval(...)) after creating the sequence. +ALTER_COL_DEFAULT = re.compile( + r"ALTER\s+TABLE\s+(?:ONLY\s+)?([A-Za-z_][\w.]*)\s+" + r"ALTER\s+COLUMN\s+(\"?[A-Za-z_]\w*\"?)\s+SET\s+DEFAULT\s+(.+?);", + re.IGNORECASE | re.DOTALL, +) + def parse_columns(body: str): """Parse a table body into a list of column dicts and a constraint list. @@ -253,6 +261,19 @@ def apply_alter_constraints(sql: str, tables: dict): tbl["constraints"].append(definition) + for m in ALTER_COL_DEFAULT.finditer(sql): + qualified = m.group(1).strip('"') + col_name = m.group(2).strip('"') + value = " ".join(m.group(3).split()) + tbl = tables.get(qualified) + if tbl is None: + continue + for col in tbl["columns"]: + if col["name"] == col_name and not any( + f.startswith("DEFAULT ") for f in col["flags"] + ): + col["flags"].append(f"DEFAULT {value}") + def find_comments(sql: str): """Extract COMMENT ON TABLE/COLUMN statements from *sql*. @@ -452,10 +473,20 @@ def collect_fk_edges(tables: dict): def render_er_diagram(schema: str, edges, tables) -> list: """Render a mermaid erDiagram for FK edges whose child is in *schema*. - Table names are unique across schemas today; entities use bare names so - the diagram stays readable (mermaid renders natively on GitHub and in - mkdocs-material via superfences). + Entities use bare table names for readability (mermaid renders natively + on GitHub and in mkdocs-material via superfences); names that collide + across schemas are disambiguated with a schema__ prefix. """ + name_schemas = {} + for tbl in tables.values(): + name_schemas.setdefault(tbl["name"], set()).add(tbl["schema"]) + + def entity(qualified): + tbl = tables[qualified] + if len(name_schemas[tbl["name"]]) > 1: + return f"{tbl['schema']}__{tbl['name']}" + return tbl["name"] + schema_edges = [ (c, p, col) for c, p, col in edges if tables.get(c, {}).get("schema") == schema and p in tables @@ -464,9 +495,7 @@ def render_er_diagram(schema: str, edges, tables) -> list: return [] out = ["#### Relationships", "", "```mermaid", "erDiagram"] for child, parent, col in sorted(set(schema_edges)): - child_name = tables[child]["name"] - parent_name = tables[parent]["name"] - out.append(f' {parent_name} ||--o{{ {child_name} : "{col}"') + out.append(f' {entity(parent)} ||--o{{ {entity(child)} : "{col}"') out.extend(["```", ""]) return out