Files
Jyotisha/frontend/tests/consultation-context.test.ts
T
Jesse_Chen 927bdd7a21
Independent Staging Quality Gate / validate (push) Failing after 8m32s
Independent Staging Quality Gate / publish (push) Has been skipped
fix(consult): compute the antardasha boundaries answers need, and stop billing an apology for an unwritten answer
The model packet read chart.modules.dasha_boundaries, a key the engine never
wrote, so no answer ever had sub-period boundaries while the receipt still
reported precise timing as allowed. The server now cuts the running mahadasha
into antardashas out of the periods the packet already shows, exposes them as
their own evidence section, and precise timing requires that section.

A run whose calculation succeeded and whose model then wrote nothing was
answered with a fixed apology and billed as completed. It now asks once more
against the cached calculation, and fails with empty_answer—no charge—if that
attempt is silent too.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-18 18:32:49 +08:00

165 lines
8.3 KiB
TypeScript

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");
const agentSource = readFileSync(new URL("../src/mastra/index.ts", import.meta.url), "utf8");
assert.match(workflowSource, /reference_transparency:\s*record\(data\.reference_transparency\)/);
assert.match(workflowSource, /vedastro_gateway:\s*record\(data\.vedastro_gateway\)/);
assert.match(workflowSource, /ashtakavarga:\s*chart\.ashtakavarga/);
assert.match(agentSource, /high_similarity_public_references_available/);
assert.match(agentSource, /requested_uncovered_domains/);
assert.match(agentSource, /public_context_only/);
assert.match(agentSource, /timing_state/);
assert.match(agentSource, /partial_match/);
assert.match(agentSource, /narayana_status/);
assert.match(agentSource, /transit_status/);
assert.match(agentSource, /Jupiter and Saturn relative houses/);
assert.match(agentSource, /exact_triggers as technical trigger points/);
assert.match(agentSource, /production_tuning_allowed=false/);
assert.match(agentSource, /no_majority_vote/);
assert.match(agentSource, /method_variant_not_majority_vote/);
assert.match(agentSource, /Shadbala\/Ashtakavarga component differences/);
assert.match(agentSource, /D2, D11/);
assert.match(agentSource, /gender-specific spouse significators are supplements/);
assert.match(agentSource, /male.*Venus/);
assert.match(agentSource, /female.*Jupiter\/Mars/);
});
test("keeps strength, Ashtakavarga, and timing evidence available to the answer model", () => {
const workflowSource = readFileSync(new URL("../src/mastra/consultation-workflow.ts", import.meta.url), "utf8");
const agentSource = readFileSync(new URL("../src/mastra/index.ts", import.meta.url), "utf8");
assert.match(workflowSource, /shadbala: chart\.shadbala/);
assert.match(workflowSource, /shadbala_boundary:/);
assert.match(workflowSource, /ashtakavarga: modules\.ashtakavarga/);
// This line used to pin the misspelling: it asserted the packet read
// `modules.dasha_boundaries` without anything checking that the engine writes
// that key. tests/test_consultation_consumer_context.py now compares the names
// across the boundary; this only pins that timing evidence is still passed.
assert.match(workflowSource, /dasha_sub_periods: modules\.dasha_sub_periods/);
assert.match(workflowSource, /narayana_dasha: modules\.narayana_dasha/);
assert.match(workflowSource, /evidence_contract:/);
assert.match(workflowSource, /missing_route_layers: consumerContext\.missing_route_layers/);
assert.match(workflowSource, /answer_policy: consumerContext\.answer_policy/);
assert.match(agentSource, /evidence_contract\.answer_policy/);
assert.match(agentSource, /can_answer_precise_timing/);
assert.match(workflowSource, /boundary: "not_auto_rectified"/);
assert.match(agentSource, /rectification\.boundary=not_auto_rectified/);
assert.match(workflowSource, /external_engine_evidence:/);
assert.match(workflowSource, /runtime_truth: record\(data\.runtime_truth\)/);
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_sub_periods: {
current: { antardasha: { lord: "Saturn", start: "2025-10-17", end: "2027-05-18" } },
boundary_count: 9,
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);
// Antardasha boundaries are what an answer needs to name a month at all, and
// they were absent from every packet while this field named a module key the
// engine never wrote.
assert.equal(serialized.includes("dasha_sub_periods"), true);
assert.equal(serialized.includes("2027-05-18"), 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);
});