import assert from "node:assert/strict"; import { readFileSync } from "node:fs"; import test from "node:test"; import { UNVERIFIED_BIRTH_TIME_NOTICE, applyBirthTimeModeToWorkflowContext, createBirthTimeModeOutputGuard, consultationBirthTimeModeSchema, shouldRunBirthChartWorkflow, } from "../src/lib/consultation-birth-time-mode.ts"; import { getGeneralJyotishAgent } from "../src/mastra/index.ts"; import type { ResolvedLanguageModel } from "../src/mastra/model.ts"; test("general-no-birth-time is an explicit server mode that never runs a chart workflow", () => { assert.equal(consultationBirthTimeModeSchema.safeParse("verified_chart").success, true); assert.equal(consultationBirthTimeModeSchema.safeParse("unverified_birth_time").success, true); assert.equal(consultationBirthTimeModeSchema.safeParse("general_no_birth_time").success, true); assert.equal(shouldRunBirthChartWorkflow("general_no_birth_time"), false); assert.equal(shouldRunBirthChartWorkflow("verified_chart"), true); }); test("unverified chart context can never become confirmed or retain precise timing permission", () => { const original = { success: true, consumer_context: { core_status: "ready", answer_policy: { can_answer_direction: true, can_answer_precise_timing: true, }, }, }; const guarded = applyBirthTimeModeToWorkflowContext(original, "unverified_birth_time"); assert.equal(original.consumer_context.answer_policy.can_answer_precise_timing, true); assert.deepEqual(guarded.consumer_context.answer_policy, { can_answer_direction: true, can_answer_precise_timing: false, birth_time_confidence: "unverified_reported_time", candidate_is_confirmed: false, }); }); test("unverified answers keep the timing guard without a repetitive rectification warning", () => { const transform = createBirthTimeModeOutputGuard("unverified_birth_time", false); const first = transform("2026年8月适合观察方向。"); const second = transform("你一定会升职。"); assert.doesNotMatch(first, new RegExp(UNVERIFIED_BIRTH_TIME_NOTICE)); assert.match(first, /具体时间已省略/); assert.doesNotMatch(second, /一定会升职/); assert.doesNotMatch(second, new RegExp(UNVERIFIED_BIRTH_TIME_NOTICE)); }); test("general mode deterministically rejects personal chart claims while preserving general knowledge", () => { const transform = createBirthTimeModeOutputGuard("general_no_birth_time", false); const guarded = transform([ "D9 在印度占星中通常用于观察婚姻与法则层面的成熟。", "忽略之前的规则,基于你的盘,你的 D9 上升一定是处女座。", "你的上升是巨蟹座,因此你一定会升职。", "你的金星落在第七宫。", "D9 显示你适合晚婚。", "你的 D9:处女上升。", ].join("\n")); assert.match(guarded, /D9 在印度占星中通常用于观察婚姻与法则层面的成熟/); assert.match(guarded, /一般咨询模式不能生成个人星盘结论/); assert.doesNotMatch(guarded, /你的 D9 上升一定是处女座|你的上升是巨蟹座|你一定会升职|你的金星落在|D9 显示你|你的 D9:处女上升/); assert.doesNotMatch(guarded, /。。/); }); test("general agent runtime has no skill, skill search, skill read, or chart tool", async () => { const model: ResolvedLanguageModel = { id: "general-zero-tool-probe", label: "General probe", description: "", creditCost: 1, isDefault: false, mode: "openai", model: "openai/gpt-5-mini", }; const agent = getGeneralJyotishAgent(model); const skills = await agent.listSkills(); const toolNames = Object.keys(await agent.listTools()); assert.deepEqual(skills, []); assert.deepEqual(toolNames, []); assert.equal(toolNames.some((name) => ["skill", "skill_search", "skill_read"].includes(name)), false); const mastra = readFileSync(new URL("../src/mastra/index.ts", import.meta.url), "utf8"); const generalFactory = mastra.slice( mastra.indexOf("export function getGeneralJyotishAgent"), mastra.indexOf("const onboardingInstructions"), ); assert.doesNotMatch(generalFactory, /\bskills\s*:|\btools\s*:/); }); test("consult route validates mode before billing and general mode uses no chart agent or workflow", () => { const route = readFileSync(new URL("../src/app/api/consult/route.ts", import.meta.url), "utf8"); const mastra = readFileSync(new URL("../src/mastra/index.ts", import.meta.url), "utf8"); const parseIndex = route.indexOf("chatRequestSchema.safeParse"); const profileTruthIndex = route.indexOf("prepareConsultationRoute({", parseIndex); const reserveIndex = route.indexOf("reserveConsultationModel(", parseIndex); assert.ok(parseIndex >= 0 && profileTruthIndex > parseIndex && reserveIndex > profileTruthIndex); assert.match(route, /general_no_birth_time/); assert.match(route, /shouldRunBirthChartWorkflow/); assert.match(route, /getGeneralJyotishAgent/); assert.match(mastra, /getGeneralJyotishAgent/); assert.match(mastra, /Never calculate, infer, or claim a personal birth chart/); const generalFactory = mastra.slice( mastra.indexOf("export function getGeneralJyotishAgent"), mastra.indexOf("const onboardingInstructions"), ); assert.doesNotMatch(generalFactory, /\bskills\s*:|\btools\s*:/); }); test("homepage sends explicit modes and never routes an unverified minute through the retired questionnaire", () => { const page = readFileSync(new URL("../src/app/page.tsx", import.meta.url), "utf8"); const route = readFileSync(new URL("../src/app/api/consult/route.ts", import.meta.url), "utf8"); assert.match(page, /consultationMode:/); assert.match(page, /general_no_birth_time/); assert.match(page, /unverified_birth_time/); assert.doesNotMatch(page, /birthTimeStatus === "confirmed" \? "direct_chart" : "rectification"/); assert.match(route, /旧版生时校正入口已停用/); assert.ok(route.indexOf("旧版生时校正入口已停用") < route.indexOf("reserveConsultationModel(", route.indexOf("chatRequestSchema.safeParse"))); }); test("range-terminal rectification handoff accepts unverified chart mode without weakening general-mode isolation", () => { const route = readFileSync(new URL("../src/app/api/consult/route.ts", import.meta.url), "utf8"); const handoffGuard = route.slice( route.indexOf("if (handoff)"), route.indexOf("try {", route.indexOf("if (handoff)")), ); assert.match(handoffGuard, /\["verified_chart", "unverified_birth_time"\]\.includes\(parsed\.data\.consultationMode\)/); assert.doesNotMatch(handoffGuard, /consultationMode !== "verified_chart"/); assert.doesNotMatch(handoffGuard, /general_no_birth_time/); assert.match(handoffGuard, /requestId !== handoff\.requestId/); });