Files
Jyotisha/frontend/supabase/migrations/20260920020000_adopted_birth_date.sql
T
jesse-ux b85c4a686a
Independent Staging Quality Gate / validate (push) Successful in 13m27s
Independent Staging Quality Gate / publish (push) Failing after 1h0m1s
fix(rectification): anchor candidate windows to civil dates across midnight
Carry explicit local date intervals instead of inferring the day from clock
order. Cluster width, delivery, adoption, and reports keep the actual civil
date; adopted date is stored separately from the reported birth_date.

Algorithm identity is scoring-9 / spec-v5. Scoring weights, confirmation
thresholds, and Skill version are unchanged. Isolated Linux final-3 gates
passed; four pre-existing Python failures remain. This is not a production
release.
2026-09-21 02:55:00 +08:00

988 lines
47 KiB
PL/PgSQL

-- Nullable adoption outputs; declaration inputs and historical rows are untouched.
begin;
alter table public.profiles
add column if not exists active_birth_date date,
add column if not exists active_birth_timezone_offset double precision,
add column if not exists active_birth_provenance jsonb;
create or replace function public.guard_adopted_birth_date()
returns trigger language plpgsql set search_path = '' as $$
begin
if new.birth_time_status not in ('accepted', 'confirmed') or new.birth_time_status is null
or new.active_birth_time is null
or (tg_op = 'UPDATE' and (
new.birth_date is distinct from old.birth_date
or new.reported_birth_time is distinct from old.reported_birth_time
or new.birth_time_source is distinct from old.birth_time_source
or new.birth_time_period is distinct from old.birth_time_period
or new.declared_window_start is distinct from old.declared_window_start
or new.declared_window_end is distinct from old.declared_window_end
or new.uncertainty_before_minutes is distinct from old.uncertainty_before_minutes
or new.uncertainty_after_minutes is distinct from old.uncertainty_after_minutes
or new.latitude is distinct from old.latitude or new.longitude is distinct from old.longitude
or new.timezone_id is distinct from old.timezone_id
or new.timezone_offset is distinct from old.timezone_offset
or (new.active_birth_time is distinct from old.active_birth_time
and new.active_birth_provenance is not distinct from old.active_birth_provenance)
)) then
new.active_birth_date := null;
new.active_birth_timezone_offset := null;
new.active_birth_provenance := null;
end if;
return new;
end;
$$;
drop trigger if exists zz_guard_adopted_birth_date on public.profiles;
create trigger zz_guard_adopted_birth_date before insert or update on public.profiles
for each row execute function public.guard_adopted_birth_date();
-- Internal, service-only validation shared by adoption and confirmation.
-- The candidate UUID identifies persisted facts; callers never supply a date.
create or replace function public.validate_dated_rectification_candidate(
p_case public.agentic_rectification_cases,
p_result public.agentic_rectification_results,
p_candidate public.agentic_rectification_candidates,
p_inference jsonb
) returns date language plpgsql security definer set search_path = '' as $$
declare
v_receipt jsonb := p_result.decision_receipt;
v_windows jsonb;
v_payload jsonb;
v_item jsonb;
v_interval jsonb;
v_window jsonb;
v_first timestamp;
v_at timestamp;
v_start timestamp;
v_end timestamp;
v_previous timestamp;
v_date date;
v_expected text;
v_count integer;
v_union pg_catalog.tsmultirange;
v_credible pg_catalog.tsmultirange;
v_expected_credible jsonb;
v_credible_ranges pg_catalog.tsrange[] := array[]::pg_catalog.tsrange[];
v_top text;
begin
if v_receipt ->> 'candidate_window_contract' is distinct from 'dated-v1' then
-- A partial new contract must never silently become a legacy same-day result.
if p_result.algorithm_version = 'rectification-v5-matrix-scoring-9'
or v_receipt ? 'candidate_window_contract' or v_receipt ? 'candidate_intervals'
or p_candidate.candidate_payload ? 'candidate_date'
or coalesce(p_inference ->> 'candidate_set_id', '') like 'dated-v1:%' then
raise exception 'agentic_rectification_candidate_state_inconsistent';
end if;
return null;
end if;
v_windows := v_receipt -> 'candidate_intervals';
if jsonb_typeof(v_windows) is distinct from 'array' or jsonb_array_length(v_windows) = 0
or v_windows is distinct from p_case.candidate_range -> 'candidate_intervals'
or jsonb_typeof(v_receipt -> 'candidate_timezone_offset') is distinct from 'number'
or (v_receipt ->> 'candidate_timezone_offset')::double precision is distinct from
(p_case.baseline_birth_snapshot ->> 'timezone_offset')::double precision
or coalesce(v_receipt ->> 'candidate_timezone_id', '') is distinct from
coalesce(p_case.baseline_birth_snapshot ->> 'timezone_id', '')
or jsonb_typeof(p_inference -> 'candidates') is distinct from 'array'
or jsonb_typeof(p_inference -> 'credible_intervals') is distinct from 'array'
or p_inference ->> 'range_start' is distinct from p_case.candidate_range ->> 'start_time'
or p_inference ->> 'range_end' is distinct from p_case.candidate_range ->> 'end_time' then
raise exception 'agentic_rectification_candidate_state_inconsistent';
end if;
for v_window in select value from jsonb_array_elements(v_windows) loop
if coalesce(v_window ->> 'start_at', '') !~ '^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}$'
or coalesce(v_window ->> 'end_at', '') !~ '^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}$' then
raise exception 'agentic_rectification_candidate_state_inconsistent';
end if;
v_start := (v_window ->> 'start_at')::timestamp;
v_end := (v_window ->> 'end_at')::timestamp;
if v_end < v_start or v_start <= v_previous then
raise exception 'agentic_rectification_candidate_state_inconsistent';
end if;
v_first := coalesce(v_first, v_start);
v_previous := v_end;
end loop;
select count(*), 'dated-v1:' || (p_case.candidate_range ->> 'start_time') || '-' ||
(p_case.candidate_range ->> 'end_time') || ':' || string_agg(
(candidate_payload ->> 'candidate_date') || 'T' || to_char(candidate_time, 'HH24:MI') || '@' ||
(candidate_payload ->> 'window_index') || '@' || (candidate_payload ->> 'window_offset_minutes') || '@' ||
(candidate_payload ->> 'segment_index'), ',' order by (candidate_payload ->> 'window_index')::integer)
into v_count, v_expected from public.agentic_rectification_candidates where result_id = p_result.id;
if v_count = 0 or jsonb_array_length(p_inference -> 'candidates') <> v_count
or p_inference ->> 'candidate_set_id' is distinct from v_expected
or (select count(distinct value ->> 'window_index') from jsonb_array_elements(p_inference -> 'candidates')) <> v_count then
raise exception 'agentic_rectification_candidate_state_inconsistent';
end if;
for v_item in select value from jsonb_array_elements(p_inference -> 'candidates') loop
select candidate_payload into v_payload from public.agentic_rectification_candidates
where result_id = p_result.id and to_char(candidate_time, 'HH24:MI') = v_item ->> 'time';
if not found or coalesce(v_item ->> 'candidate_date', '') !~ '^\d{4}-\d{2}-\d{2}$'
or coalesce(v_item ->> 'window_index', '') !~ '^\d+$'
or coalesce(v_item ->> 'window_offset_minutes', '') !~ '^\d+$'
or coalesce(v_item ->> 'segment_index', '') !~ '^\d+$'
or v_item -> 'candidate_date' is distinct from v_payload -> 'candidate_date'
or v_item -> 'window_index' is distinct from v_payload -> 'window_index'
or v_item -> 'window_offset_minutes' is distinct from v_payload -> 'window_offset_minutes'
or v_item -> 'segment_index' is distinct from v_payload -> 'segment_index'
or v_item -> 'cluster_intervals' is distinct from v_payload -> 'cluster_intervals'
or coalesce(v_item ->> 'status', '') not in ('active', 'equivalent', 'winner', 'eliminated')
or jsonb_typeof(v_item -> 'probability') is distinct from 'number'
or jsonb_typeof(v_item -> 'posterior_score') is distinct from 'number'
or jsonb_typeof(v_item -> 'cluster_intervals') is distinct from 'array'
or jsonb_array_length(v_item -> 'cluster_intervals') = 0 then
raise exception 'agentic_rectification_candidate_state_inconsistent';
end if;
v_at := ((v_item ->> 'candidate_date') || 'T' || (v_item ->> 'time'))::timestamp;
v_window := v_windows -> (v_item ->> 'segment_index')::integer;
if v_window is null or v_at < (v_window ->> 'start_at')::timestamp or v_at > (v_window ->> 'end_at')::timestamp
or extract(epoch from (v_at - v_first)) / 60 <> (v_item ->> 'window_offset_minutes')::integer then
raise exception 'agentic_rectification_candidate_state_inconsistent';
end if;
if not exists (select 1 from jsonb_array_elements(v_item -> 'cluster_intervals') x
where x -> 'segment_index' = v_item -> 'segment_index'
and (v_item ->> 'window_index')::integer between (x ->> 'start_index')::integer and (x ->> 'end_index')::integer
and v_at between (x ->> 'start_at')::timestamp and (x ->> 'end_at')::timestamp) then
raise exception 'agentic_rectification_candidate_state_inconsistent';
end if;
for v_interval in select value from jsonb_array_elements(v_item -> 'cluster_intervals') loop
v_start := (v_interval ->> 'start_at')::timestamp;
v_end := (v_interval ->> 'end_at')::timestamp;
v_window := v_windows -> (v_interval ->> 'segment_index')::integer;
if v_start is null or v_end is null or v_window is null or v_end < v_start
or v_start < (v_window ->> 'start_at')::timestamp or v_end > (v_window ->> 'end_at')::timestamp
or (v_interval ->> 'start_offset_minutes')::integer is distinct from (extract(epoch from (v_start - v_first)) / 60)::integer
or (v_interval ->> 'end_offset_minutes')::integer is distinct from (extract(epoch from (v_end - v_first)) / 60)::integer
or coalesce(v_interval ->> 'start_index', '') !~ '^\d+$'
or coalesce(v_interval ->> 'end_index', '') !~ '^\d+$'
or (v_interval ->> 'end_index')::integer < (v_interval ->> 'start_index')::integer then
raise exception 'agentic_rectification_candidate_state_inconsistent';
end if;
end loop;
end loop;
-- Historical credible semantics envelope active clusters within each declared segment,
-- but never bridge separate declared segments.
select range_agg(tsrange(first_at, last_at + interval '1 minute', '[)')),
jsonb_agg(jsonb_build_object('segment_index', segment_index,
'start_index', first_index, 'end_index', last_index,
'start_offset_minutes', (extract(epoch from (first_at - v_first)) / 60)::integer,
'end_offset_minutes', (extract(epoch from (last_at - v_first)) / 60)::integer,
'start_at', to_char(first_at, 'YYYY-MM-DD"T"HH24:MI'),
'end_at', to_char(last_at, 'YYYY-MM-DD"T"HH24:MI')) order by first_index)
into v_union, v_expected_credible
from (
select (part ->> 'segment_index')::integer as segment_index,
min((part ->> 'start_index')::integer) as first_index,
max((part ->> 'end_index')::integer) as last_index,
min((part ->> 'start_at')::timestamp) as first_at,
max((part ->> 'end_at')::timestamp) as last_at
from jsonb_array_elements(p_inference -> 'candidates') candidate,
lateral jsonb_array_elements(candidate -> 'cluster_intervals') part
where candidate ->> 'status' <> 'eliminated'
and (select (value ->> 'posterior_score')::numeric from jsonb_array_elements(p_inference -> 'candidates')
where value ->> 'status' <> 'eliminated'
order by (value ->> 'probability')::numeric desc, (value ->> 'posterior_score')::numeric desc,
(value ->> 'window_index')::integer limit 1) - (candidate ->> 'posterior_score')::numeric < 8
group by (part ->> 'segment_index')::integer
) segments;
for v_interval in select value from jsonb_array_elements(p_inference -> 'credible_intervals') loop
v_start := (v_interval ->> 'start_at')::timestamp;
v_end := (v_interval ->> 'end_at')::timestamp;
if v_start is null or v_end is null or v_end < v_start then
raise exception 'agentic_rectification_candidate_state_inconsistent';
end if;
v_credible_ranges := array_append(v_credible_ranges, tsrange(v_start, v_end + interval '1 minute', '[)'));
end loop;
select range_agg(x) into v_credible from unnest(v_credible_ranges) x;
select value ->> 'time' into v_top from jsonb_array_elements(p_inference -> 'candidates')
where value ->> 'status' <> 'eliminated'
order by (value ->> 'probability')::numeric desc, (value ->> 'posterior_score')::numeric desc,
(value ->> 'window_index')::integer limit 1;
if v_union is null or v_credible is distinct from v_union
or p_inference -> 'credible_intervals' is distinct from v_expected_credible
or p_inference ->> 'representative_time' is distinct from v_top
or not exists (select 1 from jsonb_array_elements(p_inference -> 'candidates')
where value ->> 'time' = to_char(p_candidate.candidate_time, 'HH24:MI') and value ->> 'status' <> 'eliminated') then
raise exception 'agentic_rectification_candidate_state_inconsistent';
end if;
v_date := (p_candidate.candidate_payload ->> 'candidate_date')::date;
if v_date is null then raise exception 'agentic_rectification_candidate_state_inconsistent'; end if;
return v_date;
end;
$$;
revoke all on function public.validate_dated_rectification_candidate(public.agentic_rectification_cases, public.agentic_rectification_results, public.agentic_rectification_candidates, jsonb) from public, anon, authenticated, service_role;
create or replace function public.accept_agentic_rectification_candidate_for_case_v2(
p_user_id uuid,
p_case_id uuid,
p_result_id uuid,
p_candidate_id uuid,
p_request_id uuid,
p_credible_range jsonb default null
)
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_candidate public.agentic_rectification_candidates%rowtype;
v_profile public.profiles%rowtype;
v_snapshot jsonb;
v_inference jsonb;
v_expected_candidate_set_id text;
v_persisted_candidate_count integer;
v_top_active_time text;
v_active_range_start text;
v_active_range_end text;
v_latest_transition public.agentic_rectification_inference_transitions%rowtype;
v_existing_decision public.agentic_rectification_candidate_decisions%rowtype;
v_response jsonb;
v_adopted jsonb;
v_adopted_start text;
v_adopted_end text;
v_adopted_rep text;
v_adopted_width integer;
v_saved_date date;
v_provenance jsonb;
v_saved_offset double precision;
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' using errcode = 'P0001';
end if;
perform pg_catalog.pg_advisory_xact_lock(
pg_catalog.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' using errcode = 'P0001';
end if;
select * into v_result
from public.agentic_rectification_results
where id = p_result_id
and user_id = p_user_id
and case_id = p_case_id
for update;
if not found then
raise exception 'agentic_rectification_candidate_not_found' using errcode = 'P0001';
end if;
select * into v_candidate
from public.agentic_rectification_candidates
where id = p_candidate_id
and result_id = p_result_id
and user_id = p_user_id
and case_id = p_case_id;
if not found then
raise exception 'agentic_rectification_candidate_not_found' using errcode = 'P0001';
end if;
select * into v_existing_decision
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.decision_kind <> 'accept'
or v_existing_decision.case_id <> p_case_id
or v_existing_decision.result_id <> p_result_id
or v_existing_decision.candidate_id <> p_candidate_id then
raise exception 'agentic_rectification_candidate_request_conflict' using errcode = 'P0001';
end if;
return jsonb_set(v_existing_decision.response, '{idempotent}', 'true'::jsonb, true);
end if;
if v_case.status in ('confirmed', 'closed', 'abandoned', 'superseded') then
raise exception 'agentic_rectification_case_terminal' using errcode = 'P0001';
end if;
if v_result.invalidated_at is not null or v_result.expires_at <= pg_catalog.now() then
raise exception 'agentic_rectification_candidate_expired' using errcode = 'P0001';
end if;
if not v_result.display_allowed or not v_result.selection_allowed then
raise exception 'agentic_rectification_candidate_selection_blocked' using errcode = 'P0001';
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' using errcode = 'P0001';
end if;
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' using errcode = 'P0001';
end if;
v_snapshot := v_case.baseline_birth_snapshot;
if v_profile.birth_date is distinct from (v_snapshot ->> 'birth_date')::date
or v_profile.reported_birth_time is distinct from (v_snapshot ->> 'reported_birth_time')::time without time zone
or v_profile.birth_time_source is distinct from v_snapshot ->> 'birth_time_source'
or v_profile.birth_time_period is distinct from v_snapshot ->> 'birth_time_period'
or v_profile.declared_window_start is distinct from v_snapshot ->> 'declared_window_start'
or v_profile.declared_window_end is distinct from v_snapshot ->> 'declared_window_end'
or v_profile.uncertainty_before_minutes is distinct from (v_snapshot ->> 'uncertainty_before_minutes')::integer
or v_profile.uncertainty_after_minutes is distinct from (v_snapshot ->> 'uncertainty_after_minutes')::integer
or v_profile.latitude is distinct from (v_snapshot ->> 'latitude')::double precision
or v_profile.longitude is distinct from (v_snapshot ->> 'longitude')::double precision
or v_profile.timezone_id is distinct from v_snapshot ->> 'timezone_id'
or v_profile.timezone_offset is distinct from (v_snapshot ->> 'timezone_offset')::double precision then
raise exception 'agentic_rectification_candidate_profile_changed' using errcode = 'P0001';
end if;
-- Adoption is a trust-boundary write. Structured answers append inference
-- transitions instead of mutating the engine result, so the latest transition
-- is authoritative when present; persisted display candidates remain a cache.
select * into v_latest_transition
from public.agentic_rectification_inference_transitions
where case_id = p_case_id
and result_id = v_result.id
order by revision desc
limit 1;
v_inference := case
when v_latest_transition.id is not null then v_latest_transition.inference_state
else v_result.decision_receipt -> 'inference_state'
end;
if v_inference is null
or jsonb_typeof(v_inference) <> 'object'
or jsonb_typeof(v_inference -> 'candidates') <> 'array'
or jsonb_array_length(v_inference -> 'candidates') = 0
or jsonb_typeof(v_inference -> 'revision') is distinct from 'number'
or coalesce((v_inference ->> 'revision') !~ '^[0-9]+$', true)
or length(btrim(coalesce(v_inference ->> 'candidate_set_id', ''))) = 0
or (v_inference ->> 'range_start') is distinct from v_case.candidate_range ->> 'start_time'
or (v_inference ->> 'range_end') is distinct from v_case.candidate_range ->> 'end_time' then
raise exception 'agentic_rectification_candidate_state_inconsistent' using errcode = 'P0001';
end if;
if v_latest_transition.id is not null
and (
v_latest_transition.revision is distinct from (v_inference ->> 'revision')::integer
or v_latest_transition.candidate_set_id is distinct from v_inference ->> 'candidate_set_id'
) then
raise exception 'agentic_rectification_candidate_state_inconsistent' using errcode = 'P0001';
end if;
v_saved_date := public.validate_dated_rectification_candidate(v_case, v_result, v_candidate, v_inference);
if v_saved_date is null then
select count(*),
(v_case.candidate_range ->> 'start_time') || '-' ||
(v_case.candidate_range ->> 'end_time') || ':' ||
string_agg(pg_catalog.to_char(candidate_time, 'HH24:MI'), ',' order by candidate_time)
into v_persisted_candidate_count, v_expected_candidate_set_id
from public.agentic_rectification_candidates
where result_id = v_result.id
and user_id = p_user_id
and case_id = p_case_id;
if v_persisted_candidate_count = 0
or jsonb_array_length(v_inference -> 'candidates') <> v_persisted_candidate_count
or (v_inference ->> 'candidate_set_id') is distinct from v_expected_candidate_set_id
or exists (
select 1
from pg_catalog.jsonb_array_elements(v_inference -> 'candidates') as item(value)
where jsonb_typeof(item.value) <> 'object'
) then
raise exception 'agentic_rectification_candidate_state_inconsistent' using errcode = 'P0001';
end if;
if exists (
select 1
from pg_catalog.jsonb_array_elements(v_inference -> 'candidates') as item(value)
where coalesce((item.value ->> 'time') !~ '^([01][0-9]|2[0-3]):[0-5][0-9]$', true)
or coalesce(item.value ->> 'status', '') not in ('active', 'equivalent', 'winner', 'eliminated')
or jsonb_typeof(item.value -> 'probability') is distinct from 'number'
or jsonb_typeof(item.value -> 'posterior_score') is distinct from 'number'
or jsonb_typeof(item.value -> 'cluster_range') is distinct from 'array'
) then
raise exception 'agentic_rectification_candidate_state_inconsistent' using errcode = 'P0001';
end if;
if exists (
select 1
from pg_catalog.jsonb_array_elements(v_inference -> 'candidates') as item(value)
where jsonb_array_length(item.value -> 'cluster_range') is distinct from 2
or coalesce((item.value -> 'cluster_range' ->> 0) !~ '^([01][0-9]|2[0-3]):[0-5][0-9]$', true)
or coalesce((item.value -> 'cluster_range' ->> 1) !~ '^([01][0-9]|2[0-3]):[0-5][0-9]$', true)
or item.value -> 'cluster_range' ->> 0 > item.value ->> 'time'
or item.value -> 'cluster_range' ->> 1 < item.value ->> 'time'
or not exists (
select 1
from public.agentic_rectification_candidates persisted
where persisted.result_id = v_result.id
and pg_catalog.to_char(persisted.candidate_time, 'HH24:MI') = item.value ->> 'time'
)
) or (
select count(distinct item.value ->> 'time')
from pg_catalog.jsonb_array_elements(v_inference -> 'candidates') as item(value)
) <> v_persisted_candidate_count then
raise exception 'agentic_rectification_candidate_state_inconsistent' using errcode = 'P0001';
end if;
select item.value ->> 'time'
into v_top_active_time
from pg_catalog.jsonb_array_elements(v_inference -> 'candidates') as item(value)
where item.value ->> 'status' <> 'eliminated'
order by (item.value ->> 'probability')::numeric desc,
(item.value ->> 'posterior_score')::numeric desc,
item.value ->> 'time'
limit 1;
select min(item.value -> 'cluster_range' ->> 0),
max(item.value -> 'cluster_range' ->> 1)
into v_active_range_start, v_active_range_end
from pg_catalog.jsonb_array_elements(v_inference -> 'candidates') as item(value)
where item.value ->> 'status' <> 'eliminated';
if v_top_active_time is null
or (v_inference ->> 'representative_time') is distinct from v_top_active_time
or jsonb_typeof(v_inference -> 'credible_range') <> 'array'
or jsonb_array_length(v_inference -> 'credible_range') <> 2
or (v_inference -> 'credible_range' ->> 0) is distinct from v_active_range_start
or (v_inference -> 'credible_range' ->> 1) is distinct from v_active_range_end
or not exists (
select 1
from pg_catalog.jsonb_array_elements(v_inference -> 'candidates') as item(value)
where item.value ->> 'time' = pg_catalog.to_char(v_candidate.candidate_time, 'HH24:MI')
and item.value ->> 'status' <> 'eliminated'
) then
raise exception 'agentic_rectification_candidate_state_inconsistent' using errcode = 'P0001';
end if;
else
v_top_active_time := v_inference ->> 'representative_time';
v_saved_offset := (v_result.decision_receipt ->> 'candidate_timezone_offset')::double precision;
v_provenance := jsonb_build_object('contract', 'dated-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_saved_date,
'candidate_time', to_char(v_candidate.candidate_time, 'HH24:MI'),
'timezone_offset', v_saved_offset, 'timezone_id', v_result.decision_receipt ->> 'candidate_timezone_id',
'source', 'persisted_candidate_calculation', 'adopted_at', pg_catalog.now());
end if;
if v_result.selected_candidate_id is not null then
if v_result.selected_candidate_id = p_candidate_id
and v_result.selected_time is not distinct from v_candidate.candidate_time
and v_case.accepted_time is not distinct from v_candidate.candidate_time
and v_result.selection_kind is not distinct from 'user_accepted'
and v_profile.active_birth_time is not distinct from v_candidate.candidate_time
and v_profile.birth_time_status is not distinct from 'accepted'
and v_profile.active_birth_date is not distinct from v_saved_date then
v_response := jsonb_build_object(
'success', true,
'saved_time', pg_catalog.to_char(v_candidate.candidate_time, 'HH24:MI'),
'saved_date', coalesce(v_saved_date, v_profile.birth_date),
'day_offset', coalesce(v_saved_date - v_profile.birth_date, 0),
'date_changed', coalesce(v_saved_date <> v_profile.birth_date, false),
'saved_timezone_offset', coalesce(v_saved_offset, v_profile.timezone_offset),
'status', 'accepted',
'result_id', v_result.id,
'candidate_id', v_candidate.id,
'case_status', 'candidate_accepted',
'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;
if v_case.status is distinct from 'candidate_accepted'
or v_result.selection_kind is distinct from 'user_accepted'
or v_profile.active_birth_time is distinct from v_result.selected_time
or v_profile.birth_time_status is distinct from 'accepted' then
raise exception 'agentic_rectification_candidate_selection_blocked' using errcode = 'P0001';
end if;
else
if v_result.selected_time is not null then
raise exception 'agentic_rectification_candidate_selection_blocked' using errcode = 'P0001';
end if;
end if;
update public.profiles
set active_birth_time = v_candidate.candidate_time,
active_birth_date = v_saved_date,
active_birth_timezone_offset = v_saved_offset,
active_birth_provenance = v_provenance,
birth_time = v_candidate.candidate_time,
birth_time_status = 'accepted',
rectification_confidence = case
when v_result.overall_confidence = 'high' then 100
when v_result.overall_confidence = 'medium' then 70
else 40
end,
updated_at = pg_catalog.now()
where id = p_user_id;
update public.agentic_rectification_results
set selected_candidate_id = p_candidate_id,
selected_time = v_candidate.candidate_time,
selection_kind = 'user_accepted',
selected_at = pg_catalog.now(),
updated_at = pg_catalog.now()
where id = v_result.id;
update public.agentic_rectification_results
set invalidated_at = pg_catalog.now(),
updated_at = pg_catalog.now()
where user_id = p_user_id
and case_id = p_case_id
and id <> v_result.id
and invalidated_at is null
and selected_time is null;
v_adopted := null;
if p_credible_range is not null
and jsonb_typeof(p_credible_range) = 'object' then
v_adopted_start := nullif(btrim(coalesce(p_credible_range->>'start_time', '')), '');
v_adopted_end := nullif(btrim(coalesce(p_credible_range->>'end_time', '')), '');
v_adopted_rep := nullif(btrim(coalesce(p_credible_range->>'representative_time', '')), '');
begin
v_adopted_width := (p_credible_range->>'width_minutes')::integer;
exception when others then
v_adopted_width := null;
end;
end if;
if v_adopted_start is null or v_adopted_end is null
or v_adopted_start !~ '^([01][0-9]|2[0-3]):[0-5][0-9]$'
or v_adopted_end !~ '^([01][0-9]|2[0-3]):[0-5][0-9]$' then
v_adopted_start := v_inference -> 'credible_range' ->> 0;
v_adopted_end := v_inference -> 'credible_range' ->> 1;
v_adopted_rep := v_inference ->> 'representative_time';
v_adopted_width := null;
end if;
if v_adopted_start is not null
and v_adopted_end is not null
and v_adopted_start ~ '^([01][0-9]|2[0-3]):[0-5][0-9]$'
and v_adopted_end ~ '^([01][0-9]|2[0-3]):[0-5][0-9]$' then
v_adopted := jsonb_strip_nulls(jsonb_build_object(
'start_time', v_adopted_start,
'end_time', v_adopted_end,
'representative_time', coalesce(v_adopted_rep, v_top_active_time),
'width_minutes', v_adopted_width,
'source', 'inference_credible_range'
));
end if;
if v_saved_date is not null then
v_adopted := jsonb_build_object(
'candidate_window_contract', 'dated-v1',
'intervals', v_inference -> 'credible_intervals',
'representative_time', to_char(v_candidate.candidate_time, 'HH24:MI'),
'representative_date', v_saved_date,
'source', 'inference_credible_range');
if jsonb_array_length(v_inference -> 'credible_intervals') = 1 then
v_adopted := v_adopted || jsonb_build_object(
'start_time', substring(v_inference -> 'credible_intervals' -> 0 ->> 'start_at' from 12 for 5),
'end_time', substring(v_inference -> 'credible_intervals' -> 0 ->> 'end_at' from 12 for 5));
end if;
end if;
update public.agentic_rectification_cases
set status = 'candidate_accepted',
accepted_time = v_candidate.candidate_time,
confirmed_time = null,
completed_at = null,
adopted_credible_range = coalesce(v_adopted, adopted_credible_range),
last_activity_at = pg_catalog.now(),
updated_at = pg_catalog.now()
where id = v_case.id;
v_response := jsonb_build_object(
'success', true,
'saved_time', pg_catalog.to_char(v_candidate.candidate_time, 'HH24:MI'),
'saved_date', coalesce(v_saved_date, v_profile.birth_date),
'day_offset', coalesce(v_saved_date - v_profile.birth_date, 0),
'date_changed', coalesce(v_saved_date <> v_profile.birth_date, false),
'saved_timezone_offset', coalesce(v_saved_offset, v_profile.timezone_offset),
'status', 'accepted',
'result_id', v_result.id,
'candidate_id', v_candidate.id,
'case_status', 'candidate_accepted',
'idempotent', false
);
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;
$$;
revoke all on function public.accept_agentic_rectification_candidate_for_case_v2(uuid, uuid, uuid, uuid, uuid, jsonb)
from public, anon, authenticated;
grant execute on function public.accept_agentic_rectification_candidate_for_case_v2(uuid, uuid, uuid, uuid, uuid, jsonb)
to service_role;
create or replace function public.confirm_agentic_rectification_candidate_for_case_v2(
p_user_id uuid,
p_case_id uuid,
p_result_id uuid,
p_candidate_id uuid,
p_request_id uuid,
p_consent_quote text,
p_source_turn_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_candidate public.agentic_rectification_candidates%rowtype;
v_profile public.profiles%rowtype;
v_turn public.agentic_rectification_turns%rowtype;
v_snapshot jsonb;
v_existing_decision public.agentic_rectification_candidate_decisions%rowtype;
v_response jsonb;
v_saved_date date;
v_saved_offset double precision;
v_provenance jsonb;
v_inference jsonb;
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 or p_source_turn_id is null
or length(btrim(coalesce(p_consent_quote, ''))) = 0 then
raise exception 'agentic_rectification_invalid_input' using errcode = 'P0001';
end if;
perform pg_catalog.pg_advisory_xact_lock(
pg_catalog.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' using errcode = 'P0001';
end if;
select * into v_result
from public.agentic_rectification_results
where id = p_result_id
and user_id = p_user_id
and case_id = p_case_id
for update;
if not found then
raise exception 'agentic_rectification_candidate_not_found' using errcode = 'P0001';
end if;
select * into v_candidate
from public.agentic_rectification_candidates
where id = p_candidate_id
and result_id = p_result_id
and user_id = p_user_id
and case_id = p_case_id;
if not found then
raise exception 'agentic_rectification_candidate_not_found' using errcode = 'P0001';
end if;
select * into v_existing_decision
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.decision_kind <> 'confirm'
or v_existing_decision.case_id <> p_case_id
or v_existing_decision.result_id <> p_result_id
or v_existing_decision.candidate_id <> p_candidate_id then
raise exception 'agentic_rectification_candidate_request_conflict' using errcode = 'P0001';
end if;
return jsonb_set(v_existing_decision.response, '{idempotent}', 'true'::jsonb, true);
end if;
select inference_state into v_inference 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_inference := coalesce(v_inference, v_result.decision_receipt -> 'inference_state');
v_saved_date := public.validate_dated_rectification_candidate(v_case, v_result, v_candidate, v_inference);
if v_saved_date is not null then
v_saved_offset := (v_result.decision_receipt ->> 'candidate_timezone_offset')::double precision;
v_provenance := jsonb_build_object('contract', 'dated-v1', 'case_id', p_case_id,
'result_id', p_result_id, 'candidate_id', p_candidate_id,
'declared_birth_date', v_case.baseline_birth_snapshot ->> 'birth_date',
'candidate_date', v_saved_date, 'candidate_time', to_char(v_candidate.candidate_time, 'HH24:MI'),
'timezone_offset', v_saved_offset, 'timezone_id', v_result.decision_receipt ->> 'candidate_timezone_id',
'source', 'persisted_candidate_calculation', 'adopted_at', pg_catalog.now());
end if;
if v_case.status = 'confirmed' then
if v_result.selected_candidate_id is distinct from p_candidate_id
or v_case.confirmed_time is distinct from v_candidate.candidate_time then
raise exception 'agentic_rectification_case_already_confirmed' using errcode = 'P0001';
end if;
v_response := jsonb_build_object(
'success', true,
'saved_time', pg_catalog.to_char(v_candidate.candidate_time, 'HH24:MI'),
'saved_date', coalesce(v_saved_date, (v_case.baseline_birth_snapshot ->> 'birth_date')::date),
'day_offset', coalesce(v_saved_date - (v_case.baseline_birth_snapshot ->> 'birth_date')::date, 0),
'date_changed', coalesce(v_saved_date <> (v_case.baseline_birth_snapshot ->> 'birth_date')::date, false),
'saved_timezone_offset', coalesce(v_saved_offset, (v_case.baseline_birth_snapshot ->> 'timezone_offset')::double precision),
'status', 'confirmed',
'result_id', v_result.id,
'candidate_id', v_candidate.id,
'case_status', 'confirmed',
'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, 'confirm', v_response
);
return v_response;
end if;
if v_case.status in ('closed', 'abandoned', 'superseded') then
raise exception 'agentic_rectification_case_terminal' using errcode = 'P0001';
end if;
select * into v_turn
from public.agentic_rectification_turns
where id = p_source_turn_id and case_id = p_case_id;
if not found then
raise exception 'agentic_rectification_turn_not_found' using errcode = 'P0001';
end if;
if v_turn.user_message is null
or position(
public.agentic_rectification_normalize_quote(p_consent_quote)
in public.agentic_rectification_normalize_quote(v_turn.user_message)
) = 0 then
raise exception 'agentic_rectification_consent_not_grounded' using errcode = 'P0001';
end if;
if v_result.invalidated_at is not null or v_result.expires_at <= pg_catalog.now() then
raise exception 'agentic_rectification_candidate_expired' using errcode = 'P0001';
end if;
if not v_result.confirmation_allowed then
raise exception 'agentic_rectification_confirmation_blocked' using errcode = 'P0001';
end if;
if not v_candidate.is_representative
or v_result.representative_time is distinct from v_candidate.candidate_time then
raise exception 'agentic_rectification_confirmation_exact_gate_blocked' using errcode = 'P0001';
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' using errcode = 'P0001';
end if;
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' using errcode = 'P0001';
end if;
v_snapshot := v_case.baseline_birth_snapshot;
if v_profile.birth_date is distinct from (v_snapshot ->> 'birth_date')::date
or v_profile.reported_birth_time is distinct from (v_snapshot ->> 'reported_birth_time')::time without time zone
or v_profile.birth_time_source is distinct from v_snapshot ->> 'birth_time_source'
or v_profile.birth_time_period is distinct from v_snapshot ->> 'birth_time_period'
or v_profile.declared_window_start is distinct from v_snapshot ->> 'declared_window_start'
or v_profile.declared_window_end is distinct from v_snapshot ->> 'declared_window_end'
or v_profile.uncertainty_before_minutes is distinct from (v_snapshot ->> 'uncertainty_before_minutes')::integer
or v_profile.uncertainty_after_minutes is distinct from (v_snapshot ->> 'uncertainty_after_minutes')::integer
or v_profile.latitude is distinct from (v_snapshot ->> 'latitude')::double precision
or v_profile.longitude is distinct from (v_snapshot ->> 'longitude')::double precision
or v_profile.timezone_id is distinct from v_snapshot ->> 'timezone_id'
or v_profile.timezone_offset is distinct from (v_snapshot ->> 'timezone_offset')::double precision then
raise exception 'agentic_rectification_candidate_profile_changed' using errcode = 'P0001';
end if;
if v_result.selected_candidate_id is null then
if v_result.selected_time is not null then
raise exception 'agentic_rectification_candidate_selection_blocked' using errcode = 'P0001';
end if;
v_snapshot := v_case.baseline_birth_snapshot;
if v_profile.birth_date is distinct from (v_snapshot ->> 'birth_date')::date
or v_profile.reported_birth_time is distinct from (v_snapshot ->> 'reported_birth_time')::time without time zone
or v_profile.active_birth_time is distinct from (v_snapshot ->> 'active_birth_time')::time without time zone
or v_profile.birth_time_source is distinct from v_snapshot ->> 'birth_time_source'
or v_profile.birth_time_period is distinct from v_snapshot ->> 'birth_time_period'
or v_profile.uncertainty_before_minutes is distinct from (v_snapshot ->> 'uncertainty_before_minutes')::integer
or v_profile.uncertainty_after_minutes is distinct from (v_snapshot ->> 'uncertainty_after_minutes')::integer
or v_profile.latitude is distinct from (v_snapshot ->> 'latitude')::double precision
or v_profile.longitude is distinct from (v_snapshot ->> 'longitude')::double precision
or v_profile.timezone_offset is distinct from (v_snapshot ->> 'timezone_offset')::double precision then
raise exception 'agentic_rectification_candidate_profile_changed' using errcode = 'P0001';
end if;
elsif v_result.selected_candidate_id <> p_candidate_id
or v_result.selected_time is distinct from v_candidate.candidate_time
or v_result.selection_kind is distinct from 'user_accepted'
or v_case.status is distinct from 'candidate_accepted'
or v_case.accepted_time is distinct from v_candidate.candidate_time
or v_profile.active_birth_time is distinct from v_candidate.candidate_time
or v_profile.birth_time_status is distinct from 'accepted' then
raise exception 'agentic_rectification_candidate_selection_blocked' using errcode = 'P0001';
end if;
update public.profiles
set active_birth_time = v_candidate.candidate_time,
active_birth_date = v_saved_date,
active_birth_timezone_offset = v_saved_offset,
active_birth_provenance = v_provenance,
birth_time = v_candidate.candidate_time,
birth_time_status = 'confirmed',
rectification_confidence = case
when v_result.overall_confidence = 'high' then 100
when v_result.overall_confidence = 'medium' then 70
else 40
end,
updated_at = pg_catalog.now()
where id = p_user_id;
update public.agentic_rectification_results
set selected_candidate_id = p_candidate_id,
selected_time = v_candidate.candidate_time,
selection_kind = 'engine_confirmed',
selected_at = pg_catalog.now(),
updated_at = pg_catalog.now()
where id = v_result.id;
update public.agentic_rectification_results
set invalidated_at = pg_catalog.now(),
updated_at = pg_catalog.now()
where user_id = p_user_id
and case_id = p_case_id
and id <> v_result.id
and invalidated_at is null
and selected_time is null;
update public.agentic_rectification_cases
set status = 'confirmed',
accepted_time = v_candidate.candidate_time,
confirmed_time = v_candidate.candidate_time,
completed_at = pg_catalog.now(),
last_activity_at = pg_catalog.now(),
updated_at = pg_catalog.now()
where id = v_case.id;
v_response := jsonb_build_object(
'success', true,
'saved_time', pg_catalog.to_char(v_candidate.candidate_time, 'HH24:MI'),
'saved_date', coalesce(v_saved_date, (v_case.baseline_birth_snapshot ->> 'birth_date')::date),
'day_offset', coalesce(v_saved_date - (v_case.baseline_birth_snapshot ->> 'birth_date')::date, 0),
'date_changed', coalesce(v_saved_date <> (v_case.baseline_birth_snapshot ->> 'birth_date')::date, false),
'saved_timezone_offset', coalesce(v_saved_offset, (v_case.baseline_birth_snapshot ->> 'timezone_offset')::double precision),
'status', 'confirmed',
'result_id', v_result.id,
'candidate_id', v_candidate.id,
'case_status', 'confirmed',
'idempotent', false
);
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, 'confirm', v_response
);
return v_response;
end;
$$;
revoke all on function public.confirm_agentic_rectification_candidate_for_case_v2(uuid, uuid, uuid, uuid, uuid, text, uuid)
from public, anon, authenticated;
grant execute on function public.confirm_agentic_rectification_candidate_for_case_v2(uuid, uuid, uuid, uuid, uuid, text, uuid)
to service_role;
create or replace function public.read_report_candidate_range(
p_user_id uuid,
p_rectification_case_id uuid default null
)
returns jsonb
language plpgsql
stable
security definer
set search_path = ''
as $$
declare
v_start text;
v_end text;
v_range jsonb;
begin
if p_user_id is null then
return null;
end if;
if p_rectification_case_id is not null then
select to_char(c.candidate_start, 'HH24:MI'), to_char(c.candidate_end, 'HH24:MI')
into v_start, v_end
from public.birth_time_rectification_cases as c
where c.id = p_rectification_case_id
and c.user_id = p_user_id
and c.status in ('confirmed', 'completed')
and c.candidate_start is not null
and c.candidate_end is not null;
if found then
return jsonb_build_object('start_time', v_start, 'end_time', v_end);
end if;
end if;
select case
when c.adopted_credible_range ->> 'candidate_window_contract' = 'dated-v1' then c.adopted_credible_range
when c.adopted_credible_range is not null
and nullif(btrim(coalesce(c.adopted_credible_range->>'start_time', '')), '') is not null
and nullif(btrim(coalesce(c.adopted_credible_range->>'end_time', '')), '') is not null
then c.adopted_credible_range
else c.candidate_range
end
into v_range
from public.agentic_rectification_cases as c
where c.user_id = p_user_id
and c.status = 'candidate_accepted'
order by c.updated_at desc
limit 1;
if v_range is null then
return null;
end if;
if v_range ->> 'candidate_window_contract' = 'dated-v1' then
return jsonb_build_object('candidate_intervals', v_range -> 'intervals');
end if;
v_start := nullif(btrim(coalesce(v_range->>'start_time', '')), '');
v_end := nullif(btrim(coalesce(v_range->>'end_time', '')), '');
if v_start is null or v_end is null then
return null;
end if;
return jsonb_build_object('start_time', v_start, 'end_time', v_end);
end;
$$;
revoke all on function public.read_report_candidate_range(uuid, uuid)
from public, anon, authenticated;
grant execute on function public.read_report_candidate_range(uuid, uuid)
to service_role;
commit;