Functional roles feed the *_functional_*_auxiliary rules, so57782aeachanges candidate scores for identical input (memoization fixture 12:00: 8.6274 -> 8.4977). Per the "scoring semantics change => bump ALGORITHM_VERSION" precedent (scoring-7 -> 8 -> 9), the identity moves to scoring-10; policy v3, input contract v5 and Skill versions are unchanged, history is not relabeled. Five frontend sites and one SQL guard tested `=== "...scoring-9"` for the dated candidate-window contract; they now use isDatedScoringAlgorithmVersion / a generation regex (>= 9). Migration 20261002010000 only recreates validate_dated_rectification_candidate (one-line guard change). Memoization golden v2 written by the test's own write_golden; v1 (scoring-8) frozen by sha256. Real-engine scoring-10 cross-midnight golden added. Research records re-frozen per ERR-110 (label functional_v2_2026_10_02) and scripts/functional_benefics.py added to the frozen production identity (ERR-114:57782aeachanged scores without tripping it). Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01N4f2nya58RoRu4yEmJgRGE
181 lines
11 KiB
PL/PgSQL
181 lines
11 KiB
PL/PgSQL
-- BUG-1181: the scoring algorithm identity moves to rectification-v5-matrix-scoring-10
|
|
-- (functional benefic / malefic profile v2 changes candidate scores for identical
|
|
-- input). scoring-9 introduced the dated candidate-window contract and every later
|
|
-- generation keeps it, so this guard compares the generation (>= 9) instead of one
|
|
-- literal: a bump must not let a partial new contract pass as a legacy same-day
|
|
-- result. Function body otherwise byte-identical to 20260920020000_adopted_birth_date.sql;
|
|
-- no table, column, grant or historical row changes (create or replace keeps the
|
|
-- existing owner and the revoke from 20260920020000).
|
|
|
|
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 coalesce(p_result.algorithm_version, '') ~ '^rectification-v5-matrix-scoring-(9|[1-9][0-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;
|
|
$$;
|