fix(report): classify writer failures and size CJK section budgets
Independent Staging Quality Gate / validate (push) Successful in 9m41s
Independent Staging Quality Gate / publish (push) Successful in 1m49s

Staging personal_full died as report_schema_invalid after the first
chapter blocked. Keep assertWriterOutput, stop treating refs mismatch as
missing evidence, and surface section error codes on the report APIs.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Jesse_Chen
2026-09-03 01:17:46 +08:00
co-authored by Cursor
parent 838d9e7687
commit eda37c1523
16 changed files with 691 additions and 66 deletions
@@ -19,6 +19,7 @@ type ReportListItem = Readonly<{
themes: readonly string[];
status: "generating" | "ready" | "failed";
failureCode: string | null;
failureSummary?: string | null;
createdAt: string;
completedAt: string | null;
}>;
@@ -57,6 +58,7 @@ function readReports(value: unknown): ReportListItem[] {
: [],
status: row.status as ReportListItem["status"],
failureCode: typeof row.failureCode === "string" ? row.failureCode : null,
failureSummary: typeof row.failureSummary === "string" ? row.failureSummary : null,
createdAt: typeof row.createdAt === "string" ? row.createdAt : "",
completedAt: typeof row.completedAt === "string" ? row.completedAt : null,
}];
@@ -207,7 +209,7 @@ export function PersonalReportCenter() {
</Button>
) : (
<span className="report-center-failure">{report.failureCode ?? "生成失败"}</span>
<span className="report-center-failure">{report.failureSummary ?? report.failureCode ?? "生成失败"}</span>
)}
</article>
);
@@ -35,7 +35,7 @@ export type ReportLoadState =
| { phase: "not-found" }
| { phase: "generating"; progressPercent?: number; progressPhase?: string }
| { phase: "timed-out" }
| { phase: "failed"; failureCode: string | null }
| { phase: "failed"; failureCode: string | null; failureSummary?: string | null }
| { phase: "invalid"; message: string }
| { phase: "network-error" }
| { phase: "ready"; document: ReportDocument };
@@ -48,6 +48,7 @@ export interface ReportEnvelopeView {
presentationMode: string;
status: string;
failureCode: string | null;
failureSummary?: string | null;
createdAt: string;
completedAt: string | null;
progressPercent?: number;
@@ -104,7 +105,14 @@ export function classifyReportEnvelope(statusCode: number, json: unknown): Repor
const code = typeof view.failureCode === "string" && view.failureCode.length > 0
? view.failureCode
: null;
return { phase: "failed", failureCode: code };
const summary = typeof view.failureSummary === "string" && view.failureSummary.length > 0
? view.failureSummary
: null;
return {
phase: "failed",
failureCode: code,
...(summary ? { failureSummary: summary } : {}),
};
}
default:
return { phase: "invalid", message: "报告状态无法识别。" };
@@ -293,12 +301,12 @@ export function PersonalReportPage({ reportId }: { reportId: string }) {
<main className="flex min-h-svh flex-col items-center justify-center gap-4 px-4 text-center">
<TriangleAlert aria-hidden="true" className="size-8 text-warning" />
<h1 className="text-xl font-semibold text-ink"></h1>
<p className="max-w-md text-sm text-ink-secondary">
{state.failureSummary ?? "生成过程中出现问题,未产出可用报告。请稍后重试。"}
</p>
{state.failureCode && (
<p className="font-mono text-xs text-ink-tertiary">{state.failureCode}</p>
)}
<p className="max-w-md text-sm text-ink-secondary">
</p>
<Button render={<Link href="/reports" />} nativeButton={false} variant="outline">
</Button>