fix(report): keep actionNotes within the document cap at assemble
Independent Staging Quality Gate / validate (push) Successful in 12m42s
Independent Staging Quality Gate / publish (push) Successful in 1m56s

Five-theme personal_full still failed final parse after the charts cap
fix because writer output exceeded actionNotes.max(24). Truncate at
assemble; do not raise the schema cap.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Jesse_Chen
2026-09-04 22:22:38 +08:00
co-authored by Cursor
parent d4627a5019
commit a75929c139
6 changed files with 43 additions and 8 deletions
+2 -1
View File
@@ -46,6 +46,7 @@ export const CONFLICT_STATUSES = ["unresolved", "partial", "resolved"] as const;
export const REPORT_ACTION_PRIORITIES = ["now", "next", "watch"] as const;
export const REPORT_DOCUMENT_V1_CHART_IDS = ["D1", "D9", "D10"] as const;
export const CHART_IDS = ["D1", "D2", "D6", "D8", "D9", "D10", "D11", "D24", "D30"] as const;
export const REPORT_DOCUMENT_V2_ACTION_NOTES_MAX = 24;
const claimStatusSchema = z.enum(CLAIM_STATUSES);
const themeIdSchema = z.string().regex(/^[a-z][a-z0-9_.-]{0,95}$/, "invalid theme id");
@@ -281,7 +282,7 @@ export const reportDocumentV2Schema = z.strictObject({
executiveSummary: executiveSummaryV2Schema,
natalFoundation: natalFoundationSchema,
currentPhase: currentPhaseSchema.nullable(),
actionNotes: z.array(actionNoteSchema).min(1).max(24),
actionNotes: z.array(actionNoteSchema).min(1).max(REPORT_DOCUMENT_V2_ACTION_NOTES_MAX),
charts: z.array(reportDocumentV2ChartSchema).min(1).max(CHART_IDS.length),
thematicNarrative: z.array(thematicSectionV2Schema).max(12),
blockedConflictDisclosure: z.array(blockedConflictDisclosureSchema).max(12),
@@ -5,6 +5,7 @@ import {
} from "./personal-report-contract.server-core.ts";
import {
CHART_IDS,
REPORT_DOCUMENT_V2_ACTION_NOTES_MAX,
REQUIRED_THEME_CHARTS,
type ClaimStatus,
type EvidenceAppendix,
@@ -2773,6 +2774,9 @@ export function assembleReportDocumentV2(
evidenceRefs: [...executiveRefs],
});
}
if (actionNotes.length > REPORT_DOCUMENT_V2_ACTION_NOTES_MAX) {
actionNotes.length = REPORT_DOCUMENT_V2_ACTION_NOTES_MAX;
}
const usedEvidenceRefs = new Set<string>([
...executiveRefs,
@@ -13,7 +13,7 @@ import {
type ReportEvidenceBundleV2,
type TechniqueExecutionReceipt,
} from "../src/lib/report-evidence-bundle-v2.ts";
import { CHART_IDS } from "../src/lib/personal-report-contract.ts";
import { CHART_IDS, REPORT_DOCUMENT_V2_ACTION_NOTES_MAX } from "../src/lib/personal-report-contract.ts";
import {
classifyFinalParseInnerReason,
classifyReportSchemaInnerReason,
@@ -392,6 +392,29 @@ test("five-theme sectioned pipeline with the full CHART_IDS set finishes READY",
assert.deepEqual(sectionCalls, ["career", "health", "marriage", "timing", "wealth"]);
});
test("five-theme assemble keeps actionNotes within the document cap", async () => {
const bundle = makeBundle({
themes: standardFiveThemes,
charts: CHART_IDS.map((id, offset) => chart(id, offset)),
});
const inner = sectionedAgent({});
const agent: ReportAgentPort = {
...inner,
async generateSection(sectionBundle, section, completedTitles, options) {
const output = await inner.generateSection!(sectionBundle, section, completedTitles, options);
return {
...output,
actions: Array.from({ length: 6 }, (_, index) => (
`围绕${output.title}记录可验证的现实反馈 ${index + 1}`
)),
};
},
};
const document = readyV2(await runSectioned(bundle, agent, inMemorySectionService()));
assert.equal(document.actionNotes.length, REPORT_DOCUMENT_V2_ACTION_NOTES_MAX);
assert.equal(document.blockedConflictDisclosure.length, 0);
});
test("generatePersonalReport passes the worker lease signal to the writer and settles when aborted", async () => {
const bundle = makeBundle({
themes: [{