Merge codex/pr2-final-response into staging integration

This commit is contained in:
Jesse_Chen
2026-08-15 12:27:00 +08:00
26 changed files with 1643 additions and 73 deletions
@@ -1,9 +1,15 @@
import assert from "node:assert/strict";
import test from "node:test";
import { createConsultationTools, createConsultationRuntimeState } from "../src/mastra/consultation-tools.ts";
import {
appendConsultationRuntimeStep,
consultationStepBudgetReceipt,
createConsultationTools,
createConsultationRuntimeState,
} from "../src/mastra/consultation-tools.ts";
import { getJyotishAgent } from "../src/mastra/index.ts";
import { consultationAgentPublicEventSchema, createNdjsonParser } from "../src/lib/consultation-agent-events.ts";
import { collectAgentPublicEvents, streamAgentResponse } from "../src/lib/stream-agent-response.ts";
import { createConsultationPlan } from "../src/lib/consultation-plan.ts";
import { collectAgentPublicEvents, ensureFinalResponseText, streamAgentResponse } from "../src/lib/stream-agent-response.ts";
const serverChart = {
name: "测试",
@@ -36,23 +42,35 @@ function workflow(
test("context-bound tool keeps legacy single theme compatibility and calculates once", async () => {
let calls = 0;
let captured: unknown;
let capturedPlan: unknown;
const state = createConsultationRuntimeState();
const plan = createConsultationPlan({
userIntent: "事业如何", theme: "career", consultationMode: "unverified_birth_time", modelCreditCost: 1,
});
assert.throws(
() => (plan.requestedDomains as unknown as string[]).push("timing"),
TypeError,
);
const tools = createConsultationTools({
userId: "u", sessionId: "s", requestId: "r", consultationMode: "unverified_birth_time",
serverChart, state,
runWorkflow: async (input) => { calls += 1; captured = input; return workflow(); },
plan,
theme: "career", serverChart, state,
runWorkflow: async (input, options) => {
calls += 1; captured = input; capturedPlan = options?.plan; return workflow();
},
});
const tool = tools["run-jyotish-consultation"];
assert.deepEqual(Object.keys((tool.inputSchema as unknown as { shape: object }).shape), ["question", "domains", "theme"]);
const execute = tool.execute!;
const context = { observe: { span: async (_n: string, fn: () => Promise<unknown>) => fn(), log() {} } } as never;
const [first, second] = await Promise.all([
execute({ question: "事业如何", theme: "career" }, context),
execute({ question: "事业如何", theme: "career" }, context),
execute({ question: "尝试改成精确应期", theme: "timing" }, context),
execute({ question: "尝试改成婚恋", theme: "marriage" }, context),
]);
assert.equal(calls, 1);
assert.deepEqual(first, second);
assert.deepEqual(captured, { ...serverChart.toolInput, entryMode: "direct_chart", question: "事业如何", theme: "career" });
assert.strictEqual(capturedPlan, plan);
assert.equal(state.consultationToolCallCount, 1);
assert.equal(state.workflowReceipt?.preciseTiming, "blocked");
assert.deepEqual(state.workflowReceipt?.domains, ["career"]);
@@ -211,12 +229,25 @@ test("incremental NDJSON parser handles arbitrary chunk boundaries", () => {
});
test("uses a bounded dynamic step budget and reports truncation", () => {
const state = createConsultationRuntimeState({ plannedSteps: 1, reservedValidationSteps: 1 });
assert.deepEqual(state.stepBudget, { planned: 1, reservedValidation: 1, total: 2 });
assert.equal(appendConsultationRuntimeStep(state, { kind: "skill", name: "jyotish-vedic-astrology", status: "completed" }), true);
assert.equal(appendConsultationRuntimeStep(state, { kind: "validation", name: "ensure-final-response", status: "completed" }), true);
assert.equal(appendConsultationRuntimeStep(state, { kind: "tool", name: "unexpected-extra-step", status: "completed" }), false);
assert.equal(state.steps.length, 2);
assert.equal(state.stepsTruncated, true);
assert.deepEqual(consultationStepBudgetReceipt(state), { planned: 2, used: 2, remaining: 0, truncated: true });
});
function receipt(state: ReturnType<typeof createConsultationRuntimeState>) {
return {
runId: "run",
runtime: "mastra-agentic" as const,
skill: { name: "jyotish-vedic-astrology" as const, loaded: state.jyotishSkillLoaded },
steps: state.steps,
stepBudget: consultationStepBudgetReceipt(state),
workflow: state.workflowReceipt ?? { route: "career", status: "ready", preciseTiming: "blocked", missingLayers: [] },
};
}
@@ -271,6 +302,31 @@ test("incomplete runtime contract fails without saving a successful answer", asy
assert.equal(events.filter((event) => (event as { type?: string }).type === "run.failed").length, 1);
});
test("ensures a controlled final response after a successful tool-only run", async () => {
const state = createConsultationRuntimeState();
state.jyotishSkillLoaded = true;
state.consultationToolCallCount = 1;
state.consultationToolCompleted = true;
state.workflowReceipt = { route: "career", status: "ready", preciseTiming: "blocked", missingLayers: [] };
let completedOutput = "";
async function* chunks() {
yield { type: "tool-result", payload: { toolCallId: "tool-1", toolName: "run-jyotish-consultation", result: {} } };
}
const response = streamAgentResponse({
runId: "run", requestId: "req", state, stream: chunks(), requireTool: true,
toolStatus: () => "ready", receipt: () => receipt(state),
onComplete: (output) => { completedOutput = output; },
});
const events: unknown[] = [];
const parser = createNdjsonParser((event) => events.push(event));
parser.finish(await response.text());
assert.equal(ensureFinalResponseText("", true), completedOutput);
assert.match(completedOutput, /计算已完成/);
assert.equal(events.filter((event) => (event as { type?: string }).type === "answer.delta").length, 1);
assert.equal(events.filter((event) => (event as { type?: string }).type === "run.completed").length, 1);
assert.equal(state.steps.at(-1)?.name, "ensure-final-response");
});
test("persistence failure emits run.failed instead of run.completed", async () => {
const state = createConsultationRuntimeState();
+100
View File
@@ -1,6 +1,9 @@
import assert from "node:assert/strict";
import { readFileSync } from "node:fs";
import test from "node:test";
import { createConsultationPlan } from "../src/lib/consultation-plan.ts";
import { toAgentConsultationContext, toModelOutput } from "../src/mastra/consultation-workflow.ts";
test("passes transparent public-case references into the agent context", () => {
const workflowSource = readFileSync(new URL("../src/mastra/consultation-workflow.ts", import.meta.url), "utf8");
@@ -49,3 +52,100 @@ test("keeps strength, Ashtakavarga, and timing evidence available to the answer
assert.match(workflowSource, /numerical_parity: record\(data\.external_parity_gate\)/);
assert.match(workflowSource, /real_case_calibration: record\(data\.real_case_calibration\)/);
});
test("projects only bounded server-selected evidence to the model", () => {
const context = toAgentConsultationContext({
success: true,
question: "事业如何",
chart: {
birth: { date: "1990-01-02", time: "03:04", latitude: 25.03, longitude: 121.56 },
ascendant: { sign: "Leo", degree: 12.5, reported_time: "private-reported-time" },
planets: [{ name: "Sun", sign: "Capricorn", degree: 4.2, lng: "private-lng" }],
houses: { first: { sign: "Leo" } },
dasha: { current: "Mars", next: "Rahu", utc_offset: "private-utc-offset" },
shadbala: { planets: { Sun: { total_rupa: 7.1 } } },
ashtakavarga: { house_scores: { "10": 31 } },
modules: {
shadbala: { total: 412 },
ashtakavarga: { total: 28 },
dasha_boundaries: { next: "2027-03", utc_offset: "private-module-utc-offset" },
narayana_dasha: { current: "Aries" },
},
},
routing: { primary_theme: "career" },
thematic_report: {
themes: {
career: {
theme: "career",
summary: "保留这条服务端证据摘要",
narrative: "事业证据叙事",
strength: "strong",
account_balance: "private-account-balance",
recommendations: ["保持稳健推进"],
evidence: [{
technique: "D10-Dashamsha-local",
chart: "D10",
conclusion: "D10 事业分盘已进入证据链",
sentiment: "positive",
strength: "strong",
details: {
source: "chart",
derived: true,
entitlements: "private-entitlements",
access_scopes: "private-access-scopes",
},
}],
},
},
},
consumer_context: {
route: "career", core_status: "ready", available_layers: ["natal", "timing"],
missing_route_layers: [], hard_blockers: [],
technique_truth: { status: "verified", access_scopes: "private-validation-scopes" },
answer_policy: {
can_answer_direction: true,
can_answer_precise_timing: false,
entitlements: "private-policy-entitlements",
},
user_facing_limitation: "精确月份暂不可用",
},
reference_transparency: { similar_public_cases: { status: "public_context_only" } },
rectification: { summary: "none" },
});
const output = toModelOutput(context);
const careerPlan = createConsultationPlan({ userIntent: "事业如何", theme: "career" });
const careerOutput = toModelOutput(context, careerPlan);
const serialized = JSON.stringify(output);
assert.equal(output.packet_version, "consultation-evidence-packet-v2");
assert.equal("birth" in output, false);
assert.equal(serialized.includes("1990-01-02"), false);
assert.equal(serialized.includes("03:04"), false);
assert.equal(serialized.includes("25.03"), false);
assert.equal(serialized.includes("121.56"), false);
for (const privateValue of [
"private-reported-time", "private-lng", "private-utc-offset", "private-module-utc-offset",
"private-account-balance", "private-entitlements", "private-access-scopes",
"private-validation-scopes", "private-policy-entitlements",
]) {
assert.equal(serialized.includes(privateValue), false);
}
for (const privateKey of [
"reported_time", "lng", "utc_offset", "account_balance", "entitlements", "access_scopes",
]) {
assert.equal(serialized.includes(`"${privateKey}"`), false);
}
assert.equal(serialized.includes("保留这条服务端证据摘要"), true);
assert.equal(serialized.includes("D10-Dashamsha-local"), true);
assert.equal(serialized.includes("total_rupa"), true);
assert.equal(serialized.includes("house_scores"), true);
assert.equal(serialized.includes('"dasha"'), true);
assert.equal(serialized.includes("narayana_dasha"), true);
assert.equal(serialized.includes('"status":"verified"'), true);
const answerPolicy = output.evidence_contract.answer_policy as Record<string, unknown>;
assert.equal(answerPolicy.can_answer_precise_timing, false);
assert.deepEqual(output.claim_cards.map((card) => card.category), ["natal_foundation", "domain", "timing", "validation"]);
assert.deepEqual(careerOutput.claim_cards.map((card) => card.category), ["natal_foundation", "domain"]);
assert.equal(JSON.stringify(careerOutput).includes('"dasha"'), false);
assert.equal(serialized.includes("consultation-evidence-packet-v2"), true);
});
+126
View File
@@ -0,0 +1,126 @@
import assert from "node:assert/strict";
import test from "node:test";
import { z } from "zod";
import {
ConsultationPlanValidationError,
consultationPlanMaxCreditCost,
createConsultationPlan,
validateConsultationPlan,
} from "../src/lib/consultation-plan.ts";
import { consultationDomainIds } from "../src/lib/consultation-domain-registry.ts";
for (const theme of consultationDomainIds) {
test(`creates a bounded server-owned ${theme} plan`, () => {
const plan = createConsultationPlan({
userIntent: "请回答我的问题",
theme,
consultationMode: "verified_chart",
modelCreditCost: 1,
});
assert.equal(plan.plan_version, "consultation-plan-v2");
assert.equal(plan.birthTimeMode, "verified_chart");
assert.equal(plan.precisionBoundary, "server_evidence_required");
assert.equal(plan.maxCreditCost, consultationPlanMaxCreditCost);
});
}
test("rejects a theme outside the consultation allowlist", () => {
assert.throws(
() => createConsultationPlan({
userIntent: "请回答我的问题",
theme: "medical" as "career",
consultationMode: "verified_chart",
}),
(error: unknown) => error instanceof z.ZodError,
);
});
test("server birth-time mode fixes the precision boundary", () => {
assert.equal(createConsultationPlan({
userIntent: "未来什么时候适合行动",
theme: "timing",
consultationMode: "unverified_birth_time",
}).precisionBoundary, "precise_timing_blocked");
assert.equal(createConsultationPlan({
userIntent: "什么是上升星座",
theme: "general",
consultationMode: "general_no_birth_time",
}).precisionBoundary, "precise_timing_blocked");
});
test("rejects mode or precision tampering", () => {
const plan = createConsultationPlan({
userIntent: "未来什么时候适合行动",
theme: "timing",
consultationMode: "unverified_birth_time",
});
assert.throws(
() => validateConsultationPlan({ ...plan, precisionBoundary: "server_evidence_required" }, {
consultationMode: "unverified_birth_time",
modelCreditCost: 1,
}),
(error: unknown) => error instanceof ConsultationPlanValidationError
&& error.code === "plan_precision_mismatch",
);
assert.throws(
() => validateConsultationPlan(plan, {
consultationMode: "verified_chart",
modelCreditCost: 1,
}),
(error: unknown) => error instanceof ConsultationPlanValidationError
&& error.code === "plan_mode_mismatch",
);
});
test("rejects model cost above the release consultation ceiling", () => {
assert.throws(
() => createConsultationPlan({
userIntent: "事业如何",
theme: "career",
consultationMode: "verified_chart",
modelCreditCost: consultationPlanMaxCreditCost + 1,
}),
(error: unknown) => error instanceof ConsultationPlanValidationError
&& error.code === "plan_cost_ceiling_exceeded",
);
});
test("rejects non-positive or fractional model costs", () => {
for (const modelCreditCost of [0, -1, 0.5, Number.NaN, Number.POSITIVE_INFINITY]) {
assert.throws(
() => createConsultationPlan({
userIntent: "事业如何",
theme: "career",
consultationMode: "verified_chart",
modelCreditCost,
}),
(error: unknown) => error instanceof ConsultationPlanValidationError
&& error.code === "plan_cost_ceiling_exceeded",
);
}
});
test("deep-freezes the server-owned plan and nested evidence lists", () => {
const plan = createConsultationPlan({
userIntent: "事业如何",
theme: "career",
consultationMode: "verified_chart",
modelCreditCost: 1,
});
const mutable = plan as unknown as {
precisionBoundary: string;
requestedDomains: string[];
requiredEvidenceCategories: string[];
};
assert.equal(Object.isFrozen(plan), true);
assert.equal(Object.isFrozen(plan.requestedDomains), true);
assert.equal(Object.isFrozen(plan.requiredEvidenceCategories), true);
assert.throws(() => { mutable.precisionBoundary = "precise_timing_blocked"; }, TypeError);
assert.throws(() => mutable.requestedDomains.push("timing"), TypeError);
assert.throws(() => mutable.requiredEvidenceCategories.splice(0, 1), TypeError);
assert.equal(plan.precisionBoundary, "server_evidence_required");
assert.deepEqual(plan.requestedDomains, ["career"]);
assert.deepEqual(plan.requiredEvidenceCategories, ["natal_foundation", "domain"]);
});
@@ -0,0 +1,31 @@
import assert from "node:assert/strict";
import test from "node:test";
import { createConsultationReplyMetadata, consultationReplyMetadataSchema } from "../src/lib/consultation-reply-metadata.ts";
import { parseAgentReply } from "../src/lib/agent-reply.ts";
for (const theme of ["career", "marriage", "wealth", "timing", "general"] as const) {
test(`server-owned metadata is bounded for ${theme}`, () => {
const metadata = createConsultationReplyMetadata({ theme, question: "未来的重点是什么?" });
const parsed = consultationReplyMetadataSchema.parse(metadata);
assert.equal(parsed.suggestions.length, 3);
assert.ok(parsed.suggestions.every((item) => item.length > 0 && item.length <= 80));
assert.ok(parsed.title.length >= 6 && parsed.title.length <= 14);
assert.doesNotMatch(parsed.title, /[\d\p{P}\p{S}]/u);
});
}
test("server metadata wins without blocking legacy hidden-block parsing", () => {
const reply = parseAgentReply(
[
"回答正文",
'<!--AYANAM_SUGGESTIONS:["模型问题一","模型问题二","模型问题三"]-->',
"<!--AYANAM_TITLE:模型标题-->",
].join("\n"),
"career",
createConsultationReplyMetadata({ theme: "career", question: "工作变化的重点是什么?" }),
);
assert.equal(reply.text, "回答正文");
assert.deepEqual(reply.suggestions, ["我更适合怎样的职业路径?", "未来一年事业上要避开什么?", "我该如何发挥自己的优势?"]);
assert.equal(reply.title, "工作变化的重点是什么");
});
@@ -33,14 +33,21 @@ test("route service loads complete server truth before billing and uses only rep
order.push("profile");
return profile;
},
beforeReserve({ consultationMode, serverChart }) {
order.push("plan");
assert.equal(consultationMode, "unverified_birth_time");
assert.equal(serverChart?.truth.selectedTimeKind, "reported");
return Object.freeze({ version: "consultation-plan-v2" });
},
async reserve() {
order.push("reserve");
return { reservation: "ok" };
},
});
assert.deepEqual(order, ["profile", "reserve"]);
assert.deepEqual(order, ["profile", "plan", "reserve"]);
assert.deepEqual(prepared.reservation, { reservation: "ok" });
assert.deepEqual(prepared.preReserveResult, { version: "consultation-plan-v2" });
assert.deepEqual(prepared.serverChart?.toolInput, {
year: 1997,
month: 8,
@@ -63,6 +70,32 @@ test("route service loads complete server truth before billing and uses only rep
});
});
test("pre-reserve plan guard sees resolved server mode and blocks billing on failure", async () => {
const order: string[] = [];
await assert.rejects(prepareConsultationRoute({
userId: "user-1",
mode: "general_no_birth_time",
loadProfile: async () => ({
...profile,
reported_birth_time: "14:49:00",
birth_time_source: "family_exact",
birth_time_status: "reported",
}),
beforeReserve({ consultationMode, serverChart }) {
order.push("plan");
assert.equal(consultationMode, "unverified_birth_time");
assert.equal(serverChart?.toolInput.minute, 49);
throw new Error("plan rejected");
},
reserve: async () => {
order.push("reserve");
return "reserved";
},
}), /plan rejected/);
assert.deepEqual(order, ["plan"]);
});
test("verified route uses only server active time", async () => {
const prepared = await prepareConsultationRoute({
userId: "user-1",
@@ -6,9 +6,59 @@ const route = readFileSync(new URL("../src/app/api/consult/route.ts", import.met
const reportsRoute = readFileSync(new URL("../src/app/api/reports/route.ts", import.meta.url), "utf8");
const mastra = readFileSync(new URL("../src/mastra/index.ts", import.meta.url), "utf8");
const tools = readFileSync(new URL("../src/mastra/consultation-tools.ts", import.meta.url), "utf8");
const stream = readFileSync(new URL("../src/lib/stream-agent-response.ts", import.meta.url), "utf8");
const workflow = readFileSync(new URL("../src/mastra/consultation-workflow.ts", import.meta.url), "utf8");
const plan = readFileSync(new URL("../src/lib/consultation-plan.ts", import.meta.url), "utf8");
const routeService = readFileSync(new URL("../src/lib/consultation-route-service.ts", import.meta.url), "utf8");
const pythonPlanContract = readFileSync(new URL("../../scripts/consultation_plan_contract.py", import.meta.url), "utf8");
const stagingCompose = readFileSync(new URL("../../deploy/docker-compose.staging.yml", import.meta.url), "utf8");
test("consultation plans are server-owned and bounded", () => {
assert.match(plan, /consultationPlanSchema/);
assert.match(plan, /requestedDomains/);
assert.match(plan, /requiredEvidenceCategories/);
assert.match(plan, /createConsultationPlan/);
assert.match(tools, /const userIntent = ctx\.plan\?\.userIntent \?\? input\.question/);
assert.match(tools, /input\.domains === undefined && context\.plan && context\.theme/);
assert.match(tools, /return \[context\.theme\]/);
assert.match(tools, /const domainPlan = ctx\.plan[\s\S]*createConsultationPlan\(\{/);
assert.match(tools, /userIntent,[\s\S]*theme: domain/);
assert.match(tools, /question: userIntent,[\s\S]*theme: domain/);
assert.match(tools, /plan: domainPlan/);
assert.match(workflow, /packet_version: "consultation-evidence-packet-v2"/);
assert.match(workflow, /claim_cards/);
assert.match(tools, /modelOutput: toModelOutput\(agentContext, domainPlan\)/);
assert.match(tools, /modelOutput: toModelOutput[\s\S]*ctx\.state\.consultationToolCompleted = true/);
assert.match(plan, /precisionBoundary/);
assert.match(plan, /consultationPlanMaxCreditCost = 1/);
const modelToolInput = tools.slice(tools.indexOf("const consultationToolInputSchema"), tools.indexOf("const MAX_RECORDED_STEPS"));
assert.doesNotMatch(modelToolInput, /birth|latitude|longitude|engine|script|credit/i);
const prepareCall = route.slice(route.indexOf("prepared = await prepareConsultationRoute"), route.indexOf("const modelSelection"));
assert.match(prepareCall, /beforeReserve: \(\{ consultationMode \}\) => createConsultationPlan\(\{/);
assert.ok(prepareCall.indexOf("beforeReserve:") < prepareCall.indexOf("reserve: () => reserveConsultationModel("));
assert.ok(routeService.indexOf("const preReserveResult = input.beforeReserve") < routeService.indexOf("const reservation = await input.reserve()"));
assert.match(route, /plan: prepared\.preReserveResult,[\s\S]*theme: consultationTheme/);
assert.match(route, /runConsultationWorkflow\(toolInput, \{[\s\S]*plan: prepared\.preReserveResult/);
assert.match(workflow, /plan_version: workflowRequest\.plan_version/);
assert.match(workflow, /strict_workflow_route: workflowRequest\.strictWorkflowRoute/);
assert.match(workflow, /required_layers: workflowRequest\.requiredLayers/);
assert.match(workflow, /claim_boundary: workflowRequest\.claimBoundary/);
assert.match(workflow, /precision_boundary: workflowRequest\.precision_boundary/);
assert.match(workflow, /required_evidence_categories: workflowRequest\.required_evidence_categories/);
assert.match(pythonPlanContract, /PLAN_VERSION = "consultation-plan-v2"/);
assert.match(pythonPlanContract, /server_allowlist_validated/);
assert.match(pythonPlanContract, /can_answer_precise_timing": False/);
});
test("uses one runtime step append entry and no scattered hard-coded step cap", () => {
assert.match(tools, /export function appendConsultationRuntimeStep/);
assert.doesNotMatch(tools, /steps\.length\s*>=\s*32/);
assert.doesNotMatch(stream, /state\.steps\.push/);
assert.doesNotMatch(stream, /steps\.length\s*<\s*32/);
});
test("personal consultation lets the Agent invoke the server-bound workflow tool", () => {
const agenticStart = route.indexOf("async function runAgenticConsultation");
const agenticBranch = route.slice(
@@ -22,6 +72,7 @@ test("personal consultation lets the Agent invoke the server-bound workflow tool
assert.match(tools, /\(ctx\.runWorkflow \?\? runConsultationWorkflow\)\(toolInput, \{/);
assert.match(tools, /return \{ "run-jyotish-consultation": consultationTool \};/);
assert.match(agenticBranch, /state\.workflowReceipt\?\.preciseTiming === "allowed"/);
assert.match(route, /createConsultationReplyMetadata/);
});
test("defers optional external evidence only for foreground chat", () => {
@@ -52,6 +103,8 @@ test("validates and emits non-sensitive workflow and execution receipts", () =>
test("carries commercial technique truth into the model contract", () => {
assert.match(workflow, /technique_truth/);
assert.match(mastra, /deterministic_claims_forbidden_for/);
assert.doesNotMatch(mastra, /AYANAM_SUGGESTIONS|AYANAM_TITLE/);
assert.doesNotMatch(mastra, /2-5 short paragraphs/);
assert.match(mastra, /reference_only/);
assert.match(mastra, /Do not use a restricted technique/);
});
@@ -67,9 +120,9 @@ test("projects consultation themes through explicit strict workflow taxonomy", (
});
test("staging enables the agentic consultation runtime without changing production compose", () => {
test("agentic consultation is the safe default and legacy is explicit rollback", () => {
assert.match(stagingCompose, /CONSULTATION_AGENTIC_RUNTIME: enabled/);
assert.match(route, /CONSULTATION_AGENTIC_RUNTIME/);
assert.match(route, /legacy/);
assert.match(route, /canary/);
assert.match(route, /CONSULTATION_AGENTIC_RUNTIME\?\.trim\(\)\.toLowerCase\(\) \?\? "enabled"/);
assert.match(route, /mode === "legacy"/);
assert.match(route, /mode !== "canary"/);
});
@@ -1,5 +1,6 @@
import assert from "node:assert/strict";
import test from "node:test";
import { createConsultationPlan } from "../src/lib/consultation-plan.ts";
import { projectConsultationWorkflowRequest } from "../src/lib/consultation-workflow-request.ts";
import { runConsultationWorkflow } from "../src/mastra/index.ts";
import type { ConsultationInput } from "../src/mastra/index.ts";
@@ -9,7 +10,12 @@ test("timing questions use a legal report theme and preserve a timing route hint
const question = "未来哪些阶段值得把握?";
// When: its workflow request is projected for the Python service.
const request = projectConsultationWorkflowRequest(question, "timing");
const plan = createConsultationPlan({
userIntent: question,
theme: "timing",
consultationMode: "unverified_birth_time",
});
const request = projectConsultationWorkflowRequest(question, "timing", plan);
// Then: the public theme remains a canonical allowlisted domain with strict route metadata.
assert.equal(request.question, "应期与阶段问题:未来哪些阶段值得把握?");
@@ -18,6 +24,9 @@ test("timing questions use a legal report theme and preserve a timing route hint
assert.ok(request.requiredLayers.includes("Narayana"));
assert.ok(request.requiredLayers.includes("negative holdout gate"));
assert.equal(request.claimBoundary, "candidate_day_month_window_only_until_holdout_passes");
assert.equal(request.plan_version, "consultation-plan-v2");
assert.equal(request.precision_boundary, "precise_timing_blocked");
assert.equal(request.timing_horizon, "next_12_months");
});
test("consultation workflow allows a cold engine run to finish", async () => {
@@ -68,7 +77,13 @@ test("timing input projects only legal private workflow fields", async () => {
try {
// When: the adapter invokes the Python workflow.
await runConsultationWorkflow(input);
await runConsultationWorkflow(input, {
plan: createConsultationPlan({
userIntent: input.question,
theme: input.theme,
consultationMode: "unverified_birth_time",
}),
});
} finally {
globalThis.fetch = originalFetch;
}
@@ -79,6 +94,13 @@ test("timing input projects only legal private workflow fields", async () => {
assert.equal(body.question_text, "应期与阶段问题:未来哪些阶段值得把握?");
assert.deepEqual(body.theme, ["timing"]);
assert.match(requestBody, /"timing"/);
assert.equal(body.plan_version, "consultation-plan-v2");
assert.equal(body.strict_workflow_route, "timing");
assert.ok(body.required_layers.includes("negative holdout gate"));
assert.equal(body.claim_boundary, "candidate_day_month_window_only_until_holdout_passes");
assert.equal(body.precision_boundary, "precise_timing_blocked");
assert.deepEqual(body.requested_domains, ["timing"]);
assert.deepEqual(body.required_evidence_categories, ["natal_foundation", "timing", "validation"]);
assert.equal(input.question, "未来哪些阶段值得把握?");
assert.equal(input.theme, "timing");
});
@@ -1,5 +1,6 @@
import assert from "node:assert/strict";
import { readFileSync } from "node:fs";
import { readFileSync, realpathSync } from "node:fs";
import { basename } from "node:path";
import { fileURLToPath } from "node:url";
import test from "node:test";
@@ -7,6 +8,7 @@ import {
RECTIFICATION_AGENT_HARD_STEP_LIMIT,
RECTIFICATION_AGENT_MAX_STEPS,
RECTIFICATION_AGENT_STEP_BUDGETS,
RECTIFICATION_V9_PACKAGE_PATH,
RECTIFICATION_V9_SKILL_PATH,
RECTIFICATION_V9_SKILL_NAME,
getRectificationV9Agent,
@@ -60,11 +62,10 @@ test("system prompt carries only high-priority boundaries, never the method copy
test("agent pins the dedicated rectification skill and its fixed version", () => {
assert.equal(RECTIFICATION_V9_SKILL_NAME, "jyotish-birth-time-rectification");
assert.ok(
RECTIFICATION_V9_SKILL_PATH.endsWith(
"skills/jyotish-birth-time-rectification/versions/10.0.0",
),
);
assert.equal(basename(RECTIFICATION_V9_SKILL_PATH), RECTIFICATION_V9_SKILL_NAME);
assert.ok(RECTIFICATION_V9_PACKAGE_PATH.endsWith("skills/jyotish-birth-time-rectification/versions/10.0.0"));
assert.notEqual(RECTIFICATION_V9_SKILL_PATH, RECTIFICATION_V9_PACKAGE_PATH);
assert.equal(realpathSync(RECTIFICATION_V9_SKILL_PATH), RECTIFICATION_V9_PACKAGE_PATH);
assert.equal(RECTIFICATION_SKILL_NAME, "jyotish-birth-time-rectification");
assert.equal(RECTIFICATION_SKILL_VERSION, "10.0.0");
});
+6 -1
View File
@@ -9,7 +9,7 @@ import {
writeFileSync,
} from "node:fs";
import { tmpdir } from "node:os";
import { dirname, isAbsolute, join, resolve } from "node:path";
import { basename, dirname, isAbsolute, join, resolve } from "node:path";
import { fileURLToPath } from "node:url";
import { test, type TestContext } from "node:test";
import {
@@ -17,6 +17,7 @@ import {
createSkillPackageRegistry,
resolveActiveSkillPackage,
resolveExactSkillPackage,
resolveSkillPackageRuntimePath,
resolveSkillPackageVersion,
verifyAllActiveSkillPackages,
type SkillPackageIdentity,
@@ -111,6 +112,10 @@ test("checked-in registry verifies all three active packages and returns absolut
projectRoot,
});
assert.equal(active.version, "6.9.14");
const runtimePath = resolveSkillPackageRuntimePath(active);
assert.equal(basename(runtimePath), active.name);
assert.equal(realpathSync(runtimePath), active.resolvedPath);
assert.notEqual(runtimePath, active.resolvedPath);
assert.deepEqual(
resolveExactSkillPackage(active.name, active.version, active.sha256, {
projectRoot,