Files
Jyotisha/frontend/supabase/migrations/20260907010000_personal_report_card_summary.sql
T
Jesse_ChenandCursor b466a6fc8c fix(report): stop listing reports via PostgREST JSON paths (BUG-574)
Staging PostgREST rejects the executiveSummary JSON-path alias, so GET /api/reports 500s. Persist a plain card_summary column from the Markdown excerpt instead.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-09-07 11:08:00 +08:00

167 lines
6.4 KiB
PL/PgSQL

-- Dedicated list-card excerpt. PostgREST JSON-path aliases on report_document
-- are rejected by the self-hosted PostgREST used in staging; this plain text
-- column is the only list-select form. Worker completion copies the Markdown
-- 摘要 excerpt already stored on executiveSummary.summary. Old rows stay null.
begin;
do $migration$
begin
if current_user <> 'schema_owner' then
raise exception 'personal_report_card_summary_requires_schema_owner'
using errcode = '42501';
end if;
end
$migration$;
alter table public.personal_reports
add column if not exists card_summary text;
alter table public.personal_reports
drop constraint if exists personal_reports_card_summary_length_check;
alter table public.personal_reports
add constraint personal_reports_card_summary_length_check
check (card_summary is null or char_length(card_summary) <= 500);
comment on column public.personal_reports.card_summary is
'Optional list-card excerpt, at most 500 characters. Owner SELECT only; writes are service_role via complete_personal_report_job. Not backfilled.';
-- Same signature as 20260814040000. Adds card_summary from the cover excerpt.
create or replace function public.complete_personal_report_job(
p_job_id uuid,
p_lease_token uuid,
p_user_id uuid,
p_report_id uuid,
p_request_id uuid,
p_request_fingerprint text,
p_schema_version text,
p_report_document jsonb,
p_calculation_hash text,
p_evidence_hash text,
p_skill_name text,
p_skill_version text,
p_skill_source_commit text,
p_skill_snapshot_sha256 text
)
returns setof public.personal_report_jobs
language plpgsql
set search_path = pg_catalog, public
as $$
declare
v_job public.personal_report_jobs%rowtype;
v_report public.personal_reports%rowtype;
v_completed_at timestamptz;
begin
if p_schema_version <> 'report_document.v2'
or p_request_fingerprint !~ '^[0-9a-f]{64}$'
or p_calculation_hash !~ '^[0-9a-f]{64}$'
or p_evidence_hash !~ '^[0-9a-f]{64}$'
or p_skill_name is null
or p_skill_name !~ '^[a-z0-9]([a-z0-9._-]*[a-z0-9])?$'
or p_skill_version is null
or length(p_skill_version) not between 5 and 80
or p_skill_version !~ '^(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)(-[0-9A-Za-z-]+(\.[0-9A-Za-z-]+)*)?(\+[0-9A-Za-z-]+(\.[0-9A-Za-z-]+)*)?$'
or p_skill_snapshot_sha256 !~ '^[0-9a-f]{64}$'
or (p_skill_source_commit is not null and p_skill_source_commit !~ '^[0-9a-f]{40}$') then
raise exception using errcode = '22023', message = 'personal_report_completion_payload_invalid';
end if;
select job.*
into v_job
from public.personal_report_jobs as job
where job.id = p_job_id
for update;
if not found then
raise exception using errcode = 'P0002', message = 'personal_report_job_not_found';
end if;
v_completed_at := clock_timestamp();
if v_job.user_id is distinct from p_user_id
or v_job.request_id is distinct from p_request_id
or v_job.request_fingerprint is distinct from p_request_fingerprint then
raise exception using errcode = '22023', message = 'personal_report_completion_identity_mismatch';
end if;
if v_job.status <> 'running'
or v_job.lease_token is distinct from p_lease_token
or v_job.lease_expires_at <= v_completed_at then
raise exception using errcode = '55000', message = 'personal_report_job_lease_lost';
end if;
select report.*
into v_report
from public.personal_reports as report
where report.id = p_report_id
and report.user_id = p_user_id
and report.request_id = p_request_id
for update;
if not found then
raise exception using errcode = 'P0002', message = 'personal_report_not_found';
end if;
if v_report.status <> 'generating' then
raise exception using errcode = '55000', message = 'personal_report_completion_invalid_state';
end if;
if v_report.request_fingerprint is distinct from p_request_fingerprint
or v_report.skill_name is distinct from p_skill_name
or v_report.skill_version is distinct from p_skill_version
or v_report.skill_source_commit is distinct from p_skill_source_commit
or v_report.skill_snapshot_sha256 is distinct from p_skill_snapshot_sha256 then
raise exception using errcode = '22023', message = 'personal_report_completion_identity_mismatch';
end if;
if p_report_document is null
or p_report_document ->> 'schemaVersion' <> p_schema_version
or p_report_document ->> 'reportId' <> p_report_id::text
or p_report_document #>> '{provenance,calculationHash}' <> p_calculation_hash
or p_report_document #>> '{provenance,evidenceHash}' <> p_evidence_hash
or p_report_document #>> '{provenance,skillName}' <> p_skill_name
or p_report_document #>> '{provenance,skillVersion}' <> p_skill_version
or p_report_document #>> '{provenance,skillSnapshotSha256}' <> p_skill_snapshot_sha256
or (p_report_document #>> '{provenance,skillSourceCommit}') is distinct from p_skill_source_commit then
raise exception using errcode = '22023', message = 'personal_report_completion_document_mismatch';
end if;
if v_report.calculation_hash is not null
and v_report.calculation_hash is distinct from p_calculation_hash then
raise exception using errcode = '22023', message = 'personal_report_completion_hash_mismatch';
end if;
if v_report.evidence_hash is not null
and v_report.evidence_hash is distinct from p_evidence_hash then
raise exception using errcode = '22023', message = 'personal_report_completion_hash_mismatch';
end if;
update public.personal_reports as report
set status = 'ready',
schema_version = p_schema_version,
report_document = p_report_document,
calculation_hash = p_calculation_hash,
evidence_hash = p_evidence_hash,
failure_code = null,
completed_at = v_completed_at,
updated_at = v_completed_at,
card_summary = nullif(
left(btrim(coalesce(p_report_document #>> '{executiveSummary,summary}', '')), 500),
''
)
where report.id = p_report_id;
return query
update public.personal_report_jobs as job
set status = 'ready',
lease_token = null,
lease_owner = null,
lease_acquired_at = null,
lease_expires_at = null,
heartbeat_at = v_completed_at,
next_attempt_at = null,
progress_phase = 'ready',
progress_percent = 100,
last_error_code = null,
last_error_at = null,
finished_at = v_completed_at
where job.id = p_job_id
returning job.*;
end;
$$;
commit;