fix(rectification): bump scoring identity to scoring-10 for functional profile v2; dated contract by generation (BUG-1181)
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
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
57782aea8a
commit
3733b9787b
+180
@@ -0,0 +1,180 @@
|
||||
-- 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;
|
||||
$$;
|
||||
Reference in New Issue
Block a user