-- User-turn origin for birth-time rectification. -- Typed, choice, suggestion, voice, retry, and recovery writes must be -- distinguishable when a later transcript contains a year the engine never -- produced. Business schema only; do not copy into frontend/db/migrations -- (BUG-127 / BUG-144). begin; alter table public.agentic_rectification_turns add column if not exists message_origin text; alter table public.agentic_rectification_turns add column if not exists client_action_id uuid; alter table public.agentic_rectification_turns add column if not exists content_hash text; do $$ begin if not exists ( select 1 from pg_constraint where conname = 'agentic_rectification_turns_message_origin_check' and conrelid = 'public.agentic_rectification_turns'::regclass ) then alter table public.agentic_rectification_turns add constraint agentic_rectification_turns_message_origin_check check ( message_origin is null or message_origin in ( 'typed', 'suggestion_click', 'choice_click', 'voice_input', 'retry_replay', 'system_recovery' ) ); end if; end; $$; create unique index if not exists agentic_rectification_turns_case_client_action_uidx on public.agentic_rectification_turns (case_id, client_action_id) where client_action_id is not null; create or replace function public.record_agentic_rectification_turn_origin( p_user_id uuid, p_case_id uuid, p_turn_id uuid, p_origin text, p_client_action_id uuid, p_content_hash text ) returns jsonb language plpgsql security definer set search_path = '' as $$ declare v_case public.agentic_rectification_cases%rowtype; v_turn public.agentic_rectification_turns%rowtype; begin if p_user_id is null or p_case_id is null or p_turn_id is null or p_origin not in ( 'typed', 'suggestion_click', 'choice_click', 'voice_input', 'retry_replay', 'system_recovery' ) 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; select * into v_turn from public.agentic_rectification_turns where id = p_turn_id and case_id = p_case_id for update; if not found then raise exception 'agentic_rectification_turn_not_found' using errcode = 'P0001'; end if; if v_turn.message_origin is not null then if v_turn.message_origin is distinct from p_origin or v_turn.client_action_id is distinct from p_client_action_id or v_turn.content_hash is distinct from p_content_hash then raise exception 'agentic_rectification_request_mismatch' using errcode = 'P0001'; end if; return jsonb_build_object( 'turn_id', v_turn.id, 'origin', v_turn.message_origin, 'client_action_id', v_turn.client_action_id, 'content_hash', v_turn.content_hash, 'idempotent', true ); end if; update public.agentic_rectification_turns set message_origin = p_origin, client_action_id = p_client_action_id, content_hash = p_content_hash, updated_at = pg_catalog.now() where id = p_turn_id; return jsonb_build_object( 'turn_id', p_turn_id, 'origin', p_origin, 'client_action_id', p_client_action_id, 'content_hash', p_content_hash, 'idempotent', false ); end; $$; revoke all on function public.record_agentic_rectification_turn_origin( uuid, uuid, uuid, text, uuid, text ) from public, anon, authenticated; grant execute on function public.record_agentic_rectification_turn_origin( uuid, uuid, uuid, text, uuid, text ) to service_role; commit;