Files
Jyotisha/frontend/supabase/migrations/20260720030000_conversational_rectification_transitions.sql
T
2026-07-21 05:57:30 +08:00

1527 lines
62 KiB
PL/PgSQL

begin;
create or replace function public.conversational_rectification_fingerprint(value jsonb)
returns text
language sql
immutable
strict
set search_path = ''
as $$
select pg_catalog.encode(
pg_catalog.sha256(pg_catalog.convert_to(value::text, 'UTF8')),
'hex'
);
$$;
create or replace function public.guard_imported_rectification_history()
returns trigger
language plpgsql
security definer
set search_path = ''
as $$
begin
if exists (
select 1
from public.birth_time_rectification_cases imported
where imported.imported_from_case_id = old.id
and imported.journey_protocol = 'conversational-evidence-v3'
) then
raise exception 'conversational_imported_case_read_only' using errcode = 'P0001';
end if;
if tg_op = 'DELETE' then
return old;
end if;
return new;
end;
$$;
drop trigger if exists birth_time_rectification_imported_history_update
on public.birth_time_rectification_cases;
create trigger birth_time_rectification_imported_history_update
before update on public.birth_time_rectification_cases
for each row execute function public.guard_imported_rectification_history();
drop trigger if exists birth_time_rectification_imported_history_delete
on public.birth_time_rectification_cases;
create trigger birth_time_rectification_imported_history_delete
before delete on public.birth_time_rectification_cases
for each row execute function public.guard_imported_rectification_history();
create or replace function public.conversational_rectification_case_projection(
p_user_id uuid,
p_case_id uuid
)
returns jsonb
language sql
stable
security definer
set search_path = ''
as $$
select pg_catalog.jsonb_build_object(
'case_id', c.id,
'user_id', c.user_id,
'status', c.status,
'turn_version', c.turn_version,
'revision_of_case_id', c.revision_of_case_id,
'imported_from_case_id', c.imported_from_case_id,
'baseline_active_time', case when c.baseline_active_time is null then null
else pg_catalog.to_char(c.baseline_active_time, 'HH24:MI') end,
'pending_consultation_question', c.pending_consultation_question,
'billing_state', billing.state,
'latest_turn', pg_catalog.jsonb_build_object(
'caseId', c.id,
'journeyProtocol', 'conversational-evidence-v3',
'status', c.status,
'turnVersion', turn.turn_version,
'narrative', turn.narrative,
'candidate', turn.candidate,
'technicalReceipt', turn.technical_receipt,
'evidenceRequest', turn.evidence_request,
'evidenceRecap', turn.evidence_recap,
'actions', turn.actions,
'pendingConsultationQuestion', c.pending_consultation_question
)
)
from public.birth_time_rectification_cases c
join public.birth_time_rectification_turns turn
on turn.case_id = c.id and turn.turn_version = c.turn_version
left join public.birth_time_rectification_billing billing
on billing.case_id = c.id and billing.user_id = c.user_id
where c.id = p_case_id
and c.user_id = p_user_id
and c.journey_protocol = 'conversational-evidence-v3';
$$;
create or replace function public.load_conversational_rectification_case(
p_user_id uuid,
p_case_id uuid default null
)
returns jsonb
language plpgsql
stable
security definer
set search_path = ''
as $$
declare
v_case_id uuid;
v_projection jsonb;
begin
if p_user_id is null then
return null;
end if;
if p_case_id is not null then
v_case_id := p_case_id;
else
select c.id into v_case_id
from public.birth_time_rectification_cases c
where c.user_id = p_user_id
and c.journey_protocol = 'conversational-evidence-v3'
and c.status in ('starting', 'active', 'paused', 'confirming')
order by c.updated_at desc, c.created_at desc
limit 1;
end if;
v_projection := public.conversational_rectification_case_projection(
p_user_id,
v_case_id
);
if v_projection is null then
return null;
end if;
-- This RPC is service-role-only. Mutation receipts continue to use the
-- public projection above, while account resume additionally receives the
-- private working state needed to continue on another device.
return v_projection || pg_catalog.jsonb_build_object(
'declared_birth_input', (
select c.declared_birth_input
from public.birth_time_rectification_cases c
where c.id = v_case_id and c.user_id = p_user_id
),
'private_candidate', (
select c.candidate_result
from public.birth_time_rectification_cases c
where c.id = v_case_id and c.user_id = p_user_id
),
'event_evidence', coalesce((
select pg_catalog.jsonb_agg(
pg_catalog.jsonb_build_object(
'id', evidence.id,
'rawText', evidence.raw_text,
'domain', evidence.domain,
'eventSummary', evidence.event_summary,
'dateValue', evidence.date_value,
'datePrecision', evidence.date_precision,
'extractionStatus', evidence.extraction_status,
'scoreable', evidence.scoreable,
'correctsEvidenceIds', pg_catalog.to_jsonb(evidence.corrects_evidence_ids)
) order by evidence.created_at, evidence.id
)
from public.birth_time_rectification_event_evidence evidence
where evidence.case_id = v_case_id
), '[]'::jsonb),
'validation_receipts', coalesce((
select pg_catalog.jsonb_agg(
turn.output_validation_receipt order by turn.turn_version
)
from public.birth_time_rectification_turns turn
where turn.case_id = v_case_id
), '[]'::jsonb)
);
end;
$$;
create or replace function public.replay_conversational_rectification_action(
p_user_id uuid,
p_case_id uuid,
p_expected_version bigint,
p_action_id uuid,
p_action_kind text,
p_command_fingerprint text
)
returns jsonb
language plpgsql
stable
security definer
set search_path = ''
as $$
declare
v_receipt public.birth_time_rectification_action_receipts%rowtype;
begin
if p_user_id is null or p_case_id is null or p_action_id is null
or p_expected_version is null or p_expected_version < 0
or p_action_kind not in ('save_turn', 'pause', 'abandon', 'confirm')
or p_command_fingerprint !~ '^[0-9a-f]{64}$' then
raise exception 'conversational_action_conflict' using errcode = 'P0001';
end if;
select r.* into v_receipt
from public.birth_time_rectification_action_receipts r
where r.user_id = p_user_id
and r.case_id = p_case_id
and r.action_id = p_action_id;
if not found then
return null;
end if;
-- Receipts created before command identities were introduced still use the
-- mutation RPC's complete request fingerprint for their one-step replay.
if not (v_receipt.request ? 'commandFingerprint') then
return null;
end if;
if v_receipt.action_kind is distinct from p_action_kind
or v_receipt.expected_turn_version is distinct from p_expected_version
or v_receipt.request ->> 'commandFingerprint'
is distinct from p_command_fingerprint then
raise exception 'conversational_action_conflict' using errcode = 'P0001';
end if;
return v_receipt.response;
end;
$$;
create or replace function public.conversational_rectification_case_fits_load_limits(
p_user_id uuid,
p_case_id uuid,
p_status text,
p_turn_version bigint,
p_latest_turn jsonb,
p_private_candidate jsonb,
p_new_evidence jsonb,
p_validation_receipt jsonb
)
returns boolean
language plpgsql
stable
security definer
set search_path = ''
as $$
declare
v_evidence_count bigint;
v_receipt_count bigint;
v_existing_evidence jsonb;
v_new_evidence jsonb;
v_validation_receipts jsonb;
v_projection jsonb;
begin
if public.conversational_rectification_valid_public_turn(p_latest_turn) is not true
or public.conversational_rectification_valid_private_candidate(
p_private_candidate
) is not true
or public.conversational_rectification_valid_life_event_evidence_array(
p_new_evidence
) is not true
or public.conversational_rectification_valid_validation_receipt(
p_validation_receipt
) is not true then
return false;
end if;
select pg_catalog.count(*) into v_evidence_count
from public.birth_time_rectification_event_evidence evidence
where evidence.case_id = p_case_id;
select pg_catalog.count(*) into v_receipt_count
from public.birth_time_rectification_turns turn
where turn.case_id = p_case_id;
if v_evidence_count + pg_catalog.jsonb_array_length(p_new_evidence) > 2000
or v_receipt_count + 1 > 2000 then
return false;
end if;
select coalesce(pg_catalog.jsonb_agg(
pg_catalog.jsonb_build_object(
'id', evidence.id,
'rawText', evidence.raw_text,
'domain', evidence.domain,
'eventSummary', evidence.event_summary,
'dateValue', evidence.date_value,
'datePrecision', evidence.date_precision,
'extractionStatus', evidence.extraction_status,
'scoreable', evidence.scoreable,
'correctsEvidenceIds', pg_catalog.to_jsonb(evidence.corrects_evidence_ids)
) order by evidence.created_at, evidence.id
), '[]'::jsonb) into v_existing_evidence
from public.birth_time_rectification_event_evidence evidence
where evidence.case_id = p_case_id;
select coalesce(pg_catalog.jsonb_agg(
pg_catalog.jsonb_build_object(
'id', item.value ->> 'id',
'rawText', item.value ->> 'rawText',
'domain', item.value ->> 'domain',
'eventSummary', item.value ->> 'eventSummary',
'dateValue', case
when item.value -> 'dateValue' = 'null'::jsonb then null
else item.value ->> 'dateValue'
end,
'datePrecision', item.value ->> 'datePrecision',
'extractionStatus', item.value ->> 'extractionStatus',
'scoreable', case
when item.value ? 'scoreable' then (item.value ->> 'scoreable')::boolean
else false
end,
'correctsEvidenceIds', coalesce(
item.value -> 'correctsEvidenceIds', '[]'::jsonb
)
) order by item.ordinality
), '[]'::jsonb) into v_new_evidence
from pg_catalog.jsonb_array_elements(p_new_evidence) with ordinality item(value, ordinality);
select coalesce(pg_catalog.jsonb_agg(
turn.output_validation_receipt order by turn.turn_version
), '[]'::jsonb) || pg_catalog.jsonb_build_array(p_validation_receipt)
into v_validation_receipts
from public.birth_time_rectification_turns turn
where turn.case_id = p_case_id;
select pg_catalog.jsonb_build_object(
'case_id', c.id,
'user_id', c.user_id,
'status', p_status,
'turn_version', p_turn_version,
'revision_of_case_id', c.revision_of_case_id,
'imported_from_case_id', c.imported_from_case_id,
'baseline_active_time', case when c.baseline_active_time is null then null
else pg_catalog.to_char(c.baseline_active_time, 'HH24:MI') end,
'pending_consultation_question', c.pending_consultation_question,
'billing_state', billing.state,
'latest_turn', p_latest_turn,
'declared_birth_input', c.declared_birth_input,
'private_candidate', p_private_candidate,
'event_evidence', v_existing_evidence || v_new_evidence,
'validation_receipts', v_validation_receipts
) into v_projection
from public.birth_time_rectification_cases c
left join public.birth_time_rectification_billing billing
on billing.case_id = c.id and billing.user_id = c.user_id
where c.id = p_case_id
and c.user_id = p_user_id
and c.journey_protocol = 'conversational-evidence-v3';
return v_evidence_count + pg_catalog.jsonb_array_length(p_new_evidence) <= 2000
and v_receipt_count + 1 <= 2000
and v_projection is not null
and pg_catalog.octet_length(v_projection::text) <= 4194304;
exception when others then
return false;
end;
$$;
create or replace function public.create_conversational_rectification_case(
p_user_id uuid,
p_case_id uuid,
p_expected_version bigint,
p_action_id uuid,
p_revision_of_case_id uuid,
p_pending_consultation_question text,
p_declared_birth_input jsonb,
p_first_turn jsonb,
p_validation_receipt jsonb,
p_private_candidate jsonb
)
returns jsonb
language plpgsql
security definer
set search_path = ''
as $$
declare
v_case public.birth_time_rectification_cases%rowtype;
v_profile public.profiles%rowtype;
v_billing public.birth_time_rectification_billing%rowtype;
v_receipt public.birth_time_rectification_action_receipts%rowtype;
v_response jsonb;
v_fingerprint text := public.conversational_rectification_fingerprint(
pg_catalog.jsonb_build_object(
'kind', 'create', 'userId', p_user_id, 'caseId', p_case_id,
'expectedVersion', p_expected_version, 'actionId', p_action_id,
'revisionOfCaseId', p_revision_of_case_id,
'pendingConsultationQuestion', p_pending_consultation_question,
'declaredBirthInput', p_declared_birth_input,
'firstTurn', p_first_turn, 'validationReceipt', p_validation_receipt,
'privateCandidate', p_private_candidate
)
);
begin
if p_user_id is null or p_case_id is null or p_action_id is null
or p_case_id is distinct from p_action_id
or p_expected_version is null or p_expected_version < 0 then
raise exception 'conversational_action_conflict' using errcode = 'P0001';
end if;
perform pg_catalog.pg_advisory_xact_lock(
pg_catalog.hashtextextended(
p_user_id::text || ':conversational-rectification-case',
0
)
);
perform pg_catalog.pg_advisory_xact_lock(
pg_catalog.hashtextextended(p_user_id::text || ':' || p_action_id::text, 0)
);
select c.* into v_case
from public.birth_time_rectification_cases c
where c.id = p_case_id
for update;
if found then
if v_case.user_id is distinct from p_user_id
or v_case.journey_protocol is distinct from 'conversational-evidence-v3' then
raise exception 'conversational_case_not_found' using errcode = 'P0001';
end if;
select r.* into v_receipt
from public.birth_time_rectification_action_receipts r
where r.case_id = p_case_id and r.action_id = p_action_id
for update;
if found
and v_receipt.user_id is not distinct from p_user_id
and v_receipt.action_kind is not distinct from 'create'
and v_receipt.expected_turn_version is not distinct from p_expected_version
and v_receipt.request_fingerprint is not distinct from v_fingerprint then
return v_receipt.response;
end if;
raise exception 'conversational_action_conflict' using errcode = 'P0001';
end if;
if p_expected_version is distinct from 0 then
raise exception 'conversational_stale_turn' using errcode = 'P0001';
end if;
perform 1
from public.birth_time_rectification_cases active_case
where active_case.user_id = p_user_id
and active_case.id <> p_case_id
and active_case.journey_protocol = 'conversational-evidence-v3'
and active_case.status in ('starting', 'active', 'paused', 'confirming')
for update;
if found then
raise exception 'conversational_action_conflict' using errcode = 'P0001';
end if;
select profile.* into v_profile
from public.profiles profile
where profile.id = p_user_id
for update;
if not found then
raise exception 'conversational_case_not_found' using errcode = 'P0001';
end if;
select b.* into v_billing
from public.birth_time_rectification_billing b
where b.case_id = p_case_id and b.user_id = p_user_id
for update;
if not found
or v_billing.reserve_action_id is distinct from p_action_id
or v_billing.state not in ('reserved', 'charged') then
raise exception 'conversational_billing_failed' using errcode = 'P0001';
end if;
if p_revision_of_case_id is not null then
perform 1
from public.birth_time_rectification_cases prior
where prior.id = p_revision_of_case_id
and prior.user_id = p_user_id
and prior.journey_protocol = 'conversational-evidence-v3'
and prior.status in ('completed', 'abandoned')
for update;
if not found then
raise exception 'conversational_case_not_found' using errcode = 'P0001';
end if;
end if;
if public.conversational_rectification_valid_declared_birth_input(
p_declared_birth_input
) is not true
or public.conversational_rectification_valid_public_turn(p_first_turn) is not true
or public.conversational_rectification_valid_validation_receipt(
p_validation_receipt
) is not true
or public.conversational_rectification_valid_private_candidate(
p_private_candidate
) is not true
or p_first_turn ->> 'caseId' is distinct from p_case_id::text
or p_first_turn ->> 'journeyProtocol' is distinct from 'conversational-evidence-v3'
or (p_first_turn ->> 'turnVersion')::bigint is distinct from 0
or p_first_turn ->> 'status' is distinct from 'active'
or nullif(p_first_turn ->> 'pendingConsultationQuestion', '')
is distinct from nullif(p_pending_consultation_question, '')
or pg_catalog.jsonb_path_exists(p_first_turn, '$.**.candidateWeights')
or pg_catalog.jsonb_path_exists(p_first_turn, '$.**.candidateScores')
or pg_catalog.jsonb_path_exists(p_first_turn, '$.**.partitionId')
or pg_catalog.jsonb_path_exists(p_first_turn, '$.**.rawModelOutput')
or pg_catalog.jsonb_path_exists(p_first_turn, '$.**.systemPrompt') then
raise exception 'conversational_action_conflict' using errcode = 'P0001';
end if;
insert into public.birth_time_rectification_cases (
id, user_id, journey_protocol, status, reported_date, reported_time,
reported_period, source, uncertainty_before_minutes,
uncertainty_after_minutes, declared_birth_input,
journey_snapshot, turn_version, turn_state,
candidate_result, candidate_result_id, candidate_start, candidate_end,
event_scoring_version, revision_of_case_id, baseline_active_time,
pending_consultation_question, updated_at
) values (
p_case_id, p_user_id, 'conversational-evidence-v3', 'active',
(p_declared_birth_input ->> 'birthDate')::date,
nullif(p_declared_birth_input ->> 'reportedTime', '')::time,
nullif(p_declared_birth_input ->> 'reportedPeriod', ''),
p_declared_birth_input ->> 'source',
nullif(p_declared_birth_input ->> 'uncertaintyBeforeMinutes', '')::integer,
nullif(p_declared_birth_input ->> 'uncertaintyAfterMinutes', '')::integer,
p_declared_birth_input, p_first_turn, 0, p_first_turn, p_private_candidate,
nullif(p_private_candidate ->> 'resultId', '')::uuid,
nullif(p_private_candidate ->> 'rangeStart', '')::time,
nullif(p_private_candidate ->> 'rangeEnd', '')::time,
nullif(p_private_candidate ->> 'calculationVersion', ''),
p_revision_of_case_id, v_profile.active_birth_time,
nullif(p_pending_consultation_question, ''), pg_catalog.now()
);
insert into public.birth_time_rectification_turns (
case_id, turn_version, narrative, candidate, technical_receipt,
evidence_request, evidence_recap, actions, output_validation_receipt
) values (
p_case_id, 0, p_first_turn ->> 'narrative', p_first_turn -> 'candidate',
p_first_turn -> 'technicalReceipt',
nullif(p_first_turn -> 'evidenceRequest', 'null'::jsonb),
coalesce(p_first_turn -> 'evidenceRecap', '[]'::jsonb),
coalesce(p_first_turn -> 'actions', '[]'::jsonb),
p_validation_receipt
);
v_response := public.conversational_rectification_case_projection(p_user_id, p_case_id);
insert into public.birth_time_rectification_action_receipts (
case_id, action_id, user_id, action_kind, expected_turn_version,
result_turn_version, request_fingerprint, request, response
) values (
p_case_id, p_action_id, p_user_id, 'create', p_expected_version,
0, v_fingerprint,
public.conversational_rectification_action_request(
'create', p_user_id, p_case_id, p_expected_version, p_action_id, v_fingerprint
),
v_response
);
return v_response;
end;
$$;
create or replace function public.save_conversational_rectification_turn(
p_user_id uuid,
p_case_id uuid,
p_expected_version bigint,
p_action_id uuid,
p_turn jsonb,
p_evidence jsonb,
p_validation_receipt jsonb,
p_private_candidate jsonb,
p_command_fingerprint text default null
)
returns jsonb
language plpgsql
security definer
set search_path = ''
as $$
declare
v_case public.birth_time_rectification_cases%rowtype;
v_billing public.birth_time_rectification_billing%rowtype;
v_receipt public.birth_time_rectification_action_receipts%rowtype;
v_turn_id uuid := pg_catalog.gen_random_uuid();
v_response jsonb;
v_fingerprint text := public.conversational_rectification_fingerprint(
pg_catalog.jsonb_build_object(
'kind', 'save_turn', 'userId', p_user_id, 'caseId', p_case_id,
'expectedVersion', p_expected_version, 'actionId', p_action_id,
'turn', p_turn, 'evidence', p_evidence,
'validationReceipt', p_validation_receipt,
'privateCandidate', p_private_candidate
)
);
begin
if p_user_id is null or p_case_id is null or p_action_id is null
or p_expected_version is null or p_expected_version < 0
or (p_command_fingerprint is not null
and p_command_fingerprint !~ '^[0-9a-f]{64}$') then
raise exception 'conversational_action_conflict' using errcode = 'P0001';
end if;
perform pg_catalog.pg_advisory_xact_lock(
pg_catalog.hashtextextended(p_user_id::text || ':' || p_action_id::text, 0)
);
select c.* into v_case
from public.birth_time_rectification_cases c
where c.id = p_case_id and c.user_id = p_user_id
for update;
if not found or v_case.journey_protocol is distinct from 'conversational-evidence-v3' then
raise exception 'conversational_case_not_found' using errcode = 'P0001';
end if;
select r.* into v_receipt
from public.birth_time_rectification_action_receipts r
where r.case_id = p_case_id and r.action_id = p_action_id
for update;
if found then
if v_receipt.user_id is distinct from p_user_id
or v_receipt.action_kind is distinct from 'save_turn'
or v_receipt.expected_turn_version is distinct from p_expected_version
or (case when v_receipt.request ? 'commandFingerprint' then
v_receipt.request ->> 'commandFingerprint'
is distinct from p_command_fingerprint
else v_receipt.request_fingerprint is distinct from v_fingerprint end) then
raise exception 'conversational_action_conflict' using errcode = 'P0001';
end if;
return v_receipt.response;
end if;
if v_case.turn_version is distinct from p_expected_version then
raise exception 'conversational_stale_turn' using errcode = 'P0001';
end if;
select b.* into v_billing
from public.birth_time_rectification_billing b
where b.case_id = p_case_id and b.user_id = p_user_id
for update;
if not found or v_billing.state not in ('charged', 'migration_waived') then
raise exception 'conversational_billing_failed' using errcode = 'P0001';
end if;
if v_case.status not in ('active', 'paused', 'confirming')
or public.conversational_rectification_valid_public_turn(p_turn) is not true
or public.conversational_rectification_valid_life_event_evidence_array(
p_evidence
) is not true
or public.conversational_rectification_valid_validation_receipt(
p_validation_receipt
) is not true
or public.conversational_rectification_valid_private_candidate(
p_private_candidate
) is not true
or p_turn ->> 'caseId' is distinct from p_case_id::text
or p_turn ->> 'journeyProtocol' is distinct from 'conversational-evidence-v3'
or (p_turn ->> 'turnVersion')::bigint is distinct from p_expected_version + 1
or p_turn ->> 'status' not in ('active', 'confirming')
or nullif(p_turn ->> 'pendingConsultationQuestion', '')
is distinct from nullif(v_case.pending_consultation_question, '')
or pg_catalog.jsonb_path_exists(p_turn, '$.**.candidateWeights')
or pg_catalog.jsonb_path_exists(p_turn, '$.**.candidateScores')
or pg_catalog.jsonb_path_exists(p_turn, '$.**.partitionId')
or pg_catalog.jsonb_path_exists(p_turn, '$.**.rawModelOutput')
or pg_catalog.jsonb_path_exists(p_turn, '$.**.systemPrompt') then
raise exception 'conversational_action_conflict' using errcode = 'P0001';
end if;
-- Correction targets are account-case evidence tips. Under the locked case version,
-- reject one-to-many replacement attempts, missing/cross-case targets, or
-- already-retired targets before writing a turn.
if exists (
select 1
from (
select correction.value::uuid as target_id
from pg_catalog.jsonb_array_elements(p_evidence) item(value)
cross join lateral pg_catalog.jsonb_array_elements_text(
coalesce(item.value -> 'correctsEvidenceIds', '[]'::jsonb)
) correction(value)
group by correction.value::uuid
having pg_catalog.count(*) > 1
) duplicate_correction_target
) then
raise exception 'conversational_action_conflict' using errcode = 'P0001';
end if;
if exists (
select 1
from pg_catalog.jsonb_array_elements(p_evidence) item(value)
cross join lateral pg_catalog.jsonb_array_elements_text(
coalesce(item.value -> 'correctsEvidenceIds', '[]'::jsonb)
) correction(value)
where not exists (
select 1
from public.birth_time_rectification_event_evidence target
where target.case_id = p_case_id
and target.id = correction.value::uuid
)
or exists (
select 1
from public.birth_time_rectification_event_evidence evidence
cross join lateral pg_catalog.unnest(
evidence.corrects_evidence_ids
) retired(target_id)
where evidence.case_id = p_case_id
and retired.target_id = correction.value::uuid
)
) then
raise exception 'conversational_action_conflict' using errcode = 'P0001';
end if;
if public.conversational_rectification_case_fits_load_limits(
p_user_id, p_case_id, p_turn ->> 'status', p_expected_version + 1,
p_turn, p_private_candidate, p_evidence, p_validation_receipt
) is not true then
raise exception 'conversational_action_conflict' using errcode = 'P0001';
end if;
insert into public.birth_time_rectification_turns (
id, case_id, turn_version, narrative, candidate, technical_receipt,
evidence_request, evidence_recap, actions, output_validation_receipt
) values (
v_turn_id, p_case_id, p_expected_version + 1,
p_turn ->> 'narrative', p_turn -> 'candidate', p_turn -> 'technicalReceipt',
nullif(p_turn -> 'evidenceRequest', 'null'::jsonb),
coalesce(p_turn -> 'evidenceRecap', '[]'::jsonb),
coalesce(p_turn -> 'actions', '[]'::jsonb),
p_validation_receipt
);
insert into public.birth_time_rectification_event_evidence (
id, case_id, source_turn_id, raw_text, domain, event_summary,
date_value, date_precision, extraction_status, corrects_evidence_ids, scoreable
)
select
(item ->> 'id')::uuid, p_case_id, v_turn_id, item ->> 'rawText',
item ->> 'domain', item ->> 'eventSummary',
case when item -> 'dateValue' = 'null'::jsonb then null
else item ->> 'dateValue' end,
item ->> 'datePrecision',
item ->> 'extractionStatus',
case when item ? 'correctsEvidenceIds' then array(
select correction.value::uuid
from pg_catalog.jsonb_array_elements_text(
item -> 'correctsEvidenceIds'
) correction(value)
) else '{}'::uuid[] end,
case when item ? 'scoreable' then (item ->> 'scoreable')::boolean
else false end
from pg_catalog.jsonb_array_elements(p_evidence) item;
update public.birth_time_rectification_cases
set status = p_turn ->> 'status',
turn_version = p_expected_version + 1,
turn_state = p_turn,
journey_snapshot = p_turn,
candidate_result = p_private_candidate,
candidate_result_id = nullif(p_private_candidate ->> 'resultId', '')::uuid,
candidate_start = nullif(p_private_candidate ->> 'rangeStart', '')::time,
candidate_end = nullif(p_private_candidate ->> 'rangeEnd', '')::time,
event_scoring_version = nullif(p_private_candidate ->> 'calculationVersion', ''),
updated_at = pg_catalog.now()
where id = p_case_id and user_id = p_user_id
and turn_version = p_expected_version;
if not found then
raise exception 'conversational_stale_turn' using errcode = 'P0001';
end if;
v_response := public.conversational_rectification_case_projection(p_user_id, p_case_id);
insert into public.birth_time_rectification_action_receipts (
case_id, action_id, user_id, action_kind, expected_turn_version,
result_turn_version, request_fingerprint, request, response
) values (
p_case_id, p_action_id, p_user_id, 'save_turn', p_expected_version,
p_expected_version + 1, v_fingerprint,
public.conversational_rectification_action_request(
'save_turn', p_user_id, p_case_id, p_expected_version,
p_action_id, v_fingerprint
) || case when p_command_fingerprint is null then '{}'::jsonb
else pg_catalog.jsonb_build_object(
'commandFingerprint', p_command_fingerprint
) end,
v_response
);
return v_response;
end;
$$;
create or replace function public.pause_conversational_rectification_case(
p_user_id uuid,
p_case_id uuid,
p_expected_version bigint,
p_action_id uuid,
p_turn jsonb,
p_validation_receipt jsonb,
p_command_fingerprint text default null
)
returns jsonb
language plpgsql
security definer
set search_path = ''
as $$
declare
v_case public.birth_time_rectification_cases%rowtype;
v_billing public.birth_time_rectification_billing%rowtype;
v_receipt public.birth_time_rectification_action_receipts%rowtype;
v_response jsonb;
v_fingerprint text := public.conversational_rectification_fingerprint(
pg_catalog.jsonb_build_object(
'kind', 'pause', 'userId', p_user_id, 'caseId', p_case_id,
'expectedVersion', p_expected_version, 'actionId', p_action_id,
'turn', p_turn, 'validationReceipt', p_validation_receipt
)
);
begin
if p_user_id is null or p_case_id is null or p_action_id is null
or p_expected_version is null or p_expected_version < 0
or (p_command_fingerprint is not null
and p_command_fingerprint !~ '^[0-9a-f]{64}$') then
raise exception 'conversational_action_conflict' using errcode = 'P0001';
end if;
perform pg_catalog.pg_advisory_xact_lock(
pg_catalog.hashtextextended(p_user_id::text || ':' || p_action_id::text, 0)
);
select c.* into v_case
from public.birth_time_rectification_cases c
where c.id = p_case_id and c.user_id = p_user_id
for update;
if not found or v_case.journey_protocol is distinct from 'conversational-evidence-v3' then
raise exception 'conversational_case_not_found' using errcode = 'P0001';
end if;
select r.* into v_receipt
from public.birth_time_rectification_action_receipts r
where r.case_id = p_case_id and r.action_id = p_action_id
for update;
if found then
if v_receipt.user_id is distinct from p_user_id
or v_receipt.action_kind is distinct from 'pause'
or v_receipt.expected_turn_version is distinct from p_expected_version
or (case when v_receipt.request ? 'commandFingerprint' then
v_receipt.request ->> 'commandFingerprint'
is distinct from p_command_fingerprint
else v_receipt.request_fingerprint is distinct from v_fingerprint end) then
raise exception 'conversational_action_conflict' using errcode = 'P0001';
end if;
return v_receipt.response;
end if;
if v_case.turn_version is distinct from p_expected_version then
raise exception 'conversational_stale_turn' using errcode = 'P0001';
end if;
select b.* into v_billing
from public.birth_time_rectification_billing b
where b.case_id = p_case_id and b.user_id = p_user_id
for update;
if not found or v_billing.state not in ('charged', 'migration_waived') then
raise exception 'conversational_billing_failed' using errcode = 'P0001';
end if;
if v_case.status not in ('active', 'confirming')
or public.conversational_rectification_valid_public_turn(p_turn) is not true
or public.conversational_rectification_valid_validation_receipt(
p_validation_receipt
) is not true
or p_turn ->> 'caseId' is distinct from p_case_id::text
or p_turn ->> 'journeyProtocol' is distinct from 'conversational-evidence-v3'
or (p_turn ->> 'turnVersion')::bigint is distinct from p_expected_version + 1
or p_turn ->> 'status' is distinct from 'paused'
or nullif(p_turn ->> 'pendingConsultationQuestion', '')
is distinct from nullif(v_case.pending_consultation_question, '')
or pg_catalog.jsonb_path_exists(p_turn, '$.**.candidateWeights')
or pg_catalog.jsonb_path_exists(p_turn, '$.**.candidateScores')
or pg_catalog.jsonb_path_exists(p_turn, '$.**.partitionId')
or pg_catalog.jsonb_path_exists(p_turn, '$.**.rawModelOutput')
or pg_catalog.jsonb_path_exists(p_turn, '$.**.systemPrompt') then
raise exception 'conversational_action_conflict' using errcode = 'P0001';
end if;
if public.conversational_rectification_case_fits_load_limits(
p_user_id, p_case_id, 'paused', p_expected_version + 1,
p_turn, v_case.candidate_result, '[]'::jsonb, p_validation_receipt
) is not true then
raise exception 'conversational_action_conflict' using errcode = 'P0001';
end if;
insert into public.birth_time_rectification_turns (
case_id, turn_version, narrative, candidate, technical_receipt,
evidence_request, evidence_recap, actions, output_validation_receipt
) values (
p_case_id, p_expected_version + 1, p_turn ->> 'narrative',
p_turn -> 'candidate', p_turn -> 'technicalReceipt',
nullif(p_turn -> 'evidenceRequest', 'null'::jsonb),
coalesce(p_turn -> 'evidenceRecap', '[]'::jsonb),
coalesce(p_turn -> 'actions', '[]'::jsonb),
p_validation_receipt
);
update public.birth_time_rectification_cases
set status = 'paused', turn_version = p_expected_version + 1,
turn_state = p_turn, journey_snapshot = p_turn,
updated_at = pg_catalog.now()
where id = p_case_id and user_id = p_user_id
and turn_version = p_expected_version;
if not found then
raise exception 'conversational_stale_turn' using errcode = 'P0001';
end if;
v_response := public.conversational_rectification_case_projection(p_user_id, p_case_id);
insert into public.birth_time_rectification_action_receipts (
case_id, action_id, user_id, action_kind, expected_turn_version,
result_turn_version, request_fingerprint, request, response
) values (
p_case_id, p_action_id, p_user_id, 'pause', p_expected_version,
p_expected_version + 1, v_fingerprint,
public.conversational_rectification_action_request(
'pause', p_user_id, p_case_id, p_expected_version, p_action_id, v_fingerprint
) || case when p_command_fingerprint is null then '{}'::jsonb
else pg_catalog.jsonb_build_object(
'commandFingerprint', p_command_fingerprint
) end,
v_response
);
return v_response;
end;
$$;
create or replace function public.abandon_conversational_rectification_case(
p_user_id uuid,
p_case_id uuid,
p_expected_version bigint,
p_action_id uuid,
p_turn jsonb,
p_validation_receipt jsonb,
p_command_fingerprint text default null
)
returns jsonb
language plpgsql
security definer
set search_path = ''
as $$
declare
v_case public.birth_time_rectification_cases%rowtype;
v_billing public.birth_time_rectification_billing%rowtype;
v_receipt public.birth_time_rectification_action_receipts%rowtype;
v_response jsonb;
v_fingerprint text := public.conversational_rectification_fingerprint(
pg_catalog.jsonb_build_object(
'kind', 'abandon', 'userId', p_user_id, 'caseId', p_case_id,
'expectedVersion', p_expected_version, 'actionId', p_action_id,
'turn', p_turn, 'validationReceipt', p_validation_receipt
)
);
begin
if p_user_id is null or p_case_id is null or p_action_id is null
or p_expected_version is null or p_expected_version < 0
or (p_command_fingerprint is not null
and p_command_fingerprint !~ '^[0-9a-f]{64}$') then
raise exception 'conversational_action_conflict' using errcode = 'P0001';
end if;
perform pg_catalog.pg_advisory_xact_lock(
pg_catalog.hashtextextended(p_user_id::text || ':' || p_action_id::text, 0)
);
select c.* into v_case
from public.birth_time_rectification_cases c
where c.id = p_case_id and c.user_id = p_user_id
for update;
if not found or v_case.journey_protocol is distinct from 'conversational-evidence-v3' then
raise exception 'conversational_case_not_found' using errcode = 'P0001';
end if;
select r.* into v_receipt
from public.birth_time_rectification_action_receipts r
where r.case_id = p_case_id and r.action_id = p_action_id
for update;
if found then
if v_receipt.user_id is distinct from p_user_id
or v_receipt.action_kind is distinct from 'abandon'
or v_receipt.expected_turn_version is distinct from p_expected_version
or (case when v_receipt.request ? 'commandFingerprint' then
v_receipt.request ->> 'commandFingerprint'
is distinct from p_command_fingerprint
else v_receipt.request_fingerprint is distinct from v_fingerprint end) then
raise exception 'conversational_action_conflict' using errcode = 'P0001';
end if;
return v_receipt.response;
end if;
if v_case.turn_version is distinct from p_expected_version then
raise exception 'conversational_stale_turn' using errcode = 'P0001';
end if;
select b.* into v_billing
from public.birth_time_rectification_billing b
where b.case_id = p_case_id and b.user_id = p_user_id
for update;
if not found or v_billing.state not in ('charged', 'migration_waived') then
raise exception 'conversational_billing_failed' using errcode = 'P0001';
end if;
if v_case.status not in ('active', 'paused', 'confirming')
or public.conversational_rectification_valid_public_turn(p_turn) is not true
or public.conversational_rectification_valid_validation_receipt(
p_validation_receipt
) is not true
or p_turn ->> 'caseId' is distinct from p_case_id::text
or p_turn ->> 'journeyProtocol' is distinct from 'conversational-evidence-v3'
or (p_turn ->> 'turnVersion')::bigint is distinct from p_expected_version + 1
or p_turn ->> 'status' is distinct from 'abandoned'
or nullif(p_turn ->> 'pendingConsultationQuestion', '')
is distinct from nullif(v_case.pending_consultation_question, '')
or pg_catalog.jsonb_path_exists(p_turn, '$.**.candidateWeights')
or pg_catalog.jsonb_path_exists(p_turn, '$.**.candidateScores')
or pg_catalog.jsonb_path_exists(p_turn, '$.**.partitionId')
or pg_catalog.jsonb_path_exists(p_turn, '$.**.rawModelOutput')
or pg_catalog.jsonb_path_exists(p_turn, '$.**.systemPrompt') then
raise exception 'conversational_action_conflict' using errcode = 'P0001';
end if;
if public.conversational_rectification_case_fits_load_limits(
p_user_id, p_case_id, 'abandoned', p_expected_version + 1,
p_turn, v_case.candidate_result, '[]'::jsonb, p_validation_receipt
) is not true then
raise exception 'conversational_action_conflict' using errcode = 'P0001';
end if;
insert into public.birth_time_rectification_turns (
case_id, turn_version, narrative, candidate, technical_receipt,
evidence_request, evidence_recap, actions, output_validation_receipt
) values (
p_case_id, p_expected_version + 1, p_turn ->> 'narrative',
p_turn -> 'candidate', p_turn -> 'technicalReceipt',
nullif(p_turn -> 'evidenceRequest', 'null'::jsonb),
coalesce(p_turn -> 'evidenceRecap', '[]'::jsonb),
coalesce(p_turn -> 'actions', '[]'::jsonb),
p_validation_receipt
);
update public.birth_time_rectification_cases
set status = 'abandoned', turn_version = p_expected_version + 1,
turn_state = p_turn, journey_snapshot = p_turn,
updated_at = pg_catalog.now()
where id = p_case_id and user_id = p_user_id
and turn_version = p_expected_version;
if not found then
raise exception 'conversational_stale_turn' using errcode = 'P0001';
end if;
v_response := public.conversational_rectification_case_projection(p_user_id, p_case_id);
insert into public.birth_time_rectification_action_receipts (
case_id, action_id, user_id, action_kind, expected_turn_version,
result_turn_version, request_fingerprint, request, response
) values (
p_case_id, p_action_id, p_user_id, 'abandon', p_expected_version,
p_expected_version + 1, v_fingerprint,
public.conversational_rectification_action_request(
'abandon', p_user_id, p_case_id, p_expected_version, p_action_id, v_fingerprint
) || case when p_command_fingerprint is null then '{}'::jsonb
else pg_catalog.jsonb_build_object(
'commandFingerprint', p_command_fingerprint
) end,
v_response
);
return v_response;
end;
$$;
create or replace function public.confirm_conversational_rectification_candidate(
p_user_id uuid,
p_case_id uuid,
p_expected_version bigint,
p_action_id uuid,
p_result_id uuid,
p_time time without time zone,
p_calculation_version text,
p_turn jsonb,
p_validation_receipt jsonb,
p_command_fingerprint text default null
)
returns jsonb
language plpgsql
security definer
set search_path = ''
as $$
declare
v_case public.birth_time_rectification_cases%rowtype;
v_profile public.profiles%rowtype;
v_billing public.birth_time_rectification_billing%rowtype;
v_current_turn public.birth_time_rectification_turns%rowtype;
v_receipt public.birth_time_rectification_action_receipts%rowtype;
v_response jsonb;
v_time text := pg_catalog.to_char(p_time, 'HH24:MI');
v_fingerprint text := public.conversational_rectification_fingerprint(
pg_catalog.jsonb_build_object(
'kind', 'confirm', 'userId', p_user_id, 'caseId', p_case_id,
'expectedVersion', p_expected_version, 'actionId', p_action_id,
'resultId', p_result_id, 'time', p_time,
'calculationVersion', p_calculation_version, 'turn', p_turn,
'validationReceipt', p_validation_receipt
)
);
begin
if p_command_fingerprint is not null
and p_command_fingerprint !~ '^[0-9a-f]{64}$' then
raise exception 'conversational_action_conflict' using errcode = 'P0001';
end if;
if p_user_id is null or p_case_id is null or p_action_id is null
or p_result_id is null or p_time is null
or extract(second from p_time) is distinct from 0
or nullif(p_calculation_version, '') is null
or p_expected_version is null or p_expected_version < 0 then
raise exception 'conversational_candidate_changed' using errcode = 'P0001';
end if;
perform pg_catalog.pg_advisory_xact_lock(
pg_catalog.hashtextextended(p_user_id::text || ':' || p_action_id::text, 0)
);
select c.* into v_case
from public.birth_time_rectification_cases c
where c.id = p_case_id and c.user_id = p_user_id
for update;
if not found or v_case.journey_protocol is distinct from 'conversational-evidence-v3' then
raise exception 'conversational_case_not_found' using errcode = 'P0001';
end if;
select r.* into v_receipt
from public.birth_time_rectification_action_receipts r
where r.case_id = p_case_id and r.action_id = p_action_id
for update;
if found then
if v_receipt.user_id is distinct from p_user_id
or v_receipt.action_kind is distinct from 'confirm'
or v_receipt.expected_turn_version is distinct from p_expected_version
or (case when v_receipt.request ? 'commandFingerprint' then
v_receipt.request ->> 'commandFingerprint'
is distinct from p_command_fingerprint
else v_receipt.request_fingerprint is distinct from v_fingerprint end) then
raise exception 'conversational_action_conflict' using errcode = 'P0001';
end if;
return v_receipt.response;
end if;
if v_case.turn_version is distinct from p_expected_version then
raise exception 'conversational_stale_turn' using errcode = 'P0001';
end if;
select b.* into v_billing
from public.birth_time_rectification_billing b
where b.case_id = p_case_id and b.user_id = p_user_id
for update;
if not found or v_billing.state not in ('charged', 'migration_waived') then
raise exception 'conversational_billing_failed' using errcode = 'P0001';
end if;
select turn.* into v_current_turn
from public.birth_time_rectification_turns turn
where turn.case_id = p_case_id and turn.turn_version = p_expected_version
for update;
if not found then
raise exception 'conversational_candidate_changed' using errcode = 'P0001';
end if;
select profile.* into v_profile
from public.profiles profile
where profile.id = p_user_id
for update;
if not found or v_profile.active_birth_time is distinct from v_case.baseline_active_time then
raise exception 'conversational_candidate_changed' using errcode = 'P0001';
end if;
if v_case.status is distinct from 'confirming'
or v_case.candidate_result_id is distinct from p_result_id
or v_case.candidate_result ->> 'resultId' is distinct from p_result_id::text
or v_case.candidate_result ->> 'representativeTime' is distinct from v_time
or v_case.candidate_result ->> 'calculationVersion'
is distinct from p_calculation_version
or v_case.event_scoring_version is distinct from p_calculation_version
or v_current_turn.candidate ->> 'status' is distinct from 'ready_for_confirmation'
or v_current_turn.candidate ->> 'representativeTime' is distinct from v_time
or v_current_turn.technical_receipt ->> 'calculationVersion'
is distinct from p_calculation_version
or public.conversational_rectification_valid_public_turn(p_turn) is not true
or public.conversational_rectification_valid_validation_receipt(
p_validation_receipt
) is not true
or p_turn ->> 'caseId' is distinct from p_case_id::text
or p_turn ->> 'journeyProtocol' is distinct from 'conversational-evidence-v3'
or (p_turn ->> 'turnVersion')::bigint is distinct from p_expected_version + 1
or p_turn ->> 'status' is distinct from 'completed'
or p_turn #>> '{candidate,status}' is distinct from 'confirmed'
or p_turn #>> '{candidate,representativeTime}' is distinct from v_time
or p_turn #>> '{technicalReceipt,calculationVersion}'
is distinct from p_calculation_version
or nullif(p_turn ->> 'pendingConsultationQuestion', '')
is distinct from nullif(v_case.pending_consultation_question, '')
or pg_catalog.jsonb_path_exists(p_turn, '$.**.candidateWeights')
or pg_catalog.jsonb_path_exists(p_turn, '$.**.candidateScores')
or pg_catalog.jsonb_path_exists(p_turn, '$.**.partitionId')
or pg_catalog.jsonb_path_exists(p_turn, '$.**.rawModelOutput')
or pg_catalog.jsonb_path_exists(p_turn, '$.**.systemPrompt') then
raise exception 'conversational_candidate_changed' using errcode = 'P0001';
end if;
if public.conversational_rectification_case_fits_load_limits(
p_user_id, p_case_id, 'completed', p_expected_version + 1,
p_turn, v_case.candidate_result, '[]'::jsonb, p_validation_receipt
) is not true then
raise exception 'conversational_action_conflict' using errcode = 'P0001';
end if;
insert into public.birth_time_rectification_turns (
case_id, turn_version, narrative, candidate, technical_receipt,
evidence_request, evidence_recap, actions, output_validation_receipt
) values (
p_case_id, p_expected_version + 1, p_turn ->> 'narrative',
p_turn -> 'candidate', p_turn -> 'technicalReceipt',
nullif(p_turn -> 'evidenceRequest', 'null'::jsonb),
coalesce(p_turn -> 'evidenceRecap', '[]'::jsonb),
coalesce(p_turn -> 'actions', '[]'::jsonb),
p_validation_receipt
);
update public.birth_time_rectification_cases
set status = 'completed',
turn_version = p_expected_version + 1,
turn_state = p_turn,
journey_snapshot = p_turn,
confirmed_time = p_time,
confirmed_at = pg_catalog.now(),
updated_at = pg_catalog.now()
where id = p_case_id and user_id = p_user_id
and turn_version = p_expected_version;
if not found then
raise exception 'conversational_stale_turn' using errcode = 'P0001';
end if;
update public.profiles
set active_birth_time = p_time,
birth_time = p_time,
birth_time_status = 'confirmed',
rectification_case_id = p_case_id,
updated_at = pg_catalog.now()
where id = p_user_id
and active_birth_time is not distinct from v_case.baseline_active_time;
if not found then
raise exception 'conversational_candidate_changed' using errcode = 'P0001';
end if;
v_response := public.conversational_rectification_case_projection(p_user_id, p_case_id);
insert into public.birth_time_rectification_action_receipts (
case_id, action_id, user_id, action_kind, expected_turn_version,
result_turn_version, request_fingerprint, request, response
) values (
p_case_id, p_action_id, p_user_id, 'confirm', p_expected_version,
p_expected_version + 1, v_fingerprint,
public.conversational_rectification_action_request(
'confirm', p_user_id, p_case_id, p_expected_version, p_action_id, v_fingerprint
) || case when p_command_fingerprint is null then '{}'::jsonb
else pg_catalog.jsonb_build_object(
'commandFingerprint', p_command_fingerprint
) end,
v_response
);
return v_response;
end;
$$;
create or replace function public.import_legacy_conversational_rectification_case(
p_user_id uuid,
p_case_id uuid,
p_legacy_case_id uuid,
p_expected_version bigint,
p_action_id uuid,
p_price integer,
p_pending_consultation_question text,
p_first_turn jsonb,
p_validation_receipt jsonb,
p_private_candidate jsonb
)
returns jsonb
language plpgsql
security definer
set search_path = ''
as $$
declare
v_case public.birth_time_rectification_cases%rowtype;
v_legacy public.birth_time_rectification_cases%rowtype;
v_profile public.profiles%rowtype;
v_billing public.birth_time_rectification_billing%rowtype;
v_receipt public.birth_time_rectification_action_receipts%rowtype;
v_declared_birth_input jsonb;
v_response jsonb;
v_fingerprint text := public.conversational_rectification_fingerprint(
pg_catalog.jsonb_build_object(
'kind', 'import_legacy', 'userId', p_user_id, 'caseId', p_case_id,
'legacyCaseId', p_legacy_case_id, 'expectedVersion', p_expected_version,
'actionId', p_action_id, 'price', p_price,
'pendingConsultationQuestion', p_pending_consultation_question,
'firstTurn', p_first_turn, 'validationReceipt', p_validation_receipt,
'privateCandidate', p_private_candidate
)
);
begin
if p_user_id is null or p_case_id is null or p_legacy_case_id is null
or p_action_id is null or p_case_id is distinct from p_action_id
or p_expected_version is null or p_expected_version < 0
or p_price is null or not (p_price between 1 and 1000000) then
raise exception 'conversational_action_conflict' using errcode = 'P0001';
end if;
perform pg_catalog.pg_advisory_xact_lock(
pg_catalog.hashtextextended(
p_user_id::text || ':conversational-rectification-case',
0
)
);
perform pg_catalog.pg_advisory_xact_lock(
pg_catalog.hashtextextended(p_user_id::text || ':' || p_action_id::text, 0)
);
select c.* into v_case
from public.birth_time_rectification_cases c
where c.id = p_case_id
for update;
if found then
if v_case.user_id is distinct from p_user_id
or v_case.journey_protocol is distinct from 'conversational-evidence-v3' then
raise exception 'conversational_case_not_found' using errcode = 'P0001';
end if;
select r.* into v_receipt
from public.birth_time_rectification_action_receipts r
where r.case_id = p_case_id and r.action_id = p_action_id
for update;
if found
and v_receipt.user_id is not distinct from p_user_id
and v_receipt.action_kind is not distinct from 'import_legacy'
and v_receipt.expected_turn_version is not distinct from p_expected_version
and v_receipt.request_fingerprint is not distinct from v_fingerprint then
return v_receipt.response;
end if;
raise exception 'conversational_action_conflict' using errcode = 'P0001';
end if;
select legacy.* into v_legacy
from public.birth_time_rectification_cases legacy
where legacy.id = p_legacy_case_id and legacy.user_id = p_user_id
for update;
if not found
or v_legacy.journey_protocol not in ('legacy-guided-v1', 'dynamic-choice-v2')
or v_legacy.status in ('confirmed', 'completed', 'abandoned') then
raise exception 'conversational_case_not_found' using errcode = 'P0001';
end if;
if v_legacy.turn_version is distinct from p_expected_version then
raise exception 'conversational_stale_turn' using errcode = 'P0001';
end if;
perform 1
from public.birth_time_rectification_cases imported
where imported.user_id = p_user_id
and imported.imported_from_case_id = p_legacy_case_id
and imported.journey_protocol = 'conversational-evidence-v3'
for update;
if found then
raise exception 'conversational_action_conflict' using errcode = 'P0001';
end if;
perform 1
from public.birth_time_rectification_cases active_case
where active_case.user_id = p_user_id
and active_case.id <> p_case_id
and active_case.id <> p_legacy_case_id
and active_case.journey_protocol = 'conversational-evidence-v3'
and active_case.status in ('starting', 'active', 'paused', 'confirming')
for update;
if found then
raise exception 'conversational_action_conflict' using errcode = 'P0001';
end if;
select profile.* into v_profile
from public.profiles profile
where profile.id = p_user_id
for update;
if not found then
raise exception 'conversational_case_not_found' using errcode = 'P0001';
end if;
v_profile.credits :=
public.recover_conversational_rectification_orphan_reservations(
p_user_id,
null::uuid
);
-- Preserve an explicit nullable clue while omitting inapplicable time-mode
-- keys. This yields the same source-discriminated representation accepted
-- by new starts and keeps legacy import round-trippable across devices.
v_declared_birth_input := pg_catalog.jsonb_build_object(
'birthDate', pg_catalog.to_char(v_legacy.reported_date, 'YYYY-MM-DD'),
'source', v_legacy.source,
'birthTimeClue', v_profile.birth_time_clue,
'birthplace', pg_catalog.jsonb_strip_nulls(pg_catalog.jsonb_build_object(
'countryCode', v_profile.country_code,
'provinceCode', v_profile.province_code,
'cityCode', v_profile.city_code,
'districtCode', v_profile.district_code,
'latitude', v_profile.latitude,
'longitude', v_profile.longitude,
'timezoneOffset', v_profile.timezone_offset
))
);
if v_legacy.reported_time is not null then
v_declared_birth_input := v_declared_birth_input || pg_catalog.jsonb_build_object(
'reportedTime', pg_catalog.to_char(v_legacy.reported_time, 'HH24:MI')
);
end if;
if v_legacy.reported_period is not null then
v_declared_birth_input := v_declared_birth_input || pg_catalog.jsonb_build_object(
'reportedPeriod', v_legacy.reported_period
);
end if;
if v_legacy.uncertainty_before_minutes is not null
or v_legacy.uncertainty_after_minutes is not null then
v_declared_birth_input := v_declared_birth_input || pg_catalog.jsonb_build_object(
'uncertaintyBeforeMinutes', v_legacy.uncertainty_before_minutes,
'uncertaintyAfterMinutes', v_legacy.uncertainty_after_minutes
);
end if;
select b.* into v_billing
from public.birth_time_rectification_billing b
where b.user_id = p_user_id and b.state = 'reserved'
for update;
if found then
raise exception 'conversational_action_conflict' using errcode = 'P0001';
end if;
if public.conversational_rectification_valid_declared_birth_input(
v_declared_birth_input
) is not true
or public.conversational_rectification_valid_public_turn(p_first_turn) is not true
or public.conversational_rectification_valid_validation_receipt(
p_validation_receipt
) is not true
or public.conversational_rectification_valid_private_candidate(
p_private_candidate
) is not true
or p_first_turn ->> 'caseId' is distinct from p_case_id::text
or p_first_turn ->> 'journeyProtocol' is distinct from 'conversational-evidence-v3'
or (p_first_turn ->> 'turnVersion')::bigint is distinct from 0
or p_first_turn ->> 'status' is distinct from 'active'
or nullif(p_first_turn ->> 'pendingConsultationQuestion', '')
is distinct from nullif(p_pending_consultation_question, '')
or pg_catalog.jsonb_path_exists(p_first_turn, '$.**.candidateWeights')
or pg_catalog.jsonb_path_exists(p_first_turn, '$.**.candidateScores')
or pg_catalog.jsonb_path_exists(p_first_turn, '$.**.partitionId')
or pg_catalog.jsonb_path_exists(p_first_turn, '$.**.rawModelOutput')
or pg_catalog.jsonb_path_exists(p_first_turn, '$.**.systemPrompt') then
raise exception 'conversational_action_conflict' using errcode = 'P0001';
end if;
insert into public.birth_time_rectification_cases (
id, user_id, journey_protocol, status, reported_date, reported_time,
reported_period, source, uncertainty_before_minutes,
uncertainty_after_minutes, declared_birth_input,
questionnaire, answers, life_events,
candidate_scan, journey_snapshot, turn_version, turn_state,
candidate_result, candidate_result_id, candidate_start, candidate_end,
event_scoring_version, imported_from_case_id, baseline_active_time,
pending_consultation_question, updated_at
) values (
p_case_id, p_user_id, 'conversational-evidence-v3', 'active',
v_legacy.reported_date, v_legacy.reported_time, v_legacy.reported_period,
v_legacy.source, v_legacy.uncertainty_before_minutes,
v_legacy.uncertainty_after_minutes, v_declared_birth_input,
v_legacy.questionnaire,
v_legacy.answers, v_legacy.life_events, v_legacy.candidate_scan,
p_first_turn, 0, p_first_turn, p_private_candidate,
nullif(p_private_candidate ->> 'resultId', '')::uuid,
nullif(p_private_candidate ->> 'rangeStart', '')::time,
nullif(p_private_candidate ->> 'rangeEnd', '')::time,
nullif(p_private_candidate ->> 'calculationVersion', ''),
p_legacy_case_id, v_profile.active_birth_time,
nullif(p_pending_consultation_question, ''), pg_catalog.now()
);
insert into public.birth_time_rectification_turns (
case_id, turn_version, narrative, candidate, technical_receipt,
evidence_request, evidence_recap, actions, output_validation_receipt
) values (
p_case_id, 0, p_first_turn ->> 'narrative', p_first_turn -> 'candidate',
p_first_turn -> 'technicalReceipt',
nullif(p_first_turn -> 'evidenceRequest', 'null'::jsonb),
coalesce(p_first_turn -> 'evidenceRecap', '[]'::jsonb),
coalesce(p_first_turn -> 'actions', '[]'::jsonb),
p_validation_receipt
);
insert into public.birth_time_rectification_billing (
case_id, user_id, price, state, billing_receipt_id,
complete_action_id, balance_after
) values (
p_case_id, p_user_id, p_price, 'migration_waived', pg_catalog.gen_random_uuid(),
p_action_id, v_profile.credits
);
v_response := public.conversational_rectification_case_projection(p_user_id, p_case_id);
insert into public.birth_time_rectification_action_receipts (
case_id, action_id, user_id, action_kind, expected_turn_version,
result_turn_version, request_fingerprint, request, response
) values (
p_case_id, p_action_id, p_user_id, 'import_legacy', p_expected_version,
0, v_fingerprint,
public.conversational_rectification_action_request(
'import_legacy', p_user_id, p_case_id, p_expected_version,
p_action_id, v_fingerprint
),
v_response
);
return v_response;
end;
$$;
revoke all on function public.conversational_rectification_fingerprint(jsonb)
from public, anon, authenticated, service_role;
revoke all on function public.guard_imported_rectification_history()
from public, anon, authenticated, service_role;
revoke all on function public.conversational_rectification_case_projection(uuid, uuid)
from public, anon, authenticated, service_role;
revoke all on function public.conversational_rectification_case_fits_load_limits(
uuid, uuid, text, bigint, jsonb, jsonb, jsonb, jsonb
) from public, anon, authenticated, service_role;
revoke all on function public.load_conversational_rectification_case(uuid, uuid)
from public, anon, authenticated;
revoke all on function public.replay_conversational_rectification_action(
uuid, uuid, bigint, uuid, text, text
) from public, anon, authenticated;
revoke all on function public.create_conversational_rectification_case(
uuid, uuid, bigint, uuid, uuid, text, jsonb, jsonb, jsonb, jsonb
) from public, anon, authenticated;
revoke all on function public.save_conversational_rectification_turn(
uuid, uuid, bigint, uuid, jsonb, jsonb, jsonb, jsonb, text
) from public, anon, authenticated;
revoke all on function public.pause_conversational_rectification_case(
uuid, uuid, bigint, uuid, jsonb, jsonb, text
) from public, anon, authenticated;
revoke all on function public.abandon_conversational_rectification_case(
uuid, uuid, bigint, uuid, jsonb, jsonb, text
) from public, anon, authenticated;
revoke all on function public.confirm_conversational_rectification_candidate(
uuid, uuid, bigint, uuid, uuid, time without time zone, text, jsonb, jsonb, text
) from public, anon, authenticated;
revoke all on function public.import_legacy_conversational_rectification_case(
uuid, uuid, uuid, bigint, uuid, integer, text, jsonb, jsonb, jsonb
) from public, anon, authenticated;
grant execute on function public.load_conversational_rectification_case(uuid, uuid)
to service_role;
grant execute on function public.replay_conversational_rectification_action(
uuid, uuid, bigint, uuid, text, text
) to service_role;
grant execute on function public.create_conversational_rectification_case(
uuid, uuid, bigint, uuid, uuid, text, jsonb, jsonb, jsonb, jsonb
) to service_role;
grant execute on function public.save_conversational_rectification_turn(
uuid, uuid, bigint, uuid, jsonb, jsonb, jsonb, jsonb, text
) to service_role;
grant execute on function public.pause_conversational_rectification_case(
uuid, uuid, bigint, uuid, jsonb, jsonb, text
) to service_role;
grant execute on function public.abandon_conversational_rectification_case(
uuid, uuid, bigint, uuid, jsonb, jsonb, text
) to service_role;
grant execute on function public.confirm_conversational_rectification_candidate(
uuid, uuid, bigint, uuid, uuid, time without time zone, text, jsonb, jsonb, text
) to service_role;
grant execute on function public.import_legacy_conversational_rectification_case(
uuid, uuid, uuid, bigint, uuid, integer, text, jsonb, jsonb, jsonb
) to service_role;
commit;