TASK-consult-evidence-card-v2-20260927 T3. Card version evidence-card-v2. - Base section (every card): the engine's D9 summary (D9 lagna, each planet's D9 sign and dignity, Vargottama, D1/D9 reversals); D9 houses and aspects stay out. - Career: + AL (engine pada A1), 10H SAV, SAV of the Jupiter / Saturn transit signs. - Marriage: + 5H / 5L, day / night, Punarphoo (observation_only), Double Transit on 7H / 7L (house-7 run) and DK / UL, Vivah Saham, gender unknown. - Wealth: 8H / 12H named. - Annual: the annual Tajika chart verbatim (parameter_sensitive), or 年盘未接入 when the pack is blocked / not attached (never natal data); houses 1 + running / next AD lords' houses + the year's Jupiter / Saturn transit houses, with their basis; ingress / station dates only. - Timing: Rahu / Ketu with ingress dates, Jupiter / Saturn SAV and BAV, Double Transit conclusions (no degrees); vargas follow the turn's other domain, D9 when alone. - Chara Dasha leaves every card; KP is never on a card. The lookup enum adds karakamsha, dispositor_chains, inter_chart_linkage, argala, moon_transit; a KP lookup travels with its blocked note. - System prompt and tool descriptions name the D9 summary, the parameter_sensitive / observation_only tags, 年盘未接入 and the lookups. - Telemetry card version / agentVersion bumped to v2 (three-column notes in the touched tests). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017eEAG8HD3mm8gsKXgk8uU8
94 lines
5.6 KiB
TypeScript
94 lines
5.6 KiB
TypeScript
// TASK-consult-evidence-card-20260927 T6 (D7): per-turn evidence-card record
|
||
// in [agent-observability], six fields only, no user text.
|
||
import assert from "node:assert/strict";
|
||
import { readFileSync } from "node:fs";
|
||
import test from "node:test";
|
||
|
||
import {
|
||
createAgentObservabilityLogger,
|
||
EVIDENCE_CARD_TELEMETRY_FIELDS,
|
||
evidenceCardTelemetrySchema,
|
||
type AgentObservabilityEvent,
|
||
} from "../src/lib/agent-observability.ts";
|
||
import {
|
||
consultationEvidenceCardTelemetry,
|
||
createConsultationRuntimeState,
|
||
createConsultationTools,
|
||
} from "../src/mastra/consultation-tools.ts";
|
||
|
||
type Json = Record<string, unknown>;
|
||
const golden = JSON.parse(readFileSync(
|
||
new URL("./fixtures/consult-evidence-card-golden.json", import.meta.url),
|
||
"utf8",
|
||
)) as { charts: Array<{ id: string; workflow: Json }> };
|
||
const workflow = golden.charts[0]!.workflow;
|
||
const route = readFileSync(new URL("../src/app/api/consult/route.ts", import.meta.url), "utf8");
|
||
|
||
async function stateAfterCalculation() {
|
||
const state = createConsultationRuntimeState();
|
||
const tools = createConsultationTools({
|
||
userId: "user-telemetry", sessionId: "session-telemetry", requestId: "req-telemetry", consultationMode: "verified_chart",
|
||
serverChart: { name: "public", toolInput: { year: 2000, month: 1, day: 1, hour: 0, minute: 0, city: "fixture", lat: 0, lon: 0, tz: 0, ayanamsa: "raman" }, truth: { birthTimeSource: "reported" } } as never,
|
||
state,
|
||
runWorkflow: async () => structuredClone(workflow) as never,
|
||
});
|
||
await tools["run-jyotish-consultation"].execute!({ question: "q", domains: ["parents"] } as never, { writer: { custom: async () => {} } } as never);
|
||
return state;
|
||
}
|
||
|
||
test("the record has exactly the six approved fields", async () => {
|
||
assert.deepEqual([...EVIDENCE_CARD_TELEMETRY_FIELDS].sort(), Object.keys(evidenceCardTelemetrySchema.unwrap().shape).sort());
|
||
assert.deepEqual([...EVIDENCE_CARD_TELEMETRY_FIELDS], ["domains", "cardVersion", "cardChars", "cardTokenEstimate", "citedFieldIds", "feedback"]);
|
||
const state = await stateAfterCalculation();
|
||
const record = consultationEvidenceCardTelemetry(state, "");
|
||
assert.ok(record);
|
||
assert.deepEqual(Object.keys(record).sort(), [...EVIDENCE_CARD_TELEMETRY_FIELDS].sort());
|
||
assert.deepEqual(record.domains, ["parents"]);
|
||
// 原值: "evidence-card-v1" / 新值: "evidence-card-v2" / 原因: TASK-consult-evidence-card-v2-20260927 T3 卡版本升级
|
||
assert.equal(record.cardVersion, "evidence-card-v2");
|
||
assert.equal(record.cardChars, state.evidenceCardChars);
|
||
assert.equal(record.cardTokenEstimate, Math.round(record.cardChars / 3.5));
|
||
assert.deepEqual(record.citedFieldIds, []);
|
||
assert.equal(record.feedback, "none");
|
||
});
|
||
|
||
test("the schema is strict: no extra field, no free text in field ids", () => {
|
||
const valid = { domains: ["parents"], cardVersion: "evidence-card-v2", cardChars: 10, cardTokenEstimate: 3, citedFieldIds: ["base.natal.ascendant.sign"], feedback: "up" };
|
||
assert.equal(evidenceCardTelemetrySchema.safeParse(valid).success, true);
|
||
for (const extra of ["question", "answer", "sessionId", "userId", "birthDate", "matched"]) {
|
||
assert.equal(evidenceCardTelemetrySchema.safeParse({ ...valid, [extra]: "x" }).success, false, extra);
|
||
}
|
||
assert.equal(evidenceCardTelemetrySchema.safeParse({ ...valid, citedFieldIds: ["我和父母关系如何"] }).success, false);
|
||
assert.equal(evidenceCardTelemetrySchema.safeParse({ ...valid, feedback: "great" }).success, false);
|
||
});
|
||
|
||
test("the logged line keeps field ids and drops every word of the user's text", async () => {
|
||
const state = await stateAfterCalculation();
|
||
const card = state.evidenceCard!;
|
||
const pdStart = String((card.base.timing.vimshottari.pratyantardasha as Json).start);
|
||
// Fictional sensitive values, not a real person.
|
||
const secrets = ["虚构名字王小明", "fictional.person@example.invalid", "1990-01-01", "我和父母关系如何"];
|
||
const answer = `${secrets[0]}(${secrets[1]},生日 ${secrets[2]})问:${secrets[3]}。子运从 ${pdStart} 开始。`;
|
||
const lines: AgentObservabilityEvent[] = [];
|
||
const log = createAgentObservabilityLogger((event) => lines.push(event));
|
||
log({ runId: "req-telemetry", agentVersion: "consultation-evidence-card-v2", evidenceCard: consultationEvidenceCardTelemetry(state, answer) });
|
||
const text = JSON.stringify(lines);
|
||
for (const secret of secrets) assert.equal(text.includes(secret), false, secret);
|
||
assert.equal(text.includes(pdStart), false, "the matched date is not kept");
|
||
assert.equal(text.includes("session-telemetry"), false);
|
||
assert.equal(text.includes("user-telemetry"), false);
|
||
assert.ok(lines[0]?.evidenceCard?.citedFieldIds.includes("base.timing.vimshottari.pratyantardasha.start"));
|
||
});
|
||
|
||
test("the consult route logs the record once per natal turn, without session or user id", () => {
|
||
assert.match(route, /const evidenceCard = consultationEvidenceCardTelemetry\(state, answerForTelemetry\);/);
|
||
const block = route.slice(route.indexOf("const evidenceCard = consultationEvidenceCardTelemetry"), route.indexOf("const settleRun = async"));
|
||
// 原值: consultation-evidence-card-v1 / 新值: v2 / 原因: 卡 v2(TASK-consult-evidence-card-v2-20260927 T3)
|
||
assert.match(block, /agentVersion: "consultation-evidence-card-v2"/);
|
||
assert.doesNotMatch(block, /sessionId|userId/);
|
||
const natal = route.slice(route.indexOf("const agent = getJyotishAgent(selectedModel, agentContext);"));
|
||
assert.match(natal, /answerForTelemetry = output;[\s\S]*return settleRun\(\(\) => completeResponse\(/);
|
||
// Only a finished answer counts citations; a failed run logs the card size with no citations.
|
||
assert.doesNotMatch(natal, /onError: \(error, _emitted, output\)/);
|
||
});
|