fix(report): do not fail a written report when progress projection throws
Independent Staging Quality Gate / validate (push) Has been cancelled
Independent Staging Quality Gate / publish (push) Has been cancelled

Staging wrote four ready chapters then marked the job schema-invalid 34ms
later with no summary telemetry. Keep chapter persistence and assemble
going if onProgress or a non-lease heartbeat error fails.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Jesse_Chen
2026-09-03 09:54:32 +08:00
co-authored by Cursor
parent f127ada413
commit fbd6e48036
5 changed files with 101 additions and 18 deletions
@@ -426,8 +426,14 @@ test("production writer and worker keep the same signal on initial and repair mo
new URL("../src/lib/personal-report-worker-core.ts", import.meta.url),
"utf8",
);
assert.match(workerCore, /onProgress: async \(progress\) => \{\s*await refreshLease\(\)/);
assert.match(workerCore, /onProgress: async \(progress\) => \{\s*try \{\s*await refreshLease\(\)/);
assert.doesNotMatch(workerCore, /timerUnref\(heartbeatTimer\)/);
const generationSource = readFileSync(
new URL("../src/lib/personal-report-generation.ts", import.meta.url),
"utf8",
);
assert.match(generationSource, /const emitProgress = async \(phase: string, completed: number\)/);
assert.match(generationSource, /rethrowIfAborted\(error, deps\.signal\);/);
});
test("an all-blocked plan still produces a valid v2 document with honest disclosures and no invented thematic section", async () => {
@@ -828,6 +834,23 @@ test("sectioned generation makes one filtered call per write theme, then summary
assert.match(document.executiveSummary.summary, /事业与方向/);
});
test("sectioned generation still assembles when onProgress throws after a persisted section", async () => {
const bundle = makeBundle({
themes: fullThemes.slice(0, 3),
charts: [chart("D1"), chart("D2", 1), chart("D9", 2), chart("D10", 3), chart("D11", 4)],
});
const result = await runSectioned(
bundle,
sectionedAgent({}),
inMemorySectionService(),
async () => {
throw new Error("progress_projection_failed");
},
);
const document = readyV2(result);
assert.deepEqual(document.thematicNarrative.map((section) => section.theme), ["career", "marriage", "wealth"]);
});
test("sectioned resume skips ready sections after an interruption", async () => {
const bundle = makeBundle({ themes: fullThemes, charts: [chart("D1"), chart("D2", 1), chart("D9", 2), chart("D10", 3), chart("D11", 4), chart("D24", 5)] });
const service = inMemorySectionService();