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, so 57782aea changes
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: 57782aea changed 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:
Jesse_Chen
2026-10-02 12:31:50 +08:00
co-authored by Claude Opus 5.5
parent 57782aea8a
commit 3733b9787b
25 changed files with 51388 additions and 355 deletions
@@ -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;
$$;