import assert from "node:assert/strict"; import test from "node:test"; import { PUBLIC_RECTIFICATION_TOOLS } from "../src/lib/rectification-agentic/v9/public-receipt.ts"; import { createRectificationV9Tools } from "../src/mastra/rectification-v9-tools.ts"; import { CASE_ID, EVIDENCE_ID, FOCUS_ID, RESULT_ID, CANDIDATE_ID, TURN_ID, USER_ID, fakeAccounting, } from "./rectification-v9-test-support.ts"; type ToolSchema = { inputSchema: { safeParse(value: unknown): { success: boolean }; }; }; const EXACT_TOOL_KEYS = [ "rectification-read-case", "rectification-set-focus", "rectification-resolve-focus", "rectification-record-evidence-batch", "rectification-propose-evidence", "rectification-confirm-evidence", "rectification-revise-evidence", "rectification-compare-candidates", "rectification-read-diagnostics", "rectification-offer-candidates", "rectification-accept-candidate", "rectification-confirm-birth-time", "rectification-close-case", ] as const; function toolsUnderTest() { return createRectificationV9Tools({ userId: USER_ID, caseId: CASE_ID, turnId: TURN_ID, accounting: fakeAccounting({}).client as never, }); } const validInputs: Record<(typeof EXACT_TOOL_KEYS)[number], Record> = { "rectification-read-case": { caseId: CASE_ID }, "rectification-set-focus": { caseId: CASE_ID, questionId: "career-month-question", intent: "clarify_event_date", targetEvidenceId: EVIDENCE_ID, targetDomain: "career", targetKind: "career_entry", expectedAnswerSchema: { required: ["month"] }, }, "rectification-resolve-focus": { caseId: CASE_ID, focusId: FOCUS_ID, status: "resolved", evidenceId: EVIDENCE_ID, }, "rectification-record-evidence-batch": { caseId: CASE_ID, focusId: FOCUS_ID, items: [{ quote: "2016年9月去北京工作", proposedKind: "career_entry", subject: "self", domain: "career", datePrecision: "month", occurredFrom: "2016-09", summary: "2016年9月去北京工作", }], }, "rectification-propose-evidence": { caseId: CASE_ID, quote: "2016年9月去北京工作", proposedKind: "career_entry", subject: "self", domain: "career", datePrecision: "month", occurredFrom: "2016-09", summary: "2016年9月去北京工作", }, "rectification-confirm-evidence": { caseId: CASE_ID, focusId: FOCUS_ID, evidenceId: EVIDENCE_ID, }, "rectification-revise-evidence": { caseId: CASE_ID, focusId: FOCUS_ID, evidenceId: EVIDENCE_ID, quote: "不是9月,是10月", datePrecision: "month", occurredFrom: "2016-10", summary: "更正为2016年10月去北京工作", }, "rectification-compare-candidates": { caseId: CASE_ID }, "rectification-read-diagnostics": { caseId: CASE_ID }, "rectification-offer-candidates": { caseId: CASE_ID }, "rectification-accept-candidate": { caseId: CASE_ID, resultId: RESULT_ID, candidateId: CANDIDATE_ID, }, "rectification-confirm-birth-time": { caseId: CASE_ID, resultId: RESULT_ID, candidateId: CANDIDATE_ID, consentQuote: "就用05:02", }, "rectification-close-case": { caseId: CASE_ID, reason: "completed_by_user", }, }; test("V10 exposes exactly the 13 allowlisted rectification tool keys", () => { const tools = toolsUnderTest(); assert.deepEqual(Object.keys(tools), [...EXACT_TOOL_KEYS]); assert.deepEqual([...PUBLIC_RECTIFICATION_TOOLS], [...EXACT_TOOL_KEYS]); assert.equal(new Set(Object.keys(tools)).size, 13); }); test("all 13 tool input schemas accept their minimal contract and reject unknown fields", () => { const tools = toolsUnderTest() as unknown as Record; for (const toolName of EXACT_TOOL_KEYS) { const schema = tools[toolName]?.inputSchema; assert.ok(schema, `${toolName} must expose inputSchema`); assert.equal( schema.safeParse(validInputs[toolName]).success, true, `${toolName} must accept its documented input`, ); assert.equal( schema.safeParse({ ...validInputs[toolName], userId: USER_ID }).success, false, `${toolName} must be strict and reject server-owned userId`, ); assert.equal( schema.safeParse({ ...validInputs[toolName], candidate_range: { start_time: "04:50", end_time: "05:10" } }).success, false, `${toolName} must reject server-owned candidate ranges`, ); } }); test("focus/evidence mutation schemas require durable focus and evidence references", () => { const tools = toolsUnderTest() as unknown as Record; const confirm = tools["rectification-confirm-evidence"].inputSchema; const revise = tools["rectification-revise-evidence"].inputSchema; const resolve = tools["rectification-resolve-focus"].inputSchema; assert.equal(confirm.safeParse({ caseId: CASE_ID, evidenceId: EVIDENCE_ID }).success, true); assert.equal(confirm.safeParse({ caseId: CASE_ID, focusId: FOCUS_ID }).success, false); assert.equal(revise.safeParse({ ...validInputs["rectification-revise-evidence"], focusId: undefined, }).success, false); assert.equal(resolve.safeParse({ caseId: CASE_ID, status: "declined" }).success, false); }); test("batch item schemas are independently strict and bounded", () => { const tools = toolsUnderTest() as unknown as Record; const schema = tools["rectification-record-evidence-batch"].inputSchema; const valid = validInputs["rectification-record-evidence-batch"]; const item = (valid.items as Record[])[0]!; assert.equal(schema.safeParse({ ...valid, items: [] }).success, false); assert.equal(schema.safeParse({ ...valid, items: [{ ...item, evidenceId: EVIDENCE_ID }], }).success, false); assert.equal(schema.safeParse({ ...valid, items: Array.from({ length: 13 }, () => item), }).success, false); }); test("evidence kind and domain schemas enumerate legal values so education is not a proposedKind", () => { const tools = toolsUnderTest() as unknown as Record; const propose = tools["rectification-propose-evidence"].inputSchema; const batch = tools["rectification-record-evidence-batch"].inputSchema; const validPropose = validInputs["rectification-propose-evidence"]; const validBatch = validInputs["rectification-record-evidence-batch"]; const item = (validBatch.items as Record[])[0]!; assert.equal(propose.safeParse(validPropose).success, true); assert.equal(propose.safeParse({ ...validPropose, proposedKind: "education" }).success, false); assert.equal(propose.safeParse({ ...validPropose, proposedKind: "education_start" }).success, true); assert.equal(batch.safeParse({ ...validBatch, items: [{ ...item, proposedKind: "education" }], }).success, false); assert.equal(batch.safeParse({ ...validBatch, items: [{ ...item, proposedKind: "education_start", domain: "education" }], }).success, true); });