-- Preserve both logical messages stored by a completed rectification turn. -- A normal turn stores user_message and assistant_message on the same physical -- row; the dossier must expose both in conversational order. Business schema -- only: do not copy this migration into frontend/db/migrations. begin; create or replace function public.get_agentic_rectification_case_dossier( p_user_id uuid, p_case_id uuid ) returns jsonb language plpgsql security definer set search_path = '' as $$ declare v_case public.agentic_rectification_cases%rowtype; v_turns jsonb; v_evidence jsonb; v_result public.agentic_rectification_results%rowtype; v_evidence_count bigint; v_turn_count bigint; begin if p_user_id is null or p_case_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 coalesce(jsonb_agg( jsonb_build_object( 'id', t.id, 'role', message.role, 'text', message.text, 'status', t.status, 'created_at', t.created_at, 'completed_at', t.completed_at ) order by t.created_at, message.ordinal ), '[]'::jsonb) into v_turns from public.agentic_rectification_turns t cross join lateral ( values (1, 'user'::text, t.user_message), (2, 'assistant'::text, t.assistant_message) ) as message(ordinal, role, text) where t.case_id = v_case.id and message.text is not null; select coalesce(jsonb_agg( jsonb_build_object( 'id', e.id, 'source_turn_id', e.source_turn_id, 'subject', e.subject, 'event_kind', e.event_kind, 'domain', e.domain, 'occurred_from', e.occurred_from, 'occurred_to', e.occurred_to, 'date_precision', e.date_precision, 'summary', e.summary, 'status', e.status, 'supersedes_evidence_id', e.supersedes_evidence_id, 'created_at', e.created_at ) order by e.created_at ), '[]'::jsonb) into v_evidence from public.agentic_rectification_evidence e where e.case_id = v_case.id; select count(*) into v_evidence_count from public.agentic_rectification_evidence where case_id = v_case.id; select count(*) into v_turn_count from public.agentic_rectification_turns where case_id = v_case.id; select * into v_result from public.agentic_rectification_results where case_id = v_case.id and invalidated_at is null order by created_at desc limit 1; return jsonb_build_object( 'case', jsonb_build_object( 'case_id', v_case.id, 'session_id', v_case.session_id, 'status', v_case.status, 'skill_name', v_case.skill_name, 'skill_version', v_case.skill_version, 'candidate_range', v_case.candidate_range, 'accepted_time', v_case.accepted_time, 'confirmed_time', v_case.confirmed_time, 'completed_at', v_case.completed_at, 'closed_reason', v_case.closed_reason, 'last_activity_at', v_case.last_activity_at, 'evidence_count', v_evidence_count, 'turn_count', v_turn_count ), 'turns', v_turns, 'evidence', v_evidence, 'latest_result', case when v_result.id is null then null else jsonb_build_object( 'result_id', v_result.id, 'candidates', v_result.candidates, 'overall_confidence', v_result.overall_confidence, 'selection_allowed', v_result.selection_allowed, 'confirmation_allowed', v_result.confirmation_allowed, 'representative_time', v_result.representative_time, 'selected_time', v_result.selected_time, 'selection_kind', v_result.selection_kind, 'evidence_ledger_fingerprint', v_result.evidence_ledger_fingerprint, 'candidate_range_fingerprint', v_result.candidate_range_fingerprint, 'skill_version', v_result.skill_version, 'algorithm_version', v_result.algorithm_version, 'created_at', v_result.created_at, 'invalidated_at', v_result.invalidated_at ) end ); end; $$; revoke all on function public.get_agentic_rectification_case_dossier(uuid, uuid) from public, anon, authenticated; grant execute on function public.get_agentic_rectification_case_dossier(uuid, uuid) to service_role; commit;