-- Rectification focus RPC and persisted activity receipt consistency. -- Created 2026-08-15. Forward-only repair for the deployed V10 contract. begin; create or replace function public.set_agentic_rectification_conversation_focus( p_user_id uuid, p_case_id uuid, p_question_id text, p_intent text, p_target_evidence_id uuid, p_target_domain text, p_target_kind text, p_expected_answer_schema jsonb ) returns jsonb language plpgsql security definer set search_path = '' as $$ declare v_case public.agentic_rectification_cases%rowtype; v_focus public.agentic_rectification_conversation_focuses%rowtype; begin if p_user_id is null or p_case_id is null or length(btrim(coalesce(p_question_id, ''))) = 0 or length(btrim(coalesce(p_intent, ''))) = 0 or p_expected_answer_schema is null or jsonb_typeof(p_expected_answer_schema) <> 'object' then raise exception 'agentic_rectification_invalid_input' using errcode = 'P0001'; end if; select * into v_case from public.agentic_rectification_cases where id = p_case_id and user_id = p_user_id for update; if not found then raise exception 'agentic_rectification_case_not_found' using errcode = 'P0001'; end if; if v_case.status in ('confirmed', 'closed', 'abandoned', 'superseded') then raise exception 'agentic_rectification_case_terminal' using errcode = 'P0001'; end if; if p_target_evidence_id is not null and not exists ( select 1 from public.agentic_rectification_evidence where id = p_target_evidence_id and case_id = p_case_id ) then raise exception 'agentic_rectification_evidence_not_found' using errcode = 'P0001'; end if; select * into v_focus from public.agentic_rectification_conversation_focuses where case_id = p_case_id and question_id = p_question_id; if found then if v_focus.status = 'active' and v_focus.intent = p_intent and v_focus.target_evidence_id is not distinct from p_target_evidence_id and v_focus.target_domain is not distinct from p_target_domain and v_focus.target_kind is not distinct from p_target_kind and v_focus.expected_answer_schema = p_expected_answer_schema then return jsonb_build_object( 'focus', to_jsonb(v_focus), 'idempotent', true ); end if; raise exception 'agentic_rectification_focus_idempotency_conflict' using errcode = 'P0001'; end if; update public.agentic_rectification_conversation_focuses set status = 'superseded', resolved_at = pg_catalog.now(), updated_at = pg_catalog.now() where case_id = p_case_id and status = 'active'; insert into public.agentic_rectification_conversation_focuses ( case_id, question_id, intent, target_evidence_id, target_domain, target_kind, expected_answer_schema, status, asked_at ) values ( p_case_id, p_question_id, p_intent, p_target_evidence_id, p_target_domain, p_target_kind, p_expected_answer_schema, 'active', pg_catalog.now() ) returning * into v_focus; return jsonb_build_object( 'focus', to_jsonb(v_focus), 'idempotent', false ); end; $$; revoke all on function public.set_agentic_rectification_conversation_focus( uuid, uuid, text, text, uuid, text, text, jsonb ) from public, anon, authenticated; grant execute on function public.set_agentic_rectification_conversation_focus( uuid, uuid, text, text, uuid, text, text, jsonb ) to service_role; create or replace function public.get_agentic_rectification_turn_receipt( p_user_id uuid, p_case_id uuid, p_turn_id uuid ) returns jsonb language plpgsql security definer set search_path = '' as $$ declare v_turn public.agentic_rectification_turns%rowtype; v_case public.agentic_rectification_cases%rowtype; v_attempt_id uuid; v_phases jsonb; v_tool_activities jsonb; v_tools jsonb; v_methods jsonb; v_engine_version text; begin if p_user_id is null or p_case_id is null or p_turn_id is null then raise exception 'agentic_rectification_invalid_input' using errcode = 'P0001'; end if; select * into v_case from public.agentic_rectification_cases where id = p_case_id and user_id = p_user_id; if not found then raise exception 'agentic_rectification_case_not_found' using errcode = 'P0001'; end if; select * into v_turn from public.agentic_rectification_turns where id = p_turn_id and case_id = p_case_id; if not found then raise exception 'agentic_rectification_turn_not_found' using errcode = 'P0001'; end if; v_attempt_id := v_turn.successful_attempt_id; if v_attempt_id is null then select id into v_attempt_id from public.agentic_rectification_run_attempts where turn_id = p_turn_id order by attempt_number desc limit 1; end if; select coalesce(jsonb_agg( jsonb_build_object('phase', rp.phase, 'tool', rp.tool_name) order by rp.sequence, rp.created_at, rp.id ), '[]'::jsonb) into v_phases from public.agentic_rectification_run_phases rp where rp.turn_id = p_turn_id and ( (v_attempt_id is not null and rp.attempt_id = v_attempt_id) or (v_attempt_id is null and rp.attempt_id is null) ); with latest_terminal as materialized ( select distinct on (tr.tool_name) tr.id, tr.tool_name, tr.status, tr.executed_methods, tr.started_at from public.agentic_rectification_tool_receipts tr where tr.turn_id = p_turn_id and tr.status in ('completed', 'failed') and ( (v_attempt_id is not null and tr.attempt_id = v_attempt_id) or (v_attempt_id is null and tr.attempt_id is null) ) order by tr.tool_name, tr.started_at desc, tr.id desc ) select coalesce(jsonb_agg( jsonb_build_object( 'tool', terminal.tool_name, 'status', terminal.status, 'methods', case when terminal.status = 'completed' then terminal.executed_methods else '[]'::jsonb end ) order by terminal.started_at, terminal.id ), '[]'::jsonb), coalesce(jsonb_agg(terminal.tool_name order by terminal.started_at, terminal.id) filter (where terminal.status = 'completed'), '[]'::jsonb) into v_tool_activities, v_tools from latest_terminal terminal; with latest_terminal as materialized ( select distinct on (tr.tool_name) tr.tool_name, tr.status, tr.executed_methods from public.agentic_rectification_tool_receipts tr where tr.turn_id = p_turn_id and tr.status in ('completed', 'failed') and ( (v_attempt_id is not null and tr.attempt_id = v_attempt_id) or (v_attempt_id is null and tr.attempt_id is null) ) order by tr.tool_name, tr.started_at desc, tr.id desc ) select coalesce(jsonb_agg(method order by method), '[]'::jsonb) into v_methods from ( select distinct jsonb_array_elements_text(terminal.executed_methods) as method from latest_terminal terminal where terminal.status = 'completed' ) methods; select max(tr.engine_version) into v_engine_version from public.agentic_rectification_tool_receipts tr where tr.turn_id = p_turn_id and tr.engine_version is not null and ( (v_attempt_id is not null and tr.attempt_id = v_attempt_id) or (v_attempt_id is null and tr.attempt_id is null) ); return jsonb_build_object( 'turn_id', v_turn.id, 'attempt_id', v_attempt_id, 'status', v_turn.status, 'skill_name', v_case.skill_name, 'skill_version', v_case.skill_version, 'engine_version', v_engine_version, 'phases', v_phases, 'tool_activities', v_tool_activities, 'tools', v_tools, 'methods', v_methods, 'started_at', v_turn.created_at, 'completed_at', v_turn.completed_at ); end; $$; revoke all on function public.get_agentic_rectification_turn_receipt(uuid, uuid, uuid) from public, anon, authenticated; grant execute on function public.get_agentic_rectification_turn_receipt(uuid, uuid, uuid) to service_role; commit;