Unify reader cleanup rules, lock writer table guards, and register the exact fictional timestamp collision. Preserve existing ordinary-report safety contracts and source-data gaps. Validation: report Node 165/165, final safety 29/29, Python 101/101, Chrome 28/28; both PDFs retain all 130 rows. Full Node 3704 tests with the same 91 baseline failures. Privacy test: 62 passed, 1 failed due to 17 protected-file READ_ERRORs; not a green gate. Build, DB, manual checklist and controlled-login gaps remain documented. User explicitly authorized staging push with these gaps disclosed. Co-Authored-By: Claude Code <noreply@anthropic.com>
58 lines
3.2 KiB
TypeScript
58 lines
3.2 KiB
TypeScript
import assert from "node:assert/strict";
|
|
import test from "node:test";
|
|
import { Agent } from "@mastra/core/agent";
|
|
import { validatePersonalReportAgentOutputAgainstPlan } from "../src/lib/personal-report-generation.ts";
|
|
import type { PersonalReportSectionPlan } from "../src/lib/personal-report-plan.ts";
|
|
import type { ReportEvidenceBundleV2 } from "../src/lib/report-evidence-bundle-v2.ts";
|
|
import { createPersonalReportAgent, type PersonalReportAgentOutput } from "../src/mastra/personal-report.ts";
|
|
|
|
// Writer-boundary unit inputs contain no astrological facts or fabricated engine payload.
|
|
const plan: PersonalReportSectionPlan = { schemaVersion: "personal_report_section_plan.v1", depth: "standard", sections: [] };
|
|
const bundle = { claimCards: [] } as unknown as ReportEvidenceBundleV2;
|
|
function prose(): PersonalReportAgentOutput {
|
|
return {
|
|
executiveSummary: { headline: "先把已有安排理清", summary: "把要做的事写下来,再安排下一步。", priorities: ["核对现有安排"] },
|
|
thematicNarrative: [],
|
|
};
|
|
}
|
|
|
|
const forbiddenCases = {
|
|
"Markdown table": () => ({ ...prose(), executiveSummary: { ...prose().executiveSummary, summary: "| 项目 | 说明 |\n| --- | --- |\n| 安排 | 待核对 |" } }),
|
|
"HTML table in nested array": () => ({ ...prose(), executiveSummary: { ...prose().executiveSummary, priorities: ['<TABLE class="facts"><tr><td>待核对</td></tr></TABLE>'] } }),
|
|
"factTables top-level key": () => ({ ...prose(), factTables: [] }),
|
|
"factTables nested key": () => ({ ...prose(), executiveSummary: { ...prose().executiveSummary, factTables: [] } }),
|
|
};
|
|
for (const [name, output] of Object.entries(forbiddenCases)) {
|
|
test(`writer rejects ${name} with the exact fact-table error code`, () => {
|
|
assert.throws(() => validatePersonalReportAgentOutputAgainstPlan(output(), plan, bundle), {
|
|
name: "Error", message: "report_writer_fact_tables_forbidden",
|
|
});
|
|
});
|
|
}
|
|
|
|
test("writer accepts normal prose unchanged, including a non-table inline pipe", () => {
|
|
const output = { ...prose(), executiveSummary: {
|
|
...prose().executiveSummary,
|
|
summary: prose().executiveSummary.summary + "安排 A | 安排 B 只是两种写法,不是表格。",
|
|
} };
|
|
assert.strictEqual(validatePersonalReportAgentOutputAgainstPlan(output, plan, bundle), output);
|
|
});
|
|
|
|
test("actual writer agent instructions prohibit tables and assign them to the system", async (t) => {
|
|
let calls = 0;
|
|
t.mock.method(Agent.prototype, "generate", async function (this: Agent) {
|
|
calls++;
|
|
const instructions = await this.getInstructions();
|
|
assert.match(String(instructions), /不要输出表格,表格由系统提供/);
|
|
assert.match(String(instructions), /Markdown 表格、HTML table 或 factTables 字段/);
|
|
return { object: prose(), usage: { inputTokens: 1, outputTokens: 1 }, finishReason: "stop" };
|
|
});
|
|
const writer = createPersonalReportAgent({
|
|
id: "writer-table-contract", label: "Writer test", description: "Writer test", creditCost: 0,
|
|
isDefault: false, mode: "openai",
|
|
model: { specificationVersion: "v2", provider: "writer-test", modelId: "writer-test" } as never,
|
|
});
|
|
assert.deepEqual(await writer.generate(bundle, plan), prose());
|
|
assert.equal(calls, 1);
|
|
});
|