4ceb3a5157
* feat: enforce precise timing output contract * fix: recognize package imports in fragment audit * test: make workflow stream contract formatting-independent * fix: preserve VedAstro evidence across async workflows * feat: enforce commercial technique truth contract * feat: add rectification technique receipt * feat: extend rectification event evidence * feat: score rectification arudha evidence * feat: gate high rigor rectification confirmation * feat: add controlled transit to rectification * feat: include d11 in rectification finance scoring * feat: add ashtakavarga rectification auxiliary * feat: show rectification technique receipt * feat: use verified shadbala components in rectification * fix: trace transitive script references in fragment audit * feat: run request-level rectification parity packet
230 lines
8.3 KiB
TypeScript
230 lines
8.3 KiB
TypeScript
import assert from "node:assert/strict";
|
||
import test from "node:test";
|
||
import {
|
||
BirthTimeGuideOutputError,
|
||
fallbackQuestionCopy,
|
||
guideQuestionVariants,
|
||
parseEvidenceDraftOutput,
|
||
parseGuideQuestionOutput,
|
||
renderQuestionVariant,
|
||
} from "../src/lib/birth-time-guide-agent.ts";
|
||
import { evidenceDomains, type QuestionSpec } from "../src/lib/birth-time-question-planner.ts";
|
||
import { draftEvidenceStructureTool, getBirthTimeGuideAgent } from "../src/mastra/index.ts";
|
||
import type { ResolvedLanguageModel } from "../src/mastra/model.ts";
|
||
|
||
function question(domain: QuestionSpec["domain"]): QuestionSpec {
|
||
return {
|
||
questionId: `baseline_${domain}_1`,
|
||
phase: "baseline",
|
||
domain,
|
||
requestedPrecision: ["year", "month"],
|
||
allowUnknown: true,
|
||
purposeCode: `candidate_difference_${domain}`,
|
||
plannerVersion: "candidate-difference-v1",
|
||
};
|
||
}
|
||
|
||
test("draft parser fails closed when the model changes the server-selected domain", () => {
|
||
assert.throws(
|
||
() => parseEvidenceDraftOutput(
|
||
{ domain: "relationship", precision: "month", date: "2023-04" },
|
||
{ requiredDomain: "career", sourceMessage: "2023 年 4 月换了工作" },
|
||
),
|
||
BirthTimeGuideOutputError,
|
||
);
|
||
});
|
||
|
||
test("ambiguous dates remain incomplete instead of being invented", () => {
|
||
const draft = parseEvidenceDraftOutput(
|
||
{ domain: "career", precision: null, date: null },
|
||
{ requiredDomain: "career", sourceMessage: "大概是前几年" },
|
||
);
|
||
|
||
assert.deepEqual(draft, {
|
||
domain: "career",
|
||
precision: null,
|
||
date: null,
|
||
needsReview: true,
|
||
});
|
||
});
|
||
|
||
test("an ungrounded model date is discarded", () => {
|
||
const draft = parseEvidenceDraftOutput(
|
||
{ domain: "career", precision: "month", date: "2023-04" },
|
||
{ requiredDomain: "career", sourceMessage: "记不清具体时间" },
|
||
);
|
||
|
||
assert.equal(draft.precision, null);
|
||
assert.equal(draft.date, null);
|
||
assert.equal(draft.needsReview, true);
|
||
});
|
||
|
||
test("precision and date mismatch remains a review-only draft", () => {
|
||
const draft = parseEvidenceDraftOutput(
|
||
{ domain: "career", precision: "day", date: "2023-04" },
|
||
{ requiredDomain: "career", sourceMessage: "2023 年 4 月换了工作" },
|
||
);
|
||
|
||
assert.equal(draft.precision, "day");
|
||
assert.equal(draft.date, "2023-04");
|
||
assert.equal(draft.needsReview, true);
|
||
});
|
||
|
||
test("grounded dates can produce a complete review-only proposal", () => {
|
||
const draft = parseEvidenceDraftOutput(
|
||
{ domain: "career", precision: "month", date: "2023-04" },
|
||
{ requiredDomain: "career", sourceMessage: "我在 2023 年 4 月换了工作" },
|
||
);
|
||
|
||
assert.deepEqual(draft, {
|
||
domain: "career",
|
||
precision: "month",
|
||
date: "2023-04",
|
||
needsReview: false,
|
||
});
|
||
});
|
||
|
||
test("date grounding accepts ISO and year-only evidence but never adds an unstated day", () => {
|
||
const iso = parseEvidenceDraftOutput(
|
||
{ domain: "career", precision: "month", date: "2023-04" },
|
||
{ requiredDomain: "career", sourceMessage: "工作变动发生于 2023-04" },
|
||
);
|
||
const year = parseEvidenceDraftOutput(
|
||
{ domain: "career", precision: "year", date: "2023" },
|
||
{ requiredDomain: "career", sourceMessage: "只记得是 2023 年" },
|
||
);
|
||
const inventedDay = parseEvidenceDraftOutput(
|
||
{ domain: "career", precision: "day", date: "2023-04-18" },
|
||
{ requiredDomain: "career", sourceMessage: "只记得 2023 年 4 月" },
|
||
);
|
||
|
||
assert.equal(iso.needsReview, false);
|
||
assert.equal(year.needsReview, false);
|
||
assert.equal(inventedDay.date, null);
|
||
assert.equal(inventedDay.precision, null);
|
||
assert.equal(inventedDay.needsReview, true);
|
||
});
|
||
|
||
test("date grounding accepts fullwidth Chinese month and explicit Chinese or ISO days", () => {
|
||
const cases = [
|
||
{
|
||
output: { domain: "career", precision: "month", date: "2023-04" },
|
||
message: "工作变化发生在 2023 年 4 月",
|
||
},
|
||
{
|
||
output: { domain: "career", precision: "day", date: "2023-04-18" },
|
||
message: "工作变化发生在 2023 年 4 月 18 号",
|
||
},
|
||
{
|
||
output: { domain: "career", precision: "day", date: "2023-04-18" },
|
||
message: "工作变化发生在 2023-04-18",
|
||
},
|
||
] as const;
|
||
for (const value of cases) {
|
||
const draft = parseEvidenceDraftOutput(value.output, {
|
||
requiredDomain: "career",
|
||
sourceMessage: value.message,
|
||
});
|
||
assert.equal(draft.needsReview, false, value.message);
|
||
}
|
||
});
|
||
|
||
test("date grounding rejects substrings, partial ISO tokens, and scattered numbers", () => {
|
||
const unsafeMessages = [
|
||
"订单号20230",
|
||
"编号 2023-04-X",
|
||
"2023 年有变化,另外 4 月去了旅行",
|
||
"账号 1202304",
|
||
] as const;
|
||
|
||
for (const sourceMessage of unsafeMessages) {
|
||
const draft = parseEvidenceDraftOutput(
|
||
{ domain: "career", precision: "month", date: "2023-04" },
|
||
{ requiredDomain: "career", sourceMessage },
|
||
);
|
||
assert.equal(draft.precision, null, sourceMessage);
|
||
assert.equal(draft.date, null, sourceMessage);
|
||
assert.equal(draft.needsReview, true, sourceMessage);
|
||
}
|
||
});
|
||
|
||
test("year-only grounding requires a complete year token or explicit year semantics", () => {
|
||
for (const sourceMessage of ["2023", " 2023 ", "大约在 2023 年"] as const) {
|
||
const draft = parseEvidenceDraftOutput(
|
||
{ domain: "career", precision: "year", date: "2023" },
|
||
{ requiredDomain: "career", sourceMessage },
|
||
);
|
||
assert.equal(draft.needsReview, false, sourceMessage);
|
||
}
|
||
for (const sourceMessage of ["订单20230", "编号A2023B"] as const) {
|
||
const draft = parseEvidenceDraftOutput(
|
||
{ domain: "career", precision: "year", date: "2023" },
|
||
{ requiredDomain: "career", sourceMessage },
|
||
);
|
||
assert.equal(draft.date, null, sourceMessage);
|
||
}
|
||
});
|
||
|
||
test("server renders every approved variant from its QuestionSpec domain and precision", () => {
|
||
const semanticMarkers = {
|
||
education: /升学|转学|学习方向/,
|
||
relocation: /搬家|离乡|居住地/,
|
||
relationship: /关系进入|关系结束|关系.*转变/,
|
||
career: /工作|职业方向|身份变化/,
|
||
finance: /收入|资产|负债|资源渠道/,
|
||
health_pressure: /健康压力|生活压力/,
|
||
} as const;
|
||
for (const domain of evidenceDomains) {
|
||
for (const variant of guideQuestionVariants) {
|
||
const copy = renderQuestionVariant(question(domain), variant);
|
||
assert.equal((copy.match(/[??]/g) ?? []).length, 1);
|
||
assert.ok(copy.length <= 120);
|
||
assert.match(copy, semanticMarkers[domain]);
|
||
assert.doesNotMatch(copy, /哪一天|几号|具体日期|候选|支持|更符合|更接近|出生分钟/);
|
||
for (const otherDomain of evidenceDomains.filter((item) => item !== domain)) {
|
||
assert.doesNotMatch(copy, semanticMarkers[otherDomain]);
|
||
}
|
||
}
|
||
assert.equal(fallbackQuestionCopy(question(domain)), renderQuestionVariant(question(domain), "direct"));
|
||
}
|
||
});
|
||
|
||
test("question parser accepts only a finite variant and binds it to the server QuestionSpec", () => {
|
||
const parsed = parseGuideQuestionOutput({ variant: "gentle" }, question("career"));
|
||
assert.equal(parsed, renderQuestionVariant(question("career"), "gentle"));
|
||
for (const unsafe of [
|
||
{ variant: "relationship" },
|
||
{ variant: "direct", domain: "relationship" },
|
||
{ question: "Which relationship changed?" },
|
||
{ question: "具体是哪一天?" },
|
||
{ question: "04:00 这个候选时间更符合吗?" },
|
||
{ variant: "direct", question: "越权覆盖" },
|
||
]) {
|
||
assert.throws(() => parseGuideQuestionOutput(unsafe, question("career")), BirthTimeGuideOutputError);
|
||
}
|
||
});
|
||
|
||
test("model output schemas reject extra fields", () => {
|
||
assert.throws(() => parseEvidenceDraftOutput(
|
||
{ domain: "career", precision: null, date: null, confidence: "high" },
|
||
{ requiredDomain: "career", sourceMessage: "记不清" },
|
||
));
|
||
assert.throws(() => parseGuideQuestionOutput({ variant: "direct", score: 9 }, question("career")));
|
||
});
|
||
|
||
test("birth-time guide registers only the review-only structure tool", async () => {
|
||
const model: ResolvedLanguageModel = {
|
||
id: "guide-test-model",
|
||
label: "Guide test",
|
||
description: "",
|
||
creditCost: 1,
|
||
isDefault: false,
|
||
mode: "openai",
|
||
model: "openai/gpt-5-mini",
|
||
};
|
||
const tools = await getBirthTimeGuideAgent(model).listTools();
|
||
|
||
assert.deepEqual(Object.keys(tools), ["draftEvidenceStructureTool"]);
|
||
assert.equal(draftEvidenceStructureTool.id, "draft-evidence-structure");
|
||
});
|