f708edf365
SQL kinds now match the TypeScript ledger so batch ingest can confirm dated events. Confirm no longer burns the opening focus, recap uses server date labels, and the Agent sees indistinguishable width instead of a fake unique minute. Co-authored-by: Cursor <cursoragent@cursor.com>
183 lines
5.8 KiB
TypeScript
183 lines
5.8 KiB
TypeScript
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<string, unknown>> = {
|
|
"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<string, ToolSchema>;
|
|
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<string, ToolSchema>;
|
|
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<string, ToolSchema>;
|
|
const schema = tools["rectification-record-evidence-batch"].inputSchema;
|
|
const valid = validInputs["rectification-record-evidence-batch"];
|
|
const item = (valid.items as Record<string, unknown>[])[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);
|
|
});
|