-- Creation Knowledge formal cloud schema. -- Applied to database: creation_knowledge_prod -- Schema: creation_knowledge CREATE EXTENSION IF NOT EXISTS pgcrypto; CREATE SCHEMA IF NOT EXISTS creation_knowledge; CREATE TABLE IF NOT EXISTS creation_knowledge.schema_migrations ( version text PRIMARY KEY, description text NOT NULL, applied_at timestamptz NOT NULL DEFAULT now() ); CREATE OR REPLACE FUNCTION creation_knowledge.touch_updated_at() RETURNS trigger LANGUAGE plpgsql AS $$ BEGIN NEW.updated_at = now(); RETURN NEW; END; $$; CREATE TABLE IF NOT EXISTS creation_knowledge.query_batches ( id uuid PRIMARY KEY DEFAULT gen_random_uuid(), name text NOT NULL, source_type text NOT NULL DEFAULT 'manual', generation_method text, target_platforms text[] NOT NULL DEFAULT ARRAY[]::text[], status text NOT NULL DEFAULT 'draft', metadata jsonb NOT NULL DEFAULT '{}'::jsonb, error_message text, created_at timestamptz NOT NULL DEFAULT now(), updated_at timestamptz NOT NULL DEFAULT now() ); CREATE TABLE IF NOT EXISTS creation_knowledge.queries ( id uuid PRIMARY KEY DEFAULT gen_random_uuid(), batch_id uuid REFERENCES creation_knowledge.query_batches(id) ON DELETE CASCADE, query_text text NOT NULL, axes jsonb NOT NULL DEFAULT '{}'::jsonb, keep boolean, filter_reason text, status text NOT NULL DEFAULT 'draft', sort_order integer NOT NULL DEFAULT 0, metadata jsonb NOT NULL DEFAULT '{}'::jsonb, error_message text, created_at timestamptz NOT NULL DEFAULT now(), updated_at timestamptz NOT NULL DEFAULT now() ); CREATE TABLE IF NOT EXISTS creation_knowledge.acquisition_runs ( id uuid PRIMARY KEY DEFAULT gen_random_uuid(), batch_id uuid REFERENCES creation_knowledge.query_batches(id), run_key text UNIQUE, status text NOT NULL DEFAULT 'pending', note text, metadata jsonb NOT NULL DEFAULT '{}'::jsonb, error_message text, started_at timestamptz, finished_at timestamptz, created_at timestamptz NOT NULL DEFAULT now(), updated_at timestamptz NOT NULL DEFAULT now() ); CREATE TABLE IF NOT EXISTS creation_knowledge.acquisition_jobs ( id uuid PRIMARY KEY DEFAULT gen_random_uuid(), run_id uuid NOT NULL REFERENCES creation_knowledge.acquisition_runs(id) ON DELETE CASCADE, query_id uuid REFERENCES creation_knowledge.queries(id), platform text NOT NULL, status text NOT NULL DEFAULT 'pending', search_limit integer, display_limit integer, attempt_count integer NOT NULL DEFAULT 0, metadata jsonb NOT NULL DEFAULT '{}'::jsonb, error_message text, started_at timestamptz, finished_at timestamptz, created_at timestamptz NOT NULL DEFAULT now(), updated_at timestamptz NOT NULL DEFAULT now(), UNIQUE (run_id, query_id, platform) ); CREATE TABLE IF NOT EXISTS creation_knowledge.candidate_items ( id uuid PRIMARY KEY DEFAULT gen_random_uuid(), job_id uuid REFERENCES creation_knowledge.acquisition_jobs(id) ON DELETE SET NULL, query_id uuid REFERENCES creation_knowledge.queries(id) ON DELETE SET NULL, platform text NOT NULL, platform_item_id text, canonical_url text, content_type text, title text, author_name text, published_at timestamptz, raw_summary text, status text NOT NULL DEFAULT 'candidate', source_payload jsonb NOT NULL DEFAULT '{}'::jsonb, metadata jsonb NOT NULL DEFAULT '{}'::jsonb, error_message text, created_at timestamptz NOT NULL DEFAULT now(), updated_at timestamptz NOT NULL DEFAULT now() ); CREATE TABLE IF NOT EXISTS creation_knowledge.media_assets ( id uuid PRIMARY KEY DEFAULT gen_random_uuid(), item_id uuid NOT NULL REFERENCES creation_knowledge.candidate_items(id) ON DELETE CASCADE, media_type text NOT NULL, source_url text, oss_url text, cdn_url text, position integer NOT NULL DEFAULT 0, status text NOT NULL DEFAULT 'pending', source_payload jsonb NOT NULL DEFAULT '{}'::jsonb, metadata jsonb NOT NULL DEFAULT '{}'::jsonb, error_message text, created_at timestamptz NOT NULL DEFAULT now(), updated_at timestamptz NOT NULL DEFAULT now() ); CREATE TABLE IF NOT EXISTS creation_knowledge.item_classifications ( id uuid PRIMARY KEY DEFAULT gen_random_uuid(), item_id uuid NOT NULL REFERENCES creation_knowledge.candidate_items(id) ON DELETE CASCADE, is_creation_knowledge boolean, label text, confidence numeric(5,4), reason text, model_name text, prompt_version text, result_payload jsonb NOT NULL DEFAULT '{}'::jsonb, status text NOT NULL DEFAULT 'pending', error_message text, created_at timestamptz NOT NULL DEFAULT now(), updated_at timestamptz NOT NULL DEFAULT now() ); CREATE TABLE IF NOT EXISTS creation_knowledge.decode_jobs ( id uuid PRIMARY KEY DEFAULT gen_random_uuid(), item_id uuid NOT NULL REFERENCES creation_knowledge.candidate_items(id) ON DELETE CASCADE, status text NOT NULL DEFAULT 'pending', attempt_count integer NOT NULL DEFAULT 0, metadata jsonb NOT NULL DEFAULT '{}'::jsonb, error_message text, started_at timestamptz, finished_at timestamptz, created_at timestamptz NOT NULL DEFAULT now(), updated_at timestamptz NOT NULL DEFAULT now() ); CREATE TABLE IF NOT EXISTS creation_knowledge.decode_results ( id uuid PRIMARY KEY DEFAULT gen_random_uuid(), decode_job_id uuid REFERENCES creation_knowledge.decode_jobs(id) ON DELETE SET NULL, item_id uuid NOT NULL REFERENCES creation_knowledge.candidate_items(id) ON DELETE CASCADE, read_result jsonb NOT NULL DEFAULT '{}'::jsonb, gate_result jsonb NOT NULL DEFAULT '{}'::jsonb, framing_result jsonb NOT NULL DEFAULT '{}'::jsonb, status text NOT NULL DEFAULT 'draft', error_message text, created_at timestamptz NOT NULL DEFAULT now(), updated_at timestamptz NOT NULL DEFAULT now() ); CREATE TABLE IF NOT EXISTS creation_knowledge.knowledge_particles ( id uuid PRIMARY KEY DEFAULT gen_random_uuid(), decode_result_id uuid REFERENCES creation_knowledge.decode_results(id) ON DELETE CASCADE, item_id uuid REFERENCES creation_knowledge.candidate_items(id) ON DELETE CASCADE, parent_particle_id uuid REFERENCES creation_knowledge.knowledge_particles(id) ON DELETE SET NULL, particle_type text NOT NULL CHECK (particle_type IN ('what', 'how', 'why')), title text NOT NULL, business_stage text, creation_stage text, content jsonb NOT NULL DEFAULT '{}'::jsonb, sort_order integer NOT NULL DEFAULT 0, status text NOT NULL DEFAULT 'draft', metadata jsonb NOT NULL DEFAULT '{}'::jsonb, error_message text, created_at timestamptz NOT NULL DEFAULT now(), updated_at timestamptz NOT NULL DEFAULT now() ); CREATE TABLE IF NOT EXISTS creation_knowledge.scope_results ( id uuid PRIMARY KEY DEFAULT gen_random_uuid(), particle_id uuid REFERENCES creation_knowledge.knowledge_particles(id) ON DELETE CASCADE, item_id uuid REFERENCES creation_knowledge.candidate_items(id) ON DELETE CASCADE, scope_type text NOT NULL, scope_value text NOT NULL, is_reused boolean, matched_scope_id text, confidence numeric(5,4), evidence jsonb NOT NULL DEFAULT '{}'::jsonb, status text NOT NULL DEFAULT 'draft', error_message text, created_at timestamptz NOT NULL DEFAULT now(), updated_at timestamptz NOT NULL DEFAULT now() ); CREATE TABLE IF NOT EXISTS creation_knowledge.payload_drafts ( id uuid PRIMARY KEY DEFAULT gen_random_uuid(), particle_id uuid REFERENCES creation_knowledge.knowledge_particles(id) ON DELETE CASCADE, item_id uuid REFERENCES creation_knowledge.candidate_items(id) ON DELETE CASCADE, payload jsonb NOT NULL DEFAULT '{}'::jsonb, review_status text NOT NULL DEFAULT 'pending', ingest_ready boolean NOT NULL DEFAULT false, status text NOT NULL DEFAULT 'draft', error_message text, created_at timestamptz NOT NULL DEFAULT now(), updated_at timestamptz NOT NULL DEFAULT now() ); CREATE TABLE IF NOT EXISTS creation_knowledge.ingest_records ( id uuid PRIMARY KEY DEFAULT gen_random_uuid(), payload_draft_id uuid REFERENCES creation_knowledge.payload_drafts(id) ON DELETE SET NULL, target_system text NOT NULL, target_id text, status text NOT NULL DEFAULT 'pending', attempt_count integer NOT NULL DEFAULT 0, response_payload jsonb NOT NULL DEFAULT '{}'::jsonb, error_message text, created_at timestamptz NOT NULL DEFAULT now(), updated_at timestamptz NOT NULL DEFAULT now() ); CREATE TABLE IF NOT EXISTS creation_knowledge.pipeline_runs ( id uuid PRIMARY KEY DEFAULT gen_random_uuid(), run_key text UNIQUE, batch_id uuid REFERENCES creation_knowledge.query_batches(id) ON DELETE SET NULL, status text NOT NULL DEFAULT 'pending', current_stage text, config jsonb NOT NULL DEFAULT '{}'::jsonb, metadata jsonb NOT NULL DEFAULT '{}'::jsonb, error_message text, started_at timestamptz, finished_at timestamptz, created_at timestamptz NOT NULL DEFAULT now(), updated_at timestamptz NOT NULL DEFAULT now() ); CREATE TABLE IF NOT EXISTS creation_knowledge.pipeline_jobs ( id uuid PRIMARY KEY DEFAULT gen_random_uuid(), pipeline_run_id uuid NOT NULL REFERENCES creation_knowledge.pipeline_runs(id) ON DELETE CASCADE, stage text NOT NULL, status text NOT NULL DEFAULT 'pending', target_table text, target_id uuid, attempt_count integer NOT NULL DEFAULT 0, metadata jsonb NOT NULL DEFAULT '{}'::jsonb, error_message text, started_at timestamptz, finished_at timestamptz, created_at timestamptz NOT NULL DEFAULT now(), updated_at timestamptz NOT NULL DEFAULT now() ); CREATE TABLE IF NOT EXISTS creation_knowledge.pipeline_run_events ( id uuid PRIMARY KEY DEFAULT gen_random_uuid(), pipeline_run_id uuid REFERENCES creation_knowledge.pipeline_runs(id) ON DELETE CASCADE, pipeline_job_id uuid REFERENCES creation_knowledge.pipeline_jobs(id) ON DELETE SET NULL, stage text NOT NULL, event_type text NOT NULL, status text, severity text NOT NULL DEFAULT 'info', target_table text, target_id uuid, acquisition_run_id uuid REFERENCES creation_knowledge.acquisition_runs(id) ON DELETE SET NULL, acquisition_job_id uuid REFERENCES creation_knowledge.acquisition_jobs(id) ON DELETE SET NULL, query_id uuid REFERENCES creation_knowledge.queries(id) ON DELETE SET NULL, item_id uuid REFERENCES creation_knowledge.candidate_items(id) ON DELETE SET NULL, decode_job_id uuid REFERENCES creation_knowledge.decode_jobs(id) ON DELETE SET NULL, decode_result_id uuid REFERENCES creation_knowledge.decode_results(id) ON DELETE SET NULL, payload_draft_id uuid REFERENCES creation_knowledge.payload_drafts(id) ON DELETE SET NULL, ingest_record_id uuid REFERENCES creation_knowledge.ingest_records(id) ON DELETE SET NULL, platform text, attempt_index integer, message text, payload jsonb NOT NULL DEFAULT '{}'::jsonb, error_message text, duration_ms integer, created_at timestamptz NOT NULL DEFAULT now() ); CREATE TABLE IF NOT EXISTS creation_knowledge.candidate_item_hits ( id uuid PRIMARY KEY DEFAULT gen_random_uuid(), pipeline_run_id uuid REFERENCES creation_knowledge.pipeline_runs(id) ON DELETE CASCADE, acquisition_run_id uuid REFERENCES creation_knowledge.acquisition_runs(id) ON DELETE CASCADE, acquisition_job_id uuid REFERENCES creation_knowledge.acquisition_jobs(id) ON DELETE SET NULL, query_id uuid REFERENCES creation_knowledge.queries(id) ON DELETE SET NULL, item_id uuid REFERENCES creation_knowledge.candidate_items(id) ON DELETE SET NULL, platform text NOT NULL, unique_key text, platform_item_id text, search_provider text, detail_provider text, attempt_index integer, page_index integer, page_rank integer, candidate_rank integer, source_cursor text, is_duplicate_hit boolean NOT NULL DEFAULT false, raw_candidate jsonb NOT NULL DEFAULT '{}'::jsonb, metadata jsonb NOT NULL DEFAULT '{}'::jsonb, created_at timestamptz NOT NULL DEFAULT now() ); CREATE TABLE IF NOT EXISTS creation_knowledge.llm_call_traces ( id uuid PRIMARY KEY DEFAULT gen_random_uuid(), pipeline_run_id uuid REFERENCES creation_knowledge.pipeline_runs(id) ON DELETE CASCADE, pipeline_job_id uuid REFERENCES creation_knowledge.pipeline_jobs(id) ON DELETE SET NULL, run_event_id uuid REFERENCES creation_knowledge.pipeline_run_events(id) ON DELETE SET NULL, stage text NOT NULL, substage text, provider text, model_name text, endpoint text, prompt_name text, prompt_hash text, trace_context jsonb NOT NULL DEFAULT '{}'::jsonb, request_payload jsonb NOT NULL DEFAULT '{}'::jsonb, response_payload jsonb NOT NULL DEFAULT '{}'::jsonb, parsed_payload jsonb NOT NULL DEFAULT '{}'::jsonb, status text NOT NULL DEFAULT 'pending', error_message text, latency_ms integer, attempt_index integer, created_at timestamptz NOT NULL DEFAULT now() ); CREATE TABLE IF NOT EXISTS creation_knowledge.contract_artifacts ( id uuid PRIMARY KEY DEFAULT gen_random_uuid(), contract_name text NOT NULL, contract_type text NOT NULL, version_label text, content_hash text NOT NULL, source_path text, snapshot jsonb NOT NULL DEFAULT '{}'::jsonb, created_at timestamptz NOT NULL DEFAULT now(), updated_at timestamptz NOT NULL DEFAULT now(), UNIQUE (contract_name, contract_type, source_path, content_hash) ); CREATE TABLE IF NOT EXISTS creation_knowledge.run_contract_artifacts ( pipeline_run_id uuid NOT NULL REFERENCES creation_knowledge.pipeline_runs(id) ON DELETE CASCADE, contract_artifact_id uuid NOT NULL REFERENCES creation_knowledge.contract_artifacts(id) ON DELETE CASCADE, created_at timestamptz NOT NULL DEFAULT now(), PRIMARY KEY (pipeline_run_id, contract_artifact_id) ); CREATE INDEX IF NOT EXISTS idx_queries_batch ON creation_knowledge.queries(batch_id); CREATE INDEX IF NOT EXISTS idx_acquisition_runs_batch ON creation_knowledge.acquisition_runs(batch_id); CREATE INDEX IF NOT EXISTS idx_acquisition_jobs_run ON creation_knowledge.acquisition_jobs(run_id); CREATE INDEX IF NOT EXISTS idx_candidate_items_job ON creation_knowledge.candidate_items(job_id); CREATE INDEX IF NOT EXISTS idx_candidate_items_platform_item ON creation_knowledge.candidate_items(platform, platform_item_id); CREATE INDEX IF NOT EXISTS idx_media_assets_item ON creation_knowledge.media_assets(item_id); CREATE INDEX IF NOT EXISTS idx_item_classifications_item ON creation_knowledge.item_classifications(item_id); CREATE INDEX IF NOT EXISTS idx_decode_jobs_item ON creation_knowledge.decode_jobs(item_id); CREATE INDEX IF NOT EXISTS idx_decode_results_item ON creation_knowledge.decode_results(item_id); CREATE INDEX IF NOT EXISTS idx_knowledge_particles_item ON creation_knowledge.knowledge_particles(item_id); CREATE INDEX IF NOT EXISTS idx_scope_results_particle ON creation_knowledge.scope_results(particle_id); CREATE INDEX IF NOT EXISTS idx_payload_drafts_particle ON creation_knowledge.payload_drafts(particle_id); CREATE INDEX IF NOT EXISTS idx_ingest_records_payload ON creation_knowledge.ingest_records(payload_draft_id); CREATE INDEX IF NOT EXISTS idx_pipeline_runs_batch ON creation_knowledge.pipeline_runs(batch_id); CREATE INDEX IF NOT EXISTS idx_pipeline_runs_status ON creation_knowledge.pipeline_runs(status, current_stage); CREATE INDEX IF NOT EXISTS idx_pipeline_jobs_run ON creation_knowledge.pipeline_jobs(pipeline_run_id); CREATE INDEX IF NOT EXISTS idx_pipeline_jobs_stage_status ON creation_knowledge.pipeline_jobs(stage, status); CREATE INDEX IF NOT EXISTS idx_pipeline_run_events_run ON creation_knowledge.pipeline_run_events(pipeline_run_id, created_at); CREATE INDEX IF NOT EXISTS idx_pipeline_run_events_acq_run ON creation_knowledge.pipeline_run_events(acquisition_run_id); CREATE INDEX IF NOT EXISTS idx_pipeline_run_events_query ON creation_knowledge.pipeline_run_events(query_id); CREATE INDEX IF NOT EXISTS idx_pipeline_run_events_item ON creation_knowledge.pipeline_run_events(item_id); CREATE INDEX IF NOT EXISTS idx_candidate_item_hits_pipeline_run ON creation_knowledge.candidate_item_hits(pipeline_run_id); CREATE INDEX IF NOT EXISTS idx_candidate_item_hits_acq_run ON creation_knowledge.candidate_item_hits(acquisition_run_id); CREATE INDEX IF NOT EXISTS idx_candidate_item_hits_query ON creation_knowledge.candidate_item_hits(query_id); CREATE INDEX IF NOT EXISTS idx_candidate_item_hits_item ON creation_knowledge.candidate_item_hits(item_id); CREATE INDEX IF NOT EXISTS idx_candidate_item_hits_unique_key ON creation_knowledge.candidate_item_hits(unique_key); CREATE INDEX IF NOT EXISTS idx_llm_call_traces_run_stage ON creation_knowledge.llm_call_traces(pipeline_run_id, stage, substage); CREATE INDEX IF NOT EXISTS idx_llm_call_traces_item ON creation_knowledge.llm_call_traces((trace_context ->> 'item_id')); CREATE INDEX IF NOT EXISTS idx_llm_call_traces_status ON creation_knowledge.llm_call_traces(status); CREATE INDEX IF NOT EXISTS idx_contract_artifacts_name_hash ON creation_knowledge.contract_artifacts(contract_name, contract_type, source_path, content_hash); CREATE INDEX IF NOT EXISTS idx_run_contract_artifacts_artifact ON creation_knowledge.run_contract_artifacts(contract_artifact_id); DO $$ DECLARE table_name text; BEGIN FOREACH table_name IN ARRAY ARRAY[ 'query_batches', 'queries', 'acquisition_runs', 'acquisition_jobs', 'candidate_items', 'media_assets', 'item_classifications', 'decode_jobs', 'decode_results', 'knowledge_particles', 'scope_results', 'payload_drafts', 'ingest_records', 'pipeline_runs', 'pipeline_jobs', 'contract_artifacts' ] LOOP EXECUTE format('DROP TRIGGER IF EXISTS trg_%I_touch_updated_at ON creation_knowledge.%I', table_name, table_name); EXECUTE format( 'CREATE TRIGGER trg_%I_touch_updated_at BEFORE UPDATE ON creation_knowledge.%I FOR EACH ROW EXECUTE FUNCTION creation_knowledge.touch_updated_at()', table_name, table_name ); END LOOP; END; $$; DO $$ BEGIN IF EXISTS (SELECT 1 FROM pg_roles WHERE rolname = 'ck_app') THEN GRANT CONNECT ON DATABASE creation_knowledge_prod TO ck_app; GRANT USAGE ON SCHEMA creation_knowledge TO ck_app; GRANT SELECT, INSERT, UPDATE, DELETE ON ALL TABLES IN SCHEMA creation_knowledge TO ck_app; GRANT USAGE, SELECT, UPDATE ON ALL SEQUENCES IN SCHEMA creation_knowledge TO ck_app; ALTER DEFAULT PRIVILEGES IN SCHEMA creation_knowledge GRANT SELECT, INSERT, UPDATE, DELETE ON TABLES TO ck_app; ALTER DEFAULT PRIVILEGES IN SCHEMA creation_knowledge GRANT USAGE, SELECT, UPDATE ON SEQUENCES TO ck_app; ALTER ROLE ck_app IN DATABASE creation_knowledge_prod SET search_path = creation_knowledge, public; END IF; END; $$; INSERT INTO creation_knowledge.schema_migrations(version, description) VALUES ('001_creation_knowledge_schema', 'formal creation knowledge cloud schema') ON CONFLICT (version) DO UPDATE SET description = EXCLUDED.description, applied_at = now();