begin; -- BUG-177: starting birth-time rectification from the homepage is an explicit -- create action. Existing unfinished cases remain resumable through their -- exact history/session entry, but no longer block a separate new case. drop index if exists public.agentic_rectification_cases_one_resumable_per_user; create or replace function public.open_agentic_rectification_case( p_user_id uuid, p_request_id uuid, p_intent text, p_session_id uuid, p_skill_name text, p_skill_version text, p_baseline_profile_fingerprint text, p_baseline_birth_snapshot jsonb, p_candidate_range jsonb ) returns jsonb language plpgsql security definer set search_path = '' as $$ declare v_case public.agentic_rectification_cases%rowtype; v_session public.chat_sessions%rowtype; v_session_id uuid; begin if p_user_id is null or p_request_id is null then raise exception 'agentic_rectification_invalid_input' using errcode = 'P0001'; end if; if p_intent not in ('homepage', 'session', 'new') then raise exception 'agentic_rectification_invalid_intent' using errcode = 'P0001'; end if; if length(btrim(p_skill_name)) = 0 or length(btrim(p_skill_version)) = 0 or length(btrim(coalesce(p_baseline_profile_fingerprint, ''))) = 0 then raise exception 'agentic_rectification_invalid_input' using errcode = 'P0001'; end if; -- Serialize same-user opens so a repeated requestId is deterministic. A -- separate click carries a separate requestId and is allowed to create. perform pg_catalog.pg_advisory_xact_lock( pg_catalog.hashtext('agentic_rectification_open:' || p_user_id::text) ); select c.* into v_case from public.agentic_rectification_cases c join public.agentic_rectification_open_ledger l on l.case_id = c.id and l.session_id = c.session_id where l.user_id = p_user_id and l.request_id = p_request_id limit 1; if found then return jsonb_build_object( 'disposition', case when v_case.status = any (public.agentic_rectification_resumable_statuses()) then 'resumed' else 'readonly' end, 'case_id', v_case.id, 'session_id', v_case.session_id, 'status', v_case.status, 'should_start_opening', false, 'skill_version', v_case.skill_version ); end if; if p_intent = 'session' then if p_session_id is null then raise exception 'agentic_rectification_invalid_input' using errcode = 'P0001'; end if; select * into v_session from public.chat_sessions where id = p_session_id and user_id = p_user_id; if not found then raise exception 'agentic_rectification_session_not_found' using errcode = 'P0001'; end if; if v_session.session_type <> 'birth_time_rectification' then raise exception 'agentic_rectification_session_not_rectification' using errcode = 'P0001'; end if; select * into v_case from public.agentic_rectification_cases where session_id = p_session_id; if not found then raise exception 'agentic_rectification_case_not_found' using errcode = 'P0001'; end if; return jsonb_build_object( 'disposition', case when v_case.status = any (public.agentic_rectification_resumable_statuses()) then 'resumed' else 'readonly' end, 'case_id', v_case.id, 'session_id', v_case.session_id, 'status', v_case.status, 'should_start_opening', false, 'skill_version', v_case.skill_version ); end if; if p_baseline_birth_snapshot is null or jsonb_typeof(p_baseline_birth_snapshot) <> 'object' or p_baseline_birth_snapshot ->> 'birth_date' is null or p_baseline_birth_snapshot ->> 'latitude' is null or p_baseline_birth_snapshot ->> 'longitude' is null or p_baseline_birth_snapshot ->> 'timezone_offset' is null or length(btrim(coalesce(p_baseline_birth_snapshot ->> 'birth_time_source', ''))) = 0 then raise exception 'agentic_rectification_profile_incomplete' using errcode = 'P0001'; end if; if p_candidate_range is null or jsonb_typeof(p_candidate_range) <> 'object' or not (p_candidate_range ->> 'start_time') ~ '^([01][0-9]|2[0-3]):[0-5][0-9]$' or not (p_candidate_range ->> 'end_time') ~ '^([01][0-9]|2[0-3]):[0-5][0-9]$' then raise exception 'agentic_rectification_invalid_range' using errcode = 'P0001'; end if; insert into public.chat_sessions (user_id, title, theme, session_type, messages) values (p_user_id, '生时校正', 'general', 'birth_time_rectification', '[]'::jsonb) returning id into v_session_id; insert into public.agentic_rectification_cases ( user_id, session_id, status, skill_name, skill_version, baseline_profile_fingerprint, baseline_birth_snapshot, candidate_range ) values ( p_user_id, v_session_id, 'draft', p_skill_name, p_skill_version, p_baseline_profile_fingerprint, p_baseline_birth_snapshot, p_candidate_range ) returning * into v_case; insert into public.agentic_rectification_open_ledger ( request_id, user_id, case_id, session_id, intent ) values ( p_request_id, p_user_id, v_case.id, v_session_id, p_intent ); return jsonb_build_object( 'disposition', 'created', 'case_id', v_case.id, 'session_id', v_session_id, 'status', v_case.status, 'should_start_opening', true, 'skill_version', v_case.skill_version ); end; $$; revoke all on function public.open_agentic_rectification_case(uuid, uuid, text, uuid, text, text, text, jsonb, jsonb) from public, anon, authenticated; grant execute on function public.open_agentic_rectification_case(uuid, uuid, text, uuid, text, text, text, jsonb, jsonb) to service_role; commit;