Files
Jyotisha/frontend/supabase/migrations/20260826020000_rectification_choice_focus_identity.sql
T
Jesse_Chen a0ce55f066
Independent Staging Quality Gate / validate (push) Successful in 11m17s
Independent Staging Quality Gate / publish (push) Failing after 9m28s
fix(web): drive rectification from candidate contrast and allow range close
Showing a choice card is no longer treated as completion. Distinguish probes
require real candidate groups, holdout stays out of scoring, and ordinary
sessions can finish with a credible range instead of an exact-minute gate.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-26 17:19:26 +08:00

178 lines
6.4 KiB
PL/PgSQL

-- Choice identity is the persisted focus UUID plus the inference revision.
-- Derived question_id is an audit label only. A card the server just returned
-- must not 409 stale_question while the Case revision is unchanged.
-- Business schema only; do not copy into frontend/db/migrations (BUG-127 / BUG-144).
begin;
create or replace function public.apply_agentic_rectification_choice_action(
p_user_id uuid,
p_case_id uuid,
p_action_id uuid,
p_question_id text,
p_option_id text,
p_expected_revision integer,
p_focus_id uuid,
p_focus_status text,
p_source_quote text,
p_derived_context jsonb,
p_narration text,
p_inference jsonb
)
returns jsonb
language plpgsql
security definer
set search_path = ''
as $$
declare
v_case public.agentic_rectification_cases%rowtype;
v_focus public.agentic_rectification_conversation_focuses%rowtype;
v_existing public.agentic_rectification_choice_actions%rowtype;
v_transition jsonb;
v_focus_result jsonb;
v_receipt jsonb;
v_id uuid;
v_probe_id text;
v_question_id text;
begin
if p_user_id is null or p_case_id is null or p_action_id is null
or p_option_id is null or p_option_id not in ('A', 'B', 'C', 'D', 'stop')
or p_expected_revision is null or p_expected_revision < 0
or p_focus_id is null
or p_focus_status is null or p_focus_status not in ('resolved', 'declined', 'skipped')
or p_derived_context is null or jsonb_typeof(p_derived_context) <> 'object'
or (p_inference is not null and jsonb_typeof(p_inference) <> 'object') then
raise exception 'agentic_rectification_invalid_input' using errcode = 'P0001';
end if;
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;
if v_case.status in ('confirmed', 'closed', 'abandoned', 'superseded') then
raise exception 'agentic_rectification_case_terminal' using errcode = 'P0001';
end if;
select * into v_existing
from public.agentic_rectification_choice_actions
where case_id = p_case_id
and action_id = p_action_id;
if found then
return jsonb_set(v_existing.receipt, '{idempotent}', 'true'::jsonb, true);
end if;
select * into v_focus
from public.agentic_rectification_conversation_focuses
where id = p_focus_id and case_id = p_case_id
for update;
if not found then
raise exception 'agentic_rectification_focus_not_found' using errcode = 'P0001';
end if;
if v_focus.status is distinct from 'active' then
raise exception 'agentic_rectification_stale_question' using errcode = 'P0001';
end if;
v_question_id := coalesce(nullif(btrim(coalesce(p_question_id, '')), ''), v_focus.question_id);
insert into public.agentic_rectification_choice_actions (
case_id, action_id, question_id, probe_id, option_id, expected_revision,
status, answer_class, source_quote, derived_context, focus_id, narration, receipt
) values (
p_case_id, p_action_id, v_question_id,
nullif(btrim(coalesce(p_inference ->> 'probe_id', p_derived_context ->> 'derivedFromProbeId', '')), ''),
p_option_id, p_expected_revision, 'received',
nullif(btrim(coalesce(p_inference ->> 'answer_class', p_derived_context ->> 'answerClass', '')), ''),
nullif(btrim(coalesce(p_source_quote, '')), ''),
p_derived_context, p_focus_id, nullif(btrim(coalesce(p_narration, '')), ''),
jsonb_build_object('status', 'received')
)
returning * into v_existing;
v_transition := null;
if p_inference is not null then
v_transition := public.append_agentic_rectification_inference_transition(
p_user_id,
p_case_id,
coalesce((p_inference ->> 'expected_revision')::integer, p_expected_revision),
p_inference ->> 'probe_id',
p_inference ->> 'open_probe_id',
p_inference ->> 'semantic_key',
p_inference ->> 'candidate_split_hash',
p_inference ->> 'answer_class',
p_inference ->> 'raw_answer',
p_inference -> 'inference_state',
coalesce(p_inference -> 'posterior_before', '{}'::jsonb),
coalesce(p_inference -> 'posterior_after', '{}'::jsonb),
coalesce(p_inference -> 'score_deltas', '{}'::jsonb),
p_inference ->> 'decision_state_fingerprint',
p_inference ->> 'reason',
p_inference ->> 'idempotency_key',
p_inference ->> 'candidate_set_id'
);
end if;
v_focus_result := public.resolve_agentic_rectification_conversation_focus(
p_user_id, p_case_id, p_focus_id, p_focus_status, null
);
v_probe_id := coalesce(
v_transition ->> 'probe_id',
p_inference ->> 'probe_id',
p_derived_context ->> 'derivedFromProbeId'
);
v_receipt := jsonb_build_object(
'action_id', p_action_id,
'status', 'applied',
'idempotent', false,
'question_id', v_question_id,
'option_id', p_option_id,
'probe_id', to_jsonb(v_probe_id),
'revision', coalesce((v_transition ->> 'revision')::integer, p_expected_revision),
'focus_id', v_focus_result -> 'focus_id',
'focus_status', v_focus_result -> 'status',
'transition_id', v_transition -> 'transition_id',
'source_quote', to_jsonb(nullif(btrim(coalesce(p_source_quote, '')), '')),
'derived_context', p_derived_context,
'narration', to_jsonb(nullif(btrim(coalesce(p_narration, '')), ''))
);
update public.agentic_rectification_choice_actions
set status = 'applied',
probe_id = v_probe_id,
transition_id = nullif(v_transition ->> 'transition_id', '')::uuid,
receipt = v_receipt,
updated_at = pg_catalog.now()
where id = v_existing.id
returning id into v_id;
update public.agentic_rectification_cases
set last_activity_at = pg_catalog.now(),
updated_at = pg_catalog.now()
where id = p_case_id;
return v_receipt;
exception
when unique_violation then
select * into v_existing
from public.agentic_rectification_choice_actions
where case_id = p_case_id
and action_id = p_action_id;
if found then
return jsonb_set(v_existing.receipt, '{idempotent}', 'true'::jsonb, true);
end if;
raise;
end;
$$;
revoke all on function public.apply_agentic_rectification_choice_action(
uuid, uuid, uuid, text, text, integer, uuid, text, text, jsonb, text, jsonb
) from public, anon, authenticated;
grant execute on function public.apply_agentic_rectification_choice_action(
uuid, uuid, uuid, text, text, integer, uuid, text, text, jsonb, text, jsonb
) to service_role;
commit;