Files
Jyotisha/frontend/supabase/migrations/20261001010000_rectification_segment_adoption.sql
T
Jesse_ChenandClaude Opus 5.5 73605c3b8e fix(rectification): product-domain open wrapper runs as invoker; V10 turn append in history test and replay harness; rename segment migrations (BUG-1131)
- open_agentic_rectification_case_v2 (12 args) becomes SECURITY INVOKER: the
  immutable-skill ACL reconciliation leaves EXECUTE on the 11-arg open only to
  service_role, so the definer wrapper hit 42501 on every homepage/new open.
- The pending-opening read moves to owner function
  agentic_rectification_opening_pending_v1, granted to service_role only.
- History test and persisted replay harness append turns through the V10
  request-idempotent overload; the V9 overload is revoked from service_role.
- Opening test fixture adds the required birth_time_source.
- Segment migrations renamed to 20261001* so they sort after staging's
  20260930* migrations on both fresh and existing databases.
- Stale Windows replay replaced with the production-path replay (M1/M2 equal
  to accepted research, implementation_identity included).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017eEAG8HD3mm8gsKXgk8uU8
2026-10-01 08:04:05 +08:00

441 lines
30 KiB
PL/PgSQL

-- A segment minute is an adoption output, not a new engine candidate or confirmation.
begin;
create function public.rectification_segment_adoption_minute(
p_case public.agentic_rectification_cases,
p_result public.agentic_rectification_results,
p_state jsonb
) returns jsonb language plpgsql security definer set search_path = '' as $$
declare
v_summary jsonb := p_state -> 'segment_summary';
v_minutes jsonb := p_state -> 'segment_minutes';
v_base jsonb := p_result.decision_receipt -> 'inference_state';
v_targets jsonb;
v_chart jsonb;
v_segments jsonb;
v_shares jsonb;
v_weights jsonb;
v_candidate jsonb;
v_payload jsonb;
v_minute jsonb;
v_top integer;
v_alive integer;
v_share double precision;
v_tied boolean;
v_sign integer;
v_tier text;
v_unique boolean;
v_all_unique boolean := true;
v_width integer;
v_origin timestamp;
v_end timestamp;
v_reported integer;
v_start_clock integer;
v_delta integer;
v_start integer;
v_finish integer;
v_intersection_start integer := 0;
v_intersection_end integer;
v_fallback_start integer;
v_fallback_end integer;
v_priority integer := 4;
v_current_priority integer;
v_midpoint integer;
v_adoption jsonb;
v_peak double precision;
v_index integer;
begin
if jsonb_typeof(v_summary) is distinct from 'object'
or v_summary -> 'scan_complete' is distinct from 'true'::jsonb
or p_state -> 'segment_scan_complete' is distinct from 'true'::jsonb
or jsonb_typeof(v_minutes) is distinct from 'array'
or jsonb_typeof(v_summary -> 'targets') is distinct from 'array'
or jsonb_typeof(v_summary -> 'charts') is distinct from 'array'
or coalesce(v_summary ->> 'window_minutes', '') !~ '^[0-9]+$' then
raise exception 'agentic_rectification_segment_state_inconsistent';
end if;
v_width := (v_summary ->> 'window_minutes')::integer;
v_targets := v_summary -> 'targets';
if v_width < 1 or v_width > 1440 or jsonb_array_length(v_minutes) <> v_width
or jsonb_array_length(v_targets) not between 1 and 3
or jsonb_array_length(v_summary -> 'charts') <> jsonb_array_length(v_targets)
or p_state -> 'segment_targets' is distinct from v_targets
or v_minutes is distinct from v_base -> 'segment_minutes'
or v_targets is distinct from v_base -> 'segment_targets'
or (select count(distinct value) from jsonb_array_elements(v_targets)) <> jsonb_array_length(v_targets)
or exists (select 1 from jsonb_array_elements_text(v_targets) x where x not in ('D1','D9','D10')) then
raise exception 'agentic_rectification_segment_state_inconsistent';
end if;
-- A complete proof covers one contiguous, dated window; disjoint/24h identities fail closed.
if p_result.decision_receipt ->> 'candidate_window_contract' = 'dated-v1' then
if jsonb_array_length(p_result.decision_receipt -> 'candidate_intervals') <> 1
or p_result.decision_receipt -> 'candidate_intervals' is distinct from p_case.candidate_range -> 'candidate_intervals' then
raise exception 'agentic_rectification_segment_state_inconsistent';
end if;
v_origin := (p_result.decision_receipt -> 'candidate_intervals' -> 0 ->> 'start_at')::timestamp;
v_end := (p_result.decision_receipt -> 'candidate_intervals' -> 0 ->> 'end_at')::timestamp;
else
v_start_clock := extract(hour from (p_state ->> 'range_start')::time)::integer * 60
+ extract(minute from (p_state ->> 'range_start')::time)::integer;
v_origin := (p_case.baseline_birth_snapshot ->> 'birth_date')::date + make_interval(mins => v_start_clock);
if p_case.baseline_birth_snapshot ->> 'reported_birth_time' is not null then
v_reported := extract(hour from (p_case.baseline_birth_snapshot ->> 'reported_birth_time')::time)::integer * 60
+ extract(minute from (p_case.baseline_birth_snapshot ->> 'reported_birth_time')::time)::integer;
v_delta := v_start_clock - v_reported;
if v_delta > 720 then v_origin := v_origin - interval '1 day';
elsif v_delta < -720 then v_origin := v_origin + interval '1 day'; end if;
elsif p_case.baseline_birth_snapshot ->> 'declared_window_end' < p_case.baseline_birth_snapshot ->> 'declared_window_start'
and p_state ->> 'range_start' < p_case.baseline_birth_snapshot ->> 'declared_window_start' then
v_origin := v_origin + interval '1 day';
end if;
v_end := v_origin + make_interval(mins => (extract(hour from (p_state ->> 'range_end')::time)::integer * 60
+ extract(minute from (p_state ->> 'range_end')::time)::integer - v_start_clock + 1440) % 1440);
end if;
if v_origin is null or v_end is null or extract(epoch from (v_end - v_origin))/60 + 1 <> v_width then
raise exception 'agentic_rectification_segment_state_inconsistent';
end if;
v_index := 0;
for v_minute in select value from jsonb_array_elements(v_minutes) loop
if v_minute -> 'offset' is distinct from to_jsonb(v_index)
or v_minute ->> 'time' is distinct from to_char(v_origin + make_interval(mins => v_index), 'HH24:MI')
or v_minute ->> 'date' is distinct from to_char(v_origin + make_interval(mins => v_index), 'YYYY-MM-DD')
or jsonb_typeof(v_minute -> 'signs') is distinct from 'object'
or exists (select 1 from jsonb_array_elements_text(v_targets) target
where coalesce(v_minute -> 'signs' ->> target, '') !~ '^([0-9]|1[01])$') then
raise exception 'agentic_rectification_segment_state_inconsistent';
end if;
v_index := v_index + 1;
end loop;
-- Raw posterior is server-owned ledger output. Prior and real cluster members
-- must still identify the persisted engine candidate, never client-supplied mass.
for v_candidate in select value from jsonb_array_elements(p_state -> 'candidates') loop
select candidate_payload into v_payload from public.agentic_rectification_candidates
where result_id = p_result.id and user_id = p_case.user_id and case_id = p_case.id
and to_char(candidate_time, 'HH24:MI') = v_candidate ->> 'time';
if not found or jsonb_typeof(v_candidate -> 'raw_posterior_score') is distinct from 'number'
or jsonb_typeof(v_candidate -> 'raw_eliminated') is distinct from 'boolean'
or v_candidate -> 'raw_prior_score' is distinct from v_payload -> 'raw_score'
or jsonb_typeof(v_payload -> 'raw_score') is distinct from 'number'
or v_candidate -> 'cluster_times' is distinct from v_payload -> 'cluster_times'
or jsonb_typeof(v_candidate -> 'cluster_times') is distinct from 'array'
or jsonb_array_length(v_candidate -> 'cluster_times') = 0
or (select count(distinct value) from jsonb_array_elements(v_candidate -> 'cluster_times')) <> jsonb_array_length(v_candidate -> 'cluster_times')
or exists (select 1 from jsonb_array_elements_text(v_candidate -> 'cluster_times') member
where not exists (select 1 from jsonb_array_elements(v_minutes) m where m ->> 'time' = member)) then
raise exception 'agentic_rectification_segment_state_inconsistent';
end if;
end loop;
select max((value ->> 'raw_posterior_score')::double precision) into v_peak
from jsonb_array_elements(p_state -> 'candidates') where value -> 'raw_eliminated' = 'false'::jsonb;
select coalesce(jsonb_object_agg(clock, mass), '{}'::jsonb) into v_weights from (
select member as clock, sum(greatest((candidate ->> 'raw_posterior_score')::double precision, 0)
/ jsonb_array_length(candidate -> 'cluster_times')) as mass
from jsonb_array_elements(p_state -> 'candidates') candidate,
lateral jsonb_array_elements_text(candidate -> 'cluster_times') member
where candidate -> 'raw_eliminated' = 'false'::jsonb
and v_peak - (candidate ->> 'raw_posterior_score')::double precision < 8
group by member
) weights;
v_intersection_end := v_width - 1;
v_index := 0;
for v_chart in select value from jsonb_array_elements(v_summary -> 'charts') loop
if v_chart -> 'chart' is distinct from v_targets -> v_index then
raise exception 'agentic_rectification_segment_state_inconsistent';
end if;
with numbered as (
select (m ->> 'offset')::integer as minute_offset, (m -> 'signs' ->> (v_chart ->> 'chart'))::integer as sign,
lag((m -> 'signs' ->> (v_chart ->> 'chart'))::integer) over (order by (m ->> 'offset')::integer) as previous
from jsonb_array_elements(v_minutes) m
), runs as (
select *, sum(case when sign is distinct from previous then 1 else 0 end) over (order by minute_offset) - 1 as segment
from numbered
) select jsonb_agg(jsonb_build_object('index', segment, 'start', start, 'end', finish, 'key', jsonb_build_array(sign)) order by segment)
into v_segments from (select segment, min(minute_offset) as start, max(minute_offset) as finish, min(sign) as sign from runs group by segment) x;
if v_chart -> 'segments' is distinct from v_segments then
raise exception 'agentic_rectification_segment_state_inconsistent';
end if;
with mass as (
select (s ->> 'index')::integer as index, coalesce(sum((v_weights ->> (m ->> 'time'))::double precision), 0) as mass
from jsonb_array_elements(v_segments) s join jsonb_array_elements(v_minutes) m
on (m ->> 'offset')::integer between (s ->> 'start')::integer and (s ->> 'end')::integer
group by s
), shares as (
select *, case when sum(mass) over () > 0 then mass / sum(mass) over () else 0 end as share from mass
) select jsonb_agg(round(share::numeric, 6) order by index),
count(*) filter (where mass > 1e-9),
(array_agg(index order by share desc, index) filter (where mass > 1e-9))[1],
coalesce(max(share) filter (where mass > 1e-9), 0),
coalesce((array_agg(share order by share desc, index) filter (where mass > 1e-9))[1]
- (array_agg(share order by share desc, index) filter (where mass > 1e-9))[2] < 1e-9, false)
into v_shares, v_alive, v_top, v_share, v_tied from shares;
v_unique := jsonb_array_length(v_segments) = 1;
v_all_unique := v_all_unique and v_unique;
v_sign := case when v_top is not null then (v_segments -> v_top -> 'key' ->> 0)::integer
when v_unique then (v_segments -> 0 -> 'key' ->> 0)::integer else null end;
v_share := round(v_share::numeric, 6)::double precision;
v_tier := case when v_chart ->> 'chart' = 'D1' and v_unique then 'certain'
when v_chart ->> 'chart' <> 'D1' and v_width > 61 then 'blocked'
when v_share >= 0.6 then 'credible' when v_width <= 21 and v_share >= 0.5 then 'tentative' else 'indistinct' end;
if v_chart -> 'shares' is distinct from v_shares
or v_chart -> 'alive_segments' is distinct from to_jsonb(v_alive)
or v_chart -> 'top_segment' is distinct from coalesce(to_jsonb(v_top), 'null'::jsonb)
or v_chart -> 'top_share' is distinct from to_jsonb(v_share)
or v_chart -> 'tied' is distinct from to_jsonb(v_tied)
or v_chart -> 'full_window_unique' is distinct from to_jsonb(v_unique)
or v_chart -> 'sign' is distinct from coalesce(to_jsonb(v_sign), 'null'::jsonb)
or v_chart ->> 'tier' is distinct from v_tier then
raise exception 'agentic_rectification_segment_state_inconsistent';
end if;
if v_tier <> 'blocked' and v_top is not null then
v_start := (v_segments -> v_top ->> 'start')::integer;
v_finish := (v_segments -> v_top ->> 'end')::integer;
v_intersection_start := greatest(v_intersection_start, v_start);
v_intersection_end := least(v_intersection_end, v_finish);
v_current_priority := case v_chart ->> 'chart' when 'D1' then 0 when 'D9' then 1 else 2 end;
if v_current_priority < v_priority then
v_priority := v_current_priority; v_fallback_start := v_start; v_fallback_end := v_finish;
end if;
end if;
v_index := v_index + 1;
end loop;
if v_summary -> 'no_rectification_needed' is distinct from to_jsonb(v_all_unique) or v_fallback_start is null then
raise exception 'agentic_rectification_segment_state_inconsistent';
end if;
v_midpoint := case when v_intersection_start <= v_intersection_end
then (v_intersection_start + v_intersection_end) / 2 else (v_fallback_start + v_fallback_end) / 2 end;
select m into v_adoption from jsonb_array_elements(v_minutes) m
where case when jsonb_typeof(p_state -> 'credible_intervals') = 'array' and jsonb_array_length(p_state -> 'credible_intervals') > 0 then
exists (select 1 from jsonb_array_elements(p_state -> 'credible_intervals') r
where (m ->> 'date') || 'T' || (m ->> 'time') between r ->> 'start_at' and r ->> 'end_at')
else case when p_state -> 'credible_range' ->> 0 <= p_state -> 'credible_range' ->> 1
then m ->> 'time' between p_state -> 'credible_range' ->> 0 and p_state -> 'credible_range' ->> 1
else m ->> 'time' >= p_state -> 'credible_range' ->> 0 or m ->> 'time' <= p_state -> 'credible_range' ->> 1 end end
order by abs((m ->> 'offset')::integer - v_midpoint), (m ->> 'offset')::integer limit 1;
if v_adoption is null or v_summary -> 'adoption_minute' is distinct from v_adoption then
raise exception 'agentic_rectification_segment_state_inconsistent';
end if;
return v_adoption;
end;
$$;
revoke all on function public.rectification_segment_adoption_minute(public.agentic_rectification_cases, public.agentic_rectification_results, jsonb)
from public, anon, authenticated, service_role;
create function public.accept_agentic_rectification_segment_for_case_v1(
p_user_id uuid, p_case_id uuid, p_result_id uuid, p_candidate_id uuid, p_request_id uuid
) returns jsonb language plpgsql security definer set search_path = '' as $$
declare
v_case public.agentic_rectification_cases%rowtype;
v_result public.agentic_rectification_results%rowtype;
v_profile public.profiles%rowtype;
v_existing public.agentic_rectification_candidate_decisions%rowtype;
v_previous_response jsonb;
v_state jsonb;
v_minute jsonb;
v_response jsonb;
v_provenance jsonb;
v_date date;
v_time time;
v_offset double precision;
v_candidate public.agentic_rectification_candidates%rowtype;
v_candidate_count integer;
v_candidate_set text;
v_top_time text;
v_range_start text;
v_range_end text;
begin
if p_user_id is null or p_case_id is null or p_result_id is null or p_candidate_id is null or p_request_id is null then
raise exception 'agentic_rectification_candidate_invalid_input';
end if;
perform pg_advisory_xact_lock(hashtextextended(p_user_id::text || ':' || p_request_id::text, 0));
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'; end if;
select * into v_result from public.agentic_rectification_results where id = p_result_id and case_id = p_case_id and user_id = p_user_id for update;
if not found or not exists (select 1 from public.agentic_rectification_candidates
where id = p_candidate_id and result_id = p_result_id and case_id = p_case_id and user_id = p_user_id) then
raise exception 'agentic_rectification_candidate_not_found';
end if;
select * into v_existing from public.agentic_rectification_candidate_decisions where user_id = p_user_id and request_id = p_request_id for update;
if found then
if v_existing.decision_kind <> 'accept' or v_existing.case_id <> p_case_id or v_existing.result_id <> p_result_id or v_existing.candidate_id <> p_candidate_id then
raise exception 'agentic_rectification_candidate_request_conflict';
end if;
-- Old requests remain old receipts; a retry never changes the minute already adopted.
return jsonb_set(v_existing.response, '{idempotent}', 'true'::jsonb, true);
end if;
select inference_state into v_state from public.agentic_rectification_inference_transitions
where case_id = p_case_id and result_id = p_result_id order by revision desc limit 1;
v_state := coalesce(v_state, v_result.decision_receipt -> 'inference_state');
if not (v_state ? 'segment_summary') then
return public.accept_agentic_rectification_candidate_for_case_v2(p_user_id,p_case_id,p_result_id,p_candidate_id,p_request_id,null);
end if;
v_minute := public.rectification_segment_adoption_minute(v_case, v_result, v_state);
select * into v_profile from public.profiles where id = p_user_id for update;
if not found then raise exception 'agentic_rectification_candidate_profile_changed'; end if;
if v_result.selected_candidate_id is not null and v_case.adopted_credible_range ->> 'source' = 'server_segment_midpoint'
and (v_profile.active_birth_time is distinct from v_result.selected_time
or v_profile.active_birth_date is distinct from (v_case.adopted_credible_range ->> 'representative_date')::date
or v_profile.birth_time_status is distinct from 'accepted'
or v_profile.active_birth_timezone_offset is null
or v_profile.active_birth_timezone_offset not between -14 and 14
or v_profile.active_birth_timezone_offset is distinct from (v_profile.active_birth_provenance ->> 'timezone_offset')::double precision
or v_profile.timezone_id is distinct from v_profile.active_birth_provenance ->> 'timezone_id'
or v_profile.active_birth_provenance ->> 'contract' is distinct from 'segment-v1'
or v_profile.active_birth_provenance ->> 'case_id' is distinct from p_case_id::text
or v_profile.active_birth_provenance ->> 'candidate_id' is distinct from v_result.selected_candidate_id::text
or v_profile.active_birth_provenance ->> 'candidate_date' is distinct from v_profile.active_birth_date::text
or v_profile.active_birth_provenance ->> 'candidate_time' is distinct from to_char(v_profile.active_birth_time,'HH24:MI')
or v_profile.active_birth_provenance ->> 'result_id' is distinct from p_result_id::text) then
raise exception 'agentic_rectification_candidate_selection_blocked';
end if;
-- A new request for the same segment selection must not bounce through the
-- candidate minute. Revalidate freshness and the frozen dated gate read-only.
if v_result.selected_candidate_id = p_candidate_id
and v_case.adopted_credible_range ->> 'source' = 'server_segment_midpoint'
and v_result.selected_time = (v_minute ->> 'time')::time
and v_profile.active_birth_date = (v_minute ->> 'date')::date
and v_case.accepted_time = v_result.selected_time
and v_result.selection_kind = 'user_accepted'
and v_case.status = 'candidate_accepted' then
if v_result.invalidated_at is not null or v_result.expires_at <= now() then
raise exception 'agentic_rectification_candidate_expired';
end if;
if not v_result.display_allowed or not v_result.selection_allowed then
raise exception 'agentic_rectification_candidate_selection_blocked';
end if;
if exists (select 1 from public.agentic_rectification_results newer where newer.user_id=p_user_id
and newer.case_id=p_case_id and newer.invalidated_at is null and newer.created_at>v_result.created_at) then
raise exception 'agentic_rectification_candidate_superseded';
end if;
if v_profile.birth_date is distinct from (v_case.baseline_birth_snapshot ->> 'birth_date')::date
or v_profile.reported_birth_time is distinct from (v_case.baseline_birth_snapshot ->> 'reported_birth_time')::time
or v_profile.birth_time_source is distinct from v_case.baseline_birth_snapshot ->> 'birth_time_source'
or v_profile.birth_time_period is distinct from v_case.baseline_birth_snapshot ->> 'birth_time_period'
or v_profile.declared_window_start is distinct from v_case.baseline_birth_snapshot ->> 'declared_window_start'
or v_profile.declared_window_end is distinct from v_case.baseline_birth_snapshot ->> 'declared_window_end'
or v_profile.uncertainty_before_minutes is distinct from (v_case.baseline_birth_snapshot ->> 'uncertainty_before_minutes')::integer
or v_profile.uncertainty_after_minutes is distinct from (v_case.baseline_birth_snapshot ->> 'uncertainty_after_minutes')::integer
or v_profile.latitude is distinct from (v_case.baseline_birth_snapshot ->> 'latitude')::double precision
or v_profile.longitude is distinct from (v_case.baseline_birth_snapshot ->> 'longitude')::double precision
or v_profile.timezone_id is distinct from v_case.baseline_birth_snapshot ->> 'timezone_id'
or v_profile.timezone_offset is distinct from (v_case.baseline_birth_snapshot ->> 'timezone_offset')::double precision then
raise exception 'agentic_rectification_candidate_profile_changed';
end if;
if jsonb_typeof(v_state -> 'revision') is distinct from 'number' or coalesce(v_state ->> 'revision','') !~ '^[0-9]+$'
or length(btrim(coalesce(v_state ->> 'candidate_set_id',''))) = 0
or v_state ->> 'range_start' is distinct from v_case.candidate_range ->> 'start_time'
or v_state ->> 'range_end' is distinct from v_case.candidate_range ->> 'end_time'
or exists (select 1 from public.agentic_rectification_inference_transitions t where t.case_id=p_case_id and t.result_id=p_result_id
and t.revision=(select max(revision) from public.agentic_rectification_inference_transitions where case_id=p_case_id and result_id=p_result_id)
and (t.revision is distinct from (v_state ->> 'revision')::integer or t.candidate_set_id is distinct from v_state ->> 'candidate_set_id')) then
raise exception 'agentic_rectification_candidate_state_inconsistent';
end if;
select * into v_candidate from public.agentic_rectification_candidates where id=p_candidate_id;
if public.validate_dated_rectification_candidate(v_case,v_result,v_candidate,v_state) is null then
-- Frozen legacy identity/active/credible gate, evaluated without any writes.
select count(*), (v_case.candidate_range ->> 'start_time') || '-' || (v_case.candidate_range ->> 'end_time') || ':' ||
string_agg(to_char(candidate_time,'HH24:MI'),',' order by candidate_time)
into v_candidate_count,v_candidate_set from public.agentic_rectification_candidates
where result_id=p_result_id and case_id=p_case_id and user_id=p_user_id;
if v_candidate_count=0 or jsonb_array_length(v_state -> 'candidates')<>v_candidate_count
or v_state ->> 'candidate_set_id' is distinct from v_candidate_set
or exists (select 1 from jsonb_array_elements(v_state -> 'candidates') a where
jsonb_typeof(a) is distinct from 'object' or coalesce(a ->> 'time','') !~ '^([01][0-9]|2[0-3]):[0-5][0-9]$'
or coalesce(a ->> 'status','') not in ('active','equivalent','winner','eliminated')
or jsonb_typeof(a -> 'probability') is distinct from 'number' or jsonb_typeof(a -> 'posterior_score') is distinct from 'number'
or jsonb_typeof(a -> 'cluster_range') is distinct from 'array') then
raise exception 'agentic_rectification_candidate_state_inconsistent';
end if;
if exists (select 1 from jsonb_array_elements(v_state -> 'candidates') a where
jsonb_array_length(a -> 'cluster_range') is distinct from 2
or coalesce(a -> 'cluster_range' ->> 0,'') !~ '^([01][0-9]|2[0-3]):[0-5][0-9]$'
or coalesce(a -> 'cluster_range' ->> 1,'') !~ '^([01][0-9]|2[0-3]):[0-5][0-9]$'
or a -> 'cluster_range' ->> 0 > a ->> 'time' or a -> 'cluster_range' ->> 1 < a ->> 'time'
or not exists (select 1 from public.agentic_rectification_candidates c where c.result_id=p_result_id
and to_char(c.candidate_time,'HH24:MI')=a ->> 'time'))
or (select count(distinct a ->> 'time') from jsonb_array_elements(v_state -> 'candidates') a)<>v_candidate_count then
raise exception 'agentic_rectification_candidate_state_inconsistent';
end if;
select a ->> 'time' into v_top_time from jsonb_array_elements(v_state -> 'candidates') a where a ->> 'status'<>'eliminated'
order by (a ->> 'probability')::numeric desc,(a ->> 'posterior_score')::numeric desc,a ->> 'time' limit 1;
select min(a -> 'cluster_range' ->> 0),max(a -> 'cluster_range' ->> 1) into v_range_start,v_range_end
from jsonb_array_elements(v_state -> 'candidates') a where a ->> 'status'<>'eliminated';
if v_top_time is null or v_state ->> 'representative_time' is distinct from v_top_time
or jsonb_typeof(v_state -> 'credible_range') is distinct from 'array' or jsonb_array_length(v_state -> 'credible_range')<>2
or v_state -> 'credible_range' ->> 0 is distinct from v_range_start or v_state -> 'credible_range' ->> 1 is distinct from v_range_end
or not exists (select 1 from jsonb_array_elements(v_state -> 'candidates') a
where a ->> 'time'=to_char(v_candidate.candidate_time,'HH24:MI') and a ->> 'status'<>'eliminated') then
raise exception 'agentic_rectification_candidate_state_inconsistent';
end if;
end if;
select response into v_previous_response from public.agentic_rectification_candidate_decisions
where user_id=p_user_id and case_id=p_case_id and result_id=p_result_id and candidate_id=p_candidate_id
and decision_kind='accept' and response ->> 'adoption_contract'='segment-v1'
and response ->> 'saved_time'=v_minute ->> 'time' and response ->> 'saved_date'=v_minute ->> 'date'
order by created_at desc limit 1;
if v_previous_response is not null then
v_response := v_previous_response || jsonb_build_object('idempotent',true);
insert into public.agentic_rectification_candidate_decisions(user_id,case_id,result_id,candidate_id,request_id,decision_kind,response)
values(p_user_id,p_case_id,p_result_id,p_candidate_id,p_request_id,'accept',v_response);
return v_response;
end if;
end if;
-- Reuse every frozen accept gate in the same transaction for a new selection.
-- Any later exception rolls back its writes as well as ours.
v_response := public.accept_agentic_rectification_candidate_for_case_v2(p_user_id,p_case_id,p_result_id,p_candidate_id,p_request_id,null);
v_date := (v_minute ->> 'date')::date;
v_time := (v_minute ->> 'time')::time;
v_offset := coalesce((v_result.decision_receipt ->> 'candidate_timezone_offset')::double precision, v_profile.timezone_offset);
v_provenance := jsonb_build_object('contract','segment-v1','case_id',p_case_id,'result_id',p_result_id,'candidate_id',p_candidate_id,
'declared_birth_date',v_profile.birth_date,'candidate_date',v_date,'candidate_time',v_minute ->> 'time',
'timezone_offset',v_offset,'timezone_id',v_profile.timezone_id,'source','server_segment_midpoint',
'window_offset_minutes',v_minute -> 'offset','adopted_at',now());
update public.profiles set birth_time=v_time, active_birth_time=v_time, active_birth_date=v_date,
active_birth_timezone_offset=v_offset, active_birth_provenance=v_provenance, updated_at=now() where id=p_user_id;
update public.agentic_rectification_results set selected_time=v_time, updated_at=now() where id=p_result_id;
update public.agentic_rectification_cases set accepted_time=v_time,
adopted_credible_range=coalesce(adopted_credible_range,'{}'::jsonb) || jsonb_build_object('representative_time',v_minute ->> 'time',
'representative_date',v_date,'source','server_segment_midpoint','segment_summary',v_state -> 'segment_summary'),
updated_at=now() where id=p_case_id;
v_response := v_response || jsonb_build_object('saved_time',v_minute ->> 'time','saved_date',v_date,
'day_offset',v_date-v_profile.birth_date,'date_changed',v_date<>v_profile.birth_date,
'saved_timezone_offset',v_offset,'adoption_contract','segment-v1');
update public.agentic_rectification_candidate_decisions set response=v_response where user_id=p_user_id and request_id=p_request_id;
return v_response;
end;
$$;
revoke all on function public.accept_agentic_rectification_segment_for_case_v1(uuid,uuid,uuid,uuid,uuid) from public, anon, authenticated;
grant execute on function public.accept_agentic_rectification_segment_for_case_v1(uuid,uuid,uuid,uuid,uuid) to service_role;
-- Expose only the adopted civil identity, never the profile or its full provenance.
create function public.get_agentic_rectification_segment_selection_v1(p_user_id uuid, p_case_id uuid, p_result_id uuid)
returns jsonb language sql security definer set search_path = '' as $$
select jsonb_build_object('result_id', r.id, 'candidate_id', r.selected_candidate_id,
'date', p.active_birth_date, 'time', to_char(p.active_birth_time, 'HH24:MI'), 'contract', 'segment-v1')
from public.agentic_rectification_results r
join public.agentic_rectification_cases c on c.id=r.case_id and c.user_id=r.user_id
join public.profiles p on p.id=r.user_id
where r.id=p_result_id and r.case_id=p_case_id and r.user_id=p_user_id
and r.selection_kind='user_accepted' and p.birth_time_status='accepted' and c.status='candidate_accepted'
and r.invalidated_at is null and r.expires_at>now()
and not exists (select 1 from public.agentic_rectification_results newer where newer.case_id=c.id and newer.user_id=p_user_id
and newer.invalidated_at is null and newer.created_at>r.created_at)
and p.active_birth_timezone_offset is not null and p.active_birth_timezone_offset between -14 and 14
and p.active_birth_timezone_offset=(p.active_birth_provenance ->> 'timezone_offset')::double precision
and p.timezone_id is not distinct from p.active_birth_provenance ->> 'timezone_id'
and c.accepted_time=r.selected_time and p.active_birth_time=r.selected_time
and c.adopted_credible_range ->> 'source'='server_segment_midpoint'
and c.adopted_credible_range ->> 'representative_date'=p.active_birth_date::text
and p.active_birth_provenance ->> 'contract'='segment-v1'
and p.active_birth_provenance ->> 'case_id'=c.id::text
and p.active_birth_provenance ->> 'result_id'=r.id::text
and p.active_birth_provenance ->> 'candidate_id'=r.selected_candidate_id::text
and p.active_birth_provenance ->> 'candidate_date'=p.active_birth_date::text
and p.active_birth_provenance ->> 'candidate_time'=to_char(p.active_birth_time,'HH24:MI');
$$;
revoke all on function public.get_agentic_rectification_segment_selection_v1(uuid,uuid,uuid) from public, anon, authenticated;
grant execute on function public.get_agentic_rectification_segment_selection_v1(uuid,uuid,uuid) to service_role;
commit;