fix(report): load candidate ranges through a read-only RPC

Accepted profiles were selecting a revoked rectification table and failing the report before the engine ran. Degrade to a null window when that optional read fails.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Jesse_Chen
2026-09-04 19:17:14 +08:00
co-authored by Cursor
parent 73a04e9970
commit 7baf230066
13 changed files with 602 additions and 84 deletions
+5 -30
View File
@@ -24,6 +24,7 @@ import {
type PersonalReportService,
} from "@/lib/personal-report-service";
import { createSupabasePersonalReportJobService } from "@/lib/personal-report-job-service";
import { loadReportCandidateRange } from "@/lib/report-candidate-range";
import { createAdminSupabaseClient } from "@/lib/supabase/admin";
import { authorizeUsage, completeUsage, releaseUsage } from "@/lib/consultation-billing";
import { FeaturePricingError, resolveFeaturePricing } from "@/lib/feature-pricing";
@@ -187,36 +188,10 @@ export async function POST(request: Request) {
const caseId = typeof profileRow.rectification_case_id === "string"
? profileRow.rectification_case_id
: null;
if (caseId) {
const { data, error } = await admin
.from("birth_time_rectification_cases")
.select("candidate_start,candidate_end")
.eq("id", caseId)
.eq("user_id", userId as string)
.in("status", ["confirmed", "completed"])
.maybeSingle();
if (error) throw error;
const row = data && typeof data === "object" ? data as Record<string, unknown> : null;
if (row && typeof row.candidate_start === "string" && typeof row.candidate_end === "string") {
return { startTime: row.candidate_start, endTime: row.candidate_end };
}
}
const { data, error } = await admin
.from("agentic_rectification_cases")
.select("candidate_range,updated_at")
.eq("user_id", userId as string)
.eq("status", "candidate_accepted")
.order("updated_at", { ascending: false })
.limit(1)
.maybeSingle();
if (error) throw error;
const row = data && typeof data === "object" ? data as Record<string, unknown> : null;
const range = row?.candidate_range && typeof row.candidate_range === "object"
? row.candidate_range as Record<string, unknown>
: null;
return range && typeof range.start_time === "string" && typeof range.end_time === "string"
? { startTime: range.start_time, endTime: range.end_time }
: null;
return loadReportCandidateRange(admin, {
userId: userId as string,
rectificationCaseId: caseId,
});
},
checkSessionOwned: async (sessionId) => {
const { data, error } = await supabase
+6 -33
View File
@@ -21,6 +21,7 @@ import {
resolveReportBirthClock,
resolveReportBirthTimeSensitivityInput,
} from "@/lib/personal-report-route-core";
import { loadReportCandidateRange } from "@/lib/report-candidate-range";
import { createAdminSupabaseClient } from "@/lib/supabase/admin";
import { completeUsage, releaseUsage } from "@/lib/consultation-billing";
import type { ConsultationInput } from "@/mastra/consultation-workflow";
@@ -256,39 +257,11 @@ function createProductionWorker(workerId: string) {
if (resolveReportBirthClock(profile)?.status === "confirmed") {
return generateProductionReport(context);
}
const caseId = text(profile.rectification_case_id);
if (caseId) {
const { data, error } = await admin
.from("birth_time_rectification_cases")
.select("candidate_start,candidate_end")
.eq("id", caseId)
.eq("user_id", context.report.userId)
.in("status", ["confirmed", "completed"])
.maybeSingle();
if (error) throw new PersonalReportWorkerError("calculation_unavailable", true);
const row = record(data);
if (typeof row?.candidate_start === "string" && typeof row.candidate_end === "string") {
return generateProductionReport(context, {
startTime: row.candidate_start,
endTime: row.candidate_end,
});
}
}
const { data, error } = await admin
.from("agentic_rectification_cases")
.select("candidate_range,updated_at")
.eq("user_id", context.report.userId)
.eq("status", "candidate_accepted")
.order("updated_at", { ascending: false })
.limit(1)
.maybeSingle();
if (error) throw new PersonalReportWorkerError("calculation_unavailable", true);
const range = record(record(data)?.candidate_range);
return generateProductionReport(context, range
&& typeof range.start_time === "string"
&& typeof range.end_time === "string"
? { startTime: range.start_time, endTime: range.end_time }
: null);
const range = await loadReportCandidateRange(admin, {
userId: context.report.userId,
rectificationCaseId: text(profile.rectification_case_id),
});
return generateProductionReport(context, range);
},
});
}
@@ -0,0 +1,76 @@
export const READ_REPORT_CANDIDATE_RANGE_RPC = "read_report_candidate_range";
export type ReportCandidateClockRange = Readonly<{
startTime: string;
endTime: string;
}>;
export type ReportCandidateRangeRpcClient = Readonly<{
rpc: (
fn: string,
args: Record<string, unknown>,
) => PromiseLike<{ data: unknown; error: unknown }>;
}>;
export type ReportCandidateRangeWarn = (payload: Readonly<{
event: "report_candidate_range_unavailable";
reason: string;
}>) => void;
const candidateClockPattern = /^((?:[01]\d|2[0-3]):[0-5]\d)(?::00(?:\.0+)?)?$/;
export function reportCandidateRangeErrorCode(error: unknown): string {
if (error && typeof error === "object" && "code" in error) {
const code = error.code;
if (typeof code === "string" && code.trim().length > 0) return code.trim();
}
if (error instanceof Error && error.name.trim().length > 0) return error.name;
return "unknown";
}
export function parseReportCandidateRange(value: unknown): ReportCandidateClockRange | null {
const row = value !== null && typeof value === "object" && !Array.isArray(value)
? value as Record<string, unknown>
: null;
if (!row) return null;
const startTime = matchClock(row.start_time ?? row.startTime);
const endTime = matchClock(row.end_time ?? row.endTime);
if (!startTime || !endTime || startTime > endTime) return null;
return { startTime, endTime };
}
export async function loadReportCandidateRange(
client: ReportCandidateRangeRpcClient,
input: Readonly<{ userId: string; rectificationCaseId?: string | null }>,
warn: ReportCandidateRangeWarn = defaultWarn,
): Promise<ReportCandidateClockRange | null> {
try {
const result = await client.rpc(READ_REPORT_CANDIDATE_RANGE_RPC, {
p_user_id: input.userId,
p_rectification_case_id: input.rectificationCaseId ?? null,
});
if (result.error) {
warn({
event: "report_candidate_range_unavailable",
reason: reportCandidateRangeErrorCode(result.error),
});
return null;
}
return parseReportCandidateRange(result.data);
} catch (error) {
warn({
event: "report_candidate_range_unavailable",
reason: reportCandidateRangeErrorCode(error),
});
return null;
}
}
function matchClock(value: unknown): string | null {
if (typeof value !== "string") return null;
return value.trim().match(candidateClockPattern)?.[1] ?? null;
}
function defaultWarn(payload: Readonly<{ event: string; reason: string }>): void {
console.warn(JSON.stringify(payload));
}