1527 lines
62 KiB
PL/PgSQL
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;
|