Validate on Linux Node 22 and PostgreSQL 17: 3894 frontend tests and 64 database tests pass, with no removed test names or new failures. Preserve static Home, bounded gzip, assertion-change records and manual acceptance gaps. Co-Authored-By: Claude Code <noreply@anthropic.com>
65 lines
2.9 KiB
PL/PgSQL
65 lines
2.9 KiB
PL/PgSQL
begin;
|
|
|
|
-- A subject deletion cascades through reports/jobs before a worker can observe
|
|
-- the missing subject. Release the reservation in that same transaction.
|
|
create or replace function public.release_deleted_report_reservation()
|
|
returns trigger
|
|
language plpgsql
|
|
security definer
|
|
set search_path = ''
|
|
as $$
|
|
declare
|
|
released record;
|
|
begin
|
|
-- A ready document has already been delivered, even if the worker is between
|
|
-- its durable ready write and billing completion. Do not refund that window.
|
|
if old.status = 'ready' then return old; end if;
|
|
-- Account removal may already have cascaded away the wallet itself.
|
|
if not exists (select 1 from public.profiles where id = old.user_id) then return old; end if;
|
|
select * into released from public.release_usage(old.user_id, old.request_id::text, 'report_deleted');
|
|
-- Completed usage remains charged. Any other failure must roll back deletion
|
|
-- rather than orphan a paid reservation with no durable job left to refund it.
|
|
if not released.success and released.error_code is distinct from 'request_completed' then
|
|
raise exception 'report_reservation_release_failed';
|
|
end if;
|
|
return old;
|
|
end;
|
|
$$;
|
|
|
|
revoke all on function public.release_deleted_report_reservation() from public, anon, authenticated;
|
|
create trigger personal_reports_release_before_delete
|
|
before delete on public.personal_reports
|
|
for each row execute function public.release_deleted_report_reservation();
|
|
|
|
-- Match the worker's job -> report lock order before the cascading delete.
|
|
-- Definer is needed only for job locks; the caller identity/ownership check is
|
|
-- explicit and no arbitrary refund operation is exposed to authenticated.
|
|
create or replace function public.delete_chart_subject(p_id uuid)
|
|
returns void
|
|
language plpgsql
|
|
security definer
|
|
set search_path = ''
|
|
as $$
|
|
declare
|
|
owner uuid := (select auth.uid());
|
|
begin
|
|
if owner is null then raise exception 'chart_subject_unauthenticated'; end if;
|
|
perform 1 from public.chart_profiles
|
|
where id = p_id and user_id = owner and role = 'other' for update;
|
|
if not found then raise exception 'chart_subject_not_found'; end if;
|
|
perform 1 from public.personal_report_jobs j
|
|
join public.personal_reports r on r.user_id = j.user_id and r.request_id = j.request_id
|
|
where r.user_id = owner and r.chart_profile_id = p_id
|
|
order by j.id for update of j;
|
|
perform 1 from public.personal_reports
|
|
where user_id = owner and chart_profile_id = p_id order by id for update;
|
|
delete from public.chat_sessions where user_id = owner and chart_profile_id = p_id::text;
|
|
delete from public.personal_reports where user_id = owner and chart_profile_id = p_id;
|
|
delete from public.chart_profiles where id = p_id and user_id = owner and role = 'other';
|
|
end;
|
|
$$;
|
|
revoke all on function public.delete_chart_subject(uuid) from public, anon;
|
|
grant execute on function public.delete_chart_subject(uuid) to authenticated;
|
|
|
|
commit;
|