-- 005: add query planning state before the frozen acquisition boundary. CREATE TABLE IF NOT EXISTS creation_knowledge.query_plans ( id uuid PRIMARY KEY DEFAULT gen_random_uuid(), generator_kind text NOT NULL CHECK ( generator_kind IN ('cartesian', 'manual', 'topic_table', 'agent_plan', 'history_gap') ), status text NOT NULL DEFAULT 'ready' CHECK (status IN ('planning', 'ready', 'failed')), input_snapshot jsonb NOT NULL DEFAULT '{}'::jsonb, generator_config jsonb NOT NULL DEFAULT '{}'::jsonb, max_queries integer CHECK (max_queries IS NULL OR max_queries >= 0), generated_count integer NOT NULL DEFAULT 0 CHECK (generated_count >= 0), normalized_count integer NOT NULL DEFAULT 0 CHECK (normalized_count >= 0), unique_count integer NOT NULL DEFAULT 0 CHECK (unique_count >= 0), selected_count integer NOT NULL DEFAULT 0 CHECK (selected_count >= 0), dropped_count integer NOT NULL DEFAULT 0 CHECK (dropped_count >= 0), metadata jsonb NOT NULL DEFAULT '{}'::jsonb, created_at timestamptz NOT NULL DEFAULT now(), updated_at timestamptz NOT NULL DEFAULT now(), CHECK (normalized_count <= generated_count), CHECK (unique_count <= normalized_count), CHECK (selected_count <= unique_count), CHECK (dropped_count = generated_count - selected_count) ); CREATE TABLE IF NOT EXISTS creation_knowledge.knowledge_needs ( id uuid PRIMARY KEY DEFAULT gen_random_uuid(), plan_id uuid NOT NULL REFERENCES creation_knowledge.query_plans(id) ON DELETE CASCADE, need_key text NOT NULL, decision_context text NOT NULL, unknown_information text NOT NULL, source_ref jsonb NOT NULL DEFAULT '{}'::jsonb, priority integer NOT NULL DEFAULT 0, metadata jsonb NOT NULL DEFAULT '{}'::jsonb, created_at timestamptz NOT NULL DEFAULT now(), UNIQUE (plan_id, need_key) ); CREATE TABLE IF NOT EXISTS creation_knowledge.query_plan_batches ( plan_id uuid NOT NULL REFERENCES creation_knowledge.query_plans(id) ON DELETE CASCADE, batch_id uuid NOT NULL REFERENCES creation_knowledge.query_batches(id) ON DELETE CASCADE, created_at timestamptz NOT NULL DEFAULT now(), PRIMARY KEY (plan_id, batch_id) ); CREATE TABLE IF NOT EXISTS creation_knowledge.query_knowledge_need_links ( query_id uuid NOT NULL REFERENCES creation_knowledge.queries(id) ON DELETE CASCADE, knowledge_need_id uuid NOT NULL REFERENCES creation_knowledge.knowledge_needs(id) ON DELETE CASCADE, created_at timestamptz NOT NULL DEFAULT now(), PRIMARY KEY (query_id, knowledge_need_id) ); CREATE INDEX IF NOT EXISTS idx_query_plans_kind_created ON creation_knowledge.query_plans(generator_kind, created_at); CREATE INDEX IF NOT EXISTS idx_knowledge_needs_plan_priority ON creation_knowledge.knowledge_needs(plan_id, priority DESC); CREATE INDEX IF NOT EXISTS idx_query_plan_batches_batch ON creation_knowledge.query_plan_batches(batch_id); CREATE INDEX IF NOT EXISTS idx_query_knowledge_need_links_need ON creation_knowledge.query_knowledge_need_links(knowledge_need_id); DROP TRIGGER IF EXISTS trg_query_plans_touch_updated_at ON creation_knowledge.query_plans; CREATE TRIGGER trg_query_plans_touch_updated_at BEFORE UPDATE ON creation_knowledge.query_plans FOR EACH ROW EXECUTE FUNCTION creation_knowledge.touch_updated_at(); DO $$ BEGIN IF EXISTS (SELECT 1 FROM pg_roles WHERE rolname = 'ck_app') THEN GRANT SELECT, INSERT, UPDATE, DELETE ON creation_knowledge.query_plans, creation_knowledge.knowledge_needs, creation_knowledge.query_plan_batches, creation_knowledge.query_knowledge_need_links TO ck_app; END IF; END; $$; INSERT INTO creation_knowledge.schema_migrations(version, description) VALUES ('005_query_planning_schema', 'Add query plans, knowledge needs, and legacy query links') ON CONFLICT (version) DO UPDATE SET description = EXCLUDED.description, applied_at = now();