Files
Jyotisha/frontend/tests/personal-report-generation.test.ts
T
Jesse_Chen ef1bd6dfa9 feat(report): give the writer a static interpretation guide
The report writer had no interpretation methodology at all: a local agent
calling the jyotish skill can read the reference library, the report model
could read nothing. It could only restate the bundle.

- frontend/src/lib/report-interpretation-packs/ holds one general pack and
  one pack per report theme, distilled from the in-repo reference guides.
  They constrain wording and reasoning discipline (term modernisation,
  how to talk about relative strength and SAV scores, the reasoning errors
  to avoid, the banned phrasings) and never assert a chart fact.
- The general pack rides INSIDE the cached system message so the cached
  prefix stays byte-identical across sections; the chapter pack follows it
  and summary calls get the general pack only.
- Skill jyotish-personal-report goes to 1.1.0 (1.0.0 deprecated): the
  contract now names interpretiveFacts and themeNarrativeSeeds as a
  bounded fact layer and states that the knowledge pack is not a fact
  source and cannot raise certainty.
- Telemetry records interpretiveFactCount and knowledgePackCharacters as
  numbers only; the counter never throws so telemetry cannot break a run.

Tests lock every theme resolving a pack, the 3,000 character budget, a
forbidden-substring scan (paths, module names, vendor names, artefact
names), the byte-stable cache prefix, and that no evidence id or date
appears in the static content.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016P5RoqzmUQEbeC2qjAkeGr
2026-09-01 20:35:02 +00:00

1346 lines
53 KiB
TypeScript

import assert from "node:assert/strict";
import test from "node:test";
import { safeParseServerReportDocument, computeEvidenceHash } from "../src/lib/personal-report-contract.server-core.ts";
import {
computeReportEvidenceBundleHash,
emptyReportInterpretiveFacts,
finalizeReportEvidenceBundleV2,
validateReportEvidenceBundleV2,
type ReportEvidenceBundleV2,
} from "../src/lib/report-evidence-bundle-v2.ts";
import {
REPORT_STABLE_CODES,
ReportEvidenceInsufficientError,
applyReportGuard,
assembleReportDocument,
buildReportEvidenceBundleV2,
buildReportEvidencePacket,
canonicalSerialize,
computeRequestFingerprint,
findForbiddenDeterministicClaims,
generatePersonalReport,
redactDeterministicSentences,
resolveSkillSnapshot,
sha256Hex,
type SkillSnapshot,
} from "../src/lib/personal-report-generation.ts";
import { PRECISE_TIMING_PATTERNS } from "../src/lib/personal-report-generation.ts";
import type {
PersonalReportAgentOutput,
ReportAgentPort,
ReportEvidencePacket,
} from "../src/mastra/personal-report.ts";
// ---------------------------------------------------------------------------
// Fixtures: a real-shaped workflow response matching the Python main chain
// (object-map planets/houses/sections, varga_full D9_Navamsa/D10_Dasamsa)
// ---------------------------------------------------------------------------
function pythonStyleChartPayload() {
const signNames = ["Aries", "Taurus", "Gemini", "Cancer", "Leo", "Virgo", "Libra", "Scorpio", "Sagittarius", "Capricorn", "Aquarius", "Pisces"];
const ascIndex = 4; // Leo
const planetsMap: Record<string, Record<string, unknown>> = {
Sun: { sign: "Leo", degree: 142.5, degree_raw: 142.5, degree_in_sign: 22.5, house: 1, retrograde: false, speed: 1.0 },
Moon: { sign: "Virgo", degree: 172.5, degree_raw: 172.5, degree_in_sign: 22.5, house: 2, retrograde: false, speed: 13.0 },
Mars: { sign: "Libra", degree: 202.5, degree_raw: 202.5, degree_in_sign: 22.5, house: 3, retrograde: false, speed: 0.6 },
Mercury: { sign: "Scorpio", degree: 232.5, degree_raw: 232.5, degree_in_sign: 22.5, house: 4, retrograde: true, speed: -0.5 },
Jupiter: { sign: "Sagittarius", degree: 262.5, degree_raw: 262.5, degree_in_sign: 22.5, house: 5, retrograde: false, speed: 0.2 },
Venus: { sign: "Capricorn", degree: 292.5, degree_raw: 292.5, degree_in_sign: 22.5, house: 6, retrograde: false, speed: 1.1 },
Saturn: { sign: "Aquarius", degree: 322.5, degree_raw: 322.5, degree_in_sign: 22.5, house: 7, retrograde: true, speed: -0.1 },
Rahu: { sign: "Pisces", degree: 352.5, degree_raw: 352.5, degree_in_sign: 22.5, house: 8, retrograde: true, speed: -0.05 },
Ketu: { sign: "Virgo", degree: 172.5, degree_raw: 172.5, degree_in_sign: 22.5, house: 2, retrograde: true, speed: -0.05 },
};
const housesMap: Record<string, Record<string, unknown>> = {};
for (let index = 0; index < 12; index += 1) {
housesMap[`house_${index + 1}`] = {
cusp_sign: signNames[(ascIndex + index) % 12],
cusp_degree: 140.5 + index * 30,
lord: signNames[(ascIndex + index) % 12],
};
}
const d9Planets: Record<string, Record<string, unknown>> = {
Sun: { sign: "Leo", sign_idx: 4 },
Moon: { sign: "Virgo", sign_idx: 5 },
Mars: { sign: "Cancer", sign_idx: 3 },
};
const d10Planets: Record<string, Record<string, unknown>> = {
Sun: { sign: "Taurus", sign_idx: 1 },
Moon: { sign: "Gemini", sign_idx: 2 },
};
return {
success: true,
chart: {
ascendant: { sign: "Leo", degree: 20.5, degree_raw: 140.5, lon: 140.5, sign_cn: "狮子座", lord: "Sun" },
planets: planetsMap,
houses: housesMap,
dasha: {
mahadashas: [
{ lord: "Moon", start: "2019-01-01", end: "2029-01-01" },
{ lord: "Mars", start: "2029-01-01", end: "2036-01-01" },
],
},
modules: {
varga_full: {
D9_Navamsa: {
_meta: { div: 9 },
Ascendant: { sign: "Leo", sign_idx: 4 },
...d9Planets,
_dignity: {},
},
D10_Dasamsa: {
_meta: { div: 10 },
Ascendant: { sign: "Taurus", sign_idx: 1 },
...d10Planets,
},
},
narayana_dasha: {
periods: [{ lord: "Sun", start: "2023-01-01", end: "2026-01-01" }],
},
},
},
consumer_context: {
route: "general",
core_status: "ready",
available_layers: ["D1", "D9", "D10", "Vimshottari", "Narayana"],
missing_route_layers: [],
hard_blockers: [],
answer_policy: {
can_answer_precise_timing: true,
deterministic_claims_forbidden_for: [],
},
},
machine_evidence_packet: {
conflicts: [
{ techniques: ["Vimshottari", "Narayana"], summary: "两个大运系统给出的阶段边界不一致" },
],
sections: {
D1: { status: "used", source_path: "chart.planets+chart.ascendant" },
D9: { status: "used", source_path: "modules.varga_full.D9" },
D10: { status: "used", source_path: "modules.varga_full.D10" },
D2: { status: "missing", source_path: "modules.varga_full.D2" },
planet_degrees: { status: "used", source_path: "chart.planets" },
house_degrees: { status: "used", source_path: "chart.houses" },
dasha_boundaries: { status: "used", source_path: "modules.dasha" },
narayana_dasha: { status: "used", source_path: "modules.narayana_dasha" },
external_oracle_status: { status: "used", source_path: "vedastro_official.runtime_truth" },
vedastro_official_raw_response: { status: "missing", source_path: "vedastro_official.raw_response" },
},
},
};
}
function buildPacket(overrides: Partial<BuildPacketOverrides> = {}): ReportEvidencePacket {
const workflow = overrides.workflow ?? pythonStyleChartPayload();
return buildReportEvidencePacket({
workflow,
subject: {
displayName: "测试用户",
birthTimeStatus: "confirmed",
birthPlaceLabel: "北京",
},
requestedThemes: ["career", "marriage", "wealth", "timing"],
reportType: "personal_full",
presentationMode: "default",
candidateRange: null,
skillSnapshot: {
name: "jyotish-vedic-astrology",
version: "6.9.14",
sha256: "a".repeat(64),
sourceCommit: "b".repeat(40),
},
});
}
type BuildPacketOverrides = {
workflow: unknown;
};
function agentOutput(overrides: Partial<PersonalReportAgentOutput> = {}): PersonalReportAgentOutput {
return {
executiveSummary: {
headline: "综合盘面以事业发展为主线",
summary: "命盘显示事业层面具备稳定的结构,财富与婚恋需结合分盘审慎解读。",
priorities: ["先聚焦职业方向", "再核对感情与财富主题"],
},
thematicNarrative: [
{
id: "career",
theme: "career",
title: "事业",
narrative: "事业层面以十宫与 D10 结构为主,方向性判断稳定,具体应期需要结合大运边界观察。",
actions: ["在稳定领域深耕"],
caveats: ["该部分为方向性描述"],
claimStatus: "single_system_inference",
evidenceRefs: ["ev-audit-1", "ev-audit-3"],
},
{
id: "marriage",
theme: "marriage",
title: "婚恋",
narrative: "婚恋部分以七宫与 D9 为主,呈现结构特征,不构成确定性结论。",
actions: [],
caveats: [],
claimStatus: "single_system_inference",
evidenceRefs: ["ev-audit-2"],
},
],
...overrides,
};
}
// ---------------------------------------------------------------------------
// Packet builder: allowlist + fail-closed
// ---------------------------------------------------------------------------
test("packet builder extracts only allowlisted facts, never internal noise", () => {
const workflow = pythonStyleChartPayload();
(workflow as Record<string, unknown>).internal_path = "/opt/app/private/engine.py";
(workflow as Record<string, unknown>).prompt_text = "系统提示词原文";
(workflow as Record<string, unknown>).traceback = "Traceback (most recent call last)";
(workflow as Record<string, unknown>).raw_payload = { anything: true };
const packet = buildPacket({ workflow });
assert.equal(packet.skillName, "jyotish-vedic-astrology");
assert.equal(packet.skillVersion, "6.9.14");
assert.equal(packet.skillSnapshotSha256, "a".repeat(64));
assert.equal(packet.skillSourceCommit, "b".repeat(40));
const serialized = JSON.stringify(packet);
assert.doesNotMatch(serialized, /internal_path|prompt_text|traceback|raw_payload|\/opt\/app/);
assert.match(serialized, /ev-audit-/);
assert.match(serialized, /ev-conflict-/);
});
test("packet builder computes a canonical 64-hex calculation hash and marks derivation", () => {
const packet = buildPacket();
assert.match(packet.chart.calculationHash, /^[0-9a-f]{64}$/);
assert.equal(packet.chart.calculationHashDerived, true);
});
test("packet builder keeps a real engine hash when present", () => {
const workflow = pythonStyleChartPayload() as Record<string, unknown>;
const chart = workflow.chart as Record<string, unknown>;
chart.result_hash = "c".repeat(64);
const packet = buildPacket({ workflow });
assert.equal(packet.chart.calculationHash, "c".repeat(64));
assert.equal(packet.chart.calculationHashDerived, false);
});
test("packet builder fails closed without real D1 houses", () => {
const workflow = pythonStyleChartPayload() as Record<string, unknown>;
const chart = workflow.chart as Record<string, unknown>;
const houses = chart.houses as Record<string, unknown>;
delete houses["house_9"];
delete houses["house_10"];
delete houses["house_11"];
delete houses["house_12"];
assert.throws(() => buildPacket({ workflow }), ReportEvidenceInsufficientError);
});
test("packet builder fails closed without retrograde facts", () => {
const workflow = pythonStyleChartPayload() as Record<string, unknown>;
const chart = workflow.chart as Record<string, unknown>;
const planets = chart.planets as Record<string, Record<string, unknown>>;
planets.Sun.retrograde = undefined;
assert.throws(() => buildPacket({ workflow }), ReportEvidenceInsufficientError);
});
test("packet builder fails closed without ascendant or evidence refs", () => {
const workflow = pythonStyleChartPayload() as Record<string, unknown>;
const chart = workflow.chart as Record<string, unknown>;
delete chart.ascendant;
assert.throws(() => buildPacket({ workflow }), ReportEvidenceInsufficientError);
const workflow2 = pythonStyleChartPayload() as Record<string, unknown>;
const consumer = workflow2.consumer_context as Record<string, unknown>;
consumer.available_layers = [];
const machine = workflow2.machine_evidence_packet as Record<string, unknown>;
machine.sections = {};
machine.conflicts = [];
assert.throws(() => buildPacket({ workflow: workflow2 }), ReportEvidenceInsufficientError);
});
test("no mock fallback: an empty workflow never yields a usable packet", () => {
assert.throws(() => buildPacket({ workflow: {} }), ReportEvidenceInsufficientError);
});
test("packet builder normalizes the real Python object-map shapes", () => {
const packet = buildPacket();
// planets object map -> facts, absolute longitude from degree/degree_raw.
assert.equal(packet.chart.planets.length, 9);
const sun = packet.chart.planets.find((planet) => planet.id === "Sun");
assert.ok(sun);
assert.equal(sun.sign, "Leo");
assert.equal(sun.degree, 142.5);
assert.equal(sun.house, 1);
assert.equal(sun.retrograde, false);
assert.equal(packet.chart.planets.find((planet) => planet.id === "Saturn")?.retrograde, true);
// houses object map (cusp_sign only) -> whole-sign derived signs, marked.
assert.equal(packet.chart.houses.length, 12);
assert.equal(packet.chart.houses[0].sign, "Leo");
assert.equal(packet.chart.houses[0].signDerived, true);
assert.deepEqual(packet.chart.houses[0].occupants, ["Sun"]);
assert.equal(packet.chart.houses[1].sign, "Virgo");
assert.deepEqual([...packet.chart.houses[1].occupants].sort(), ["Ketu", "Moon"]);
// sections object map -> deterministic statuses: core calculation sections
// verified, internal layers partial, external/missing degraded.
const refs = packet.evidenceRefs;
const verified = refs.filter((ref) => ref.status === "verified").map((ref) => ref.technique);
assert.ok(verified.includes("planet_degrees"));
assert.ok(verified.includes("house_degrees"));
const blocked = refs.filter((ref) => ref.status === "blocked").map((ref) => ref.technique);
assert.ok(blocked.includes("D2"));
assert.ok(blocked.includes("vedastro_official_raw_response"));
const partial = refs.filter((ref) => ref.status === "partial").map((ref) => ref.technique);
assert.ok(partial.includes("dasha_boundaries"));
assert.ok(partial.includes("external_oracle_status"));
// conflicts produce canonical ev-conflict refs.
assert.ok(refs.some((ref) => ref.id.startsWith("ev-conflict-")));
});
test("packet builder resolves the base chart from modules.chart and nested chart", () => {
const base = pythonStyleChartPayload() as Record<string, unknown>;
const chartData = base.chart as Record<string, unknown>;
// modules.chart wins over nested chart over top level.
const modulesChart = {
...chartData,
planets: { Sun: { sign: "Aries", degree: 10.5, degree_raw: 10.5, house: 1, retrograde: false } },
};
const modules = chartData.modules as Record<string, unknown>;
modules.chart = modulesChart;
const viaModules = buildPacket({ workflow: base });
assert.equal(viaModules.chart.planets.length, 1);
assert.equal(viaModules.chart.planets[0].sign, "Aries");
delete modules.chart;
// nested chart_data.chart is the orchestrator's second choice.
const nested = {
...chartData,
planets: { Moon: { sign: "Pisces", degree: 350.5, degree_raw: 350.5, house: 12, retrograde: false } },
};
chartData.chart = nested;
const viaNested = buildPacket({ workflow: base });
assert.equal(viaNested.chart.planets.length, 1);
assert.equal(viaNested.chart.planets[0].id, "Moon");
});
test("varga houses are whole-sign derived from the divisional ascendant, never fabricated", () => {
const packet = buildPacket();
const d9 = packet.chart.vargaHouses.find((varga) => varga.id === "D9");
assert.ok(d9);
assert.equal(d9.houses.length, 12);
// D9 ascendant is Leo (index 4): house 1 Leo, house 2 Virgo.
assert.equal(d9.houses[0].sign, "Leo");
assert.equal(d9.houses[1].sign, "Virgo");
// Moon sits in Virgo (index 5) -> whole-sign house 2 of D9.
assert.ok(d9.houses[1].occupants.includes("Moon"));
assert.ok(d9.houses.every((house) => house.signDerived === true));
const d10 = packet.chart.vargaHouses.find((varga) => varga.id === "D10");
assert.ok(d10);
// D10 ascendant is Taurus (index 1): house 1 Taurus, house 2 Gemini.
assert.equal(d10.houses[0].sign, "Taurus");
assert.equal(d10.houses[1].sign, "Gemini");
assert.ok(d10.houses[1].occupants.includes("Moon"));
});
test("array-shaped chart data remains supported", () => {
const workflow = {
success: true,
chart: {
ascendant: { sign: "Leo", degree: 12.5 },
planets: [
{ id: "Sun", sign: "Leo", degree: 142.5, house: 1, retrograde: false },
{ id: "Moon", sign: "Virgo", degree: 172.5, house: 2, retrograde: false },
],
houses: Array.from({ length: 12 }, (_, index) => ({
number: index + 1,
sign: ["Aries", "Taurus", "Gemini", "Cancer", "Leo", "Virgo", "Libra", "Scorpio", "Sagittarius", "Capricorn", "Aquarius", "Pisces"][index],
})),
},
consumer_context: {
route: "general",
core_status: "ready",
available_layers: ["D1"],
missing_route_layers: [],
hard_blockers: [],
answer_policy: { can_answer_precise_timing: true, deterministic_claims_forbidden_for: [] },
},
machine_evidence_packet: {
conflicts: [],
sections: [{ name: "planet_degrees", status: "verified", note: "" }],
},
};
const packet = buildPacket({ workflow });
assert.equal(packet.chart.planets.length, 2);
assert.equal(packet.chart.houses.length, 12);
// Array houses carry a real sign: not derived.
assert.equal(packet.chart.houses[0].signDerived, false);
assert.equal(packet.chart.houses[0].sign, "Aries");
assert.ok(packet.evidenceRefs.some((ref) => ref.status === "verified"));
});
test("section status mapping is deterministic (used core sections verified, external degraded)", () => {
const packet = buildPacket();
const byTechnique = new Map(packet.evidenceRefs.map((ref) => [ref.technique, ref.status]));
assert.equal(byTechnique.get("D1"), "verified");
assert.equal(byTechnique.get("planet_degrees"), "verified");
assert.equal(byTechnique.get("house_degrees"), "verified");
assert.equal(byTechnique.get("dasha_boundaries"), "partial");
assert.equal(byTechnique.get("external_oracle_status"), "partial");
assert.equal(byTechnique.get("D2"), "blocked");
assert.equal(byTechnique.get("vedastro_official_raw_response"), "blocked");
});
// ---------------------------------------------------------------------------
// Fingerprint
// ---------------------------------------------------------------------------
test("request fingerprint is canonical: sorted, deduped themes, no requestId", () => {
const base = {
reportType: "personal_full",
presentationMode: "default",
depth: "standard",
themes: ["wealth", "career", "wealth", "timing"],
sessionId: null,
chartProfileId: null,
};
const fingerprintA = computeRequestFingerprint(base);
const fingerprintB = computeRequestFingerprint({
reportType: "personal_full",
presentationMode: "default",
depth: "standard",
themes: ["career", "timing", "wealth"],
sessionId: null,
chartProfileId: null,
});
assert.equal(fingerprintA, fingerprintB);
assert.equal(fingerprintA, sha256Hex(canonicalSerialize({
reportType: "personal_full",
presentationMode: "default",
depth: "standard",
themes: ["career", "timing", "wealth"],
sessionId: null,
chartProfileId: null,
})));
const differentType = computeRequestFingerprint({
...base,
reportType: "personal_thematic",
});
assert.notEqual(fingerprintA, differentType);
const withSession = computeRequestFingerprint({
...base,
sessionId: "11111111-1111-4111-8111-111111111111",
});
assert.notEqual(fingerprintA, withSession);
});
// ---------------------------------------------------------------------------
// Assembly: canonical contract shape
// ---------------------------------------------------------------------------
test("assembled document passes the canonical server parse with a D1 of 12 houses", () => {
const packet = buildPacket();
const document = assembleReportDocument({
reportId: "22222222-2222-4222-8222-222222222222",
generatedAt: "2026-08-06T00:00:00.000Z",
packet,
agentOutput: agentOutput(),
});
const parsed = safeParseServerReportDocument(document);
assert.equal(parsed.ok, true);
if (!parsed.ok) return;
assert.equal(parsed.document.charts.length, 3);
assert.equal(parsed.document.provenance.skillName, "jyotish-vedic-astrology");
assert.equal(parsed.document.provenance.skillVersion, "6.9.14");
assert.equal(parsed.document.provenance.skillSnapshotSha256, "a".repeat(64));
assert.equal(parsed.document.provenance.skillSourceCommit, "b".repeat(40));
const d1 = parsed.document.charts.find((chart) => chart.id === "D1");
assert.ok(d1);
assert.equal(d1.houses.length, 12);
assert.deepEqual(
d1.houses.map((house) => house.houseNumber).sort((a, b) => a - b),
Array.from({ length: 12 }, (_, index) => index + 1),
);
assert.ok(d1.planets && d1.planets.length === 9);
assert.equal(d1.planets[0].retrograde, false);
assert.equal(d1.planets[6].retrograde, true);
// Appendix ids are canonical ev- ids and globally unique.
const ids = [
...parsed.document.evidenceAppendix.techniqueAudit.map((row) => row.id),
...parsed.document.evidenceAppendix.conflicts.map((row) => row.id),
...parsed.document.evidenceAppendix.calculationEvidence.map((row) => row.id),
];
assert.equal(new Set(ids).size, ids.length);
assert.ok(ids.every((id) => /^ev-[a-z0-9_-]{1,63}$/.test(id)));
// evidenceHash matches the canonical recomputation from the appendix.
assert.equal(
parsed.document.provenance.evidenceHash,
computeEvidenceHash(parsed.document.evidenceAppendix),
);
});
test("technique audit reflects final evidence status and narrative usage", () => {
const workflow = pythonStyleChartPayload() as Record<string, unknown>;
const machinePacket = workflow.machine_evidence_packet as Record<string, unknown>;
const sections = machinePacket.sections as Record<string, Record<string, unknown>>;
sections.external_oracle_status.status = "official_blocked";
const packet = buildPacket({ workflow });
assert.deepEqual(packet.blockedTechniques, []);
const output = agentOutput();
const blockedRef = packet.evidenceRefs.find((ref) => ref.technique === "D2");
assert.ok(blockedRef);
output.thematicNarrative[0].evidenceRefs.push(blockedRef.id);
const document = assembleReportDocument({
reportId: "22222222-2222-4222-8222-222222222222",
generatedAt: "2026-08-06T00:00:00.000Z",
packet,
agentOutput: output,
});
const auditById = new Map(document.evidenceAppendix.techniqueAudit.map((row) => [row.id, row]));
const referencedIds = new Set(document.thematicNarrative.flatMap((section) => section.evidenceRefs));
assert.ok([...referencedIds].every((id) => auditById.get(id)?.used === true));
assert.equal(document.evidenceAppendix.techniqueAudit.find((row) => row.techniqueName === "D1")?.status, "verified");
assert.equal(document.evidenceAppendix.techniqueAudit.find((row) => row.techniqueName === "D2")?.status, "blocked");
assert.match(
document.evidenceAppendix.techniqueAudit.find((row) => row.techniqueName === "D2")?.notes ?? "",
/未返回本次计算数据/,
);
const external = document.evidenceAppendix.techniqueAudit.find(
(row) => row.techniqueName === "external_oracle_status",
);
assert.equal(external?.status, "blocked");
assert.match(external?.notes ?? "", /官方外部引擎本次未验证或不可用/);
});
test("D9/D10 charts are omitted when no real divisional houses exist", () => {
const workflow = pythonStyleChartPayload() as Record<string, unknown>;
const chart = workflow.chart as Record<string, unknown>;
const modules = chart.modules as Record<string, unknown>;
modules.varga_full = {};
const packet = buildPacket({ workflow });
const document = assembleReportDocument({
reportId: "22222222-2222-4222-8222-222222222222",
generatedAt: "2026-08-06T00:00:00.000Z",
packet,
agentOutput: agentOutput(),
});
const parsed = safeParseServerReportDocument(document);
assert.equal(parsed.ok, true);
if (!parsed.ok) return;
assert.deepEqual(parsed.document.charts.map((chartRow) => chartRow.id), ["D1"]);
});
// ---------------------------------------------------------------------------
// Deterministic guard
// ---------------------------------------------------------------------------
test("guard rejects dangling evidence refs", () => {
const packet = buildPacket();
const document = assembleReportDocument({
reportId: "22222222-2222-4222-8222-222222222222",
generatedAt: "2026-08-06T00:00:00.000Z",
packet,
agentOutput: agentOutput({
thematicNarrative: [
{
id: "career",
theme: "career",
title: "事业",
narrative: "稳定结构。",
actions: [],
caveats: [],
claimStatus: "single_system_inference",
evidenceRefs: ["ev-audit-999"],
},
],
}),
});
const guarded = applyReportGuard(document, packet);
assert.equal(guarded.ok, false);
if (!guarded.ok) assert.match(guarded.reason, /unresolved_evidence_ref/);
});
test("guard redacts precise timing and downgrades the section when timing is blocked", () => {
const packet = buildPacket();
const timingBlockedPacket = { ...packet, answerPolicy: { ...packet.answerPolicy, canAnswerPreciseTiming: false } };
const document = assembleReportDocument({
reportId: "22222222-2222-4222-8222-222222222222",
generatedAt: "2026-08-06T00:00:00.000Z",
packet: timingBlockedPacket,
agentOutput: agentOutput({
thematicNarrative: [
{
id: "career",
theme: "career",
title: "事业",
narrative: "方向稳定。2027年3月将迎来事业转折,届时务必把握机会。",
actions: ["2027年3月跳槽"],
caveats: [],
claimStatus: "single_system_inference",
evidenceRefs: ["ev-audit-1"],
},
],
}),
});
const guarded = applyReportGuard(document, timingBlockedPacket);
assert.equal(guarded.ok, true);
if (!guarded.ok) return;
const section = (guarded.document as unknown as { thematicNarrative: { narrative: string; claimStatus: string; caveats: string[] }[] })
.thematicNarrative[0];
assert.doesNotMatch(section.narrative, /2027年3月/);
assert.equal(section.claimStatus, "blocked");
assert.ok(section.caveats.some((caveat) => caveat.includes("确定性边界")));
});
test("guard rejects medical deterministic claims outright", () => {
const packet = buildPacket();
const document = assembleReportDocument({
reportId: "22222222-2222-4222-8222-222222222222",
generatedAt: "2026-08-06T00:00:00.000Z",
packet,
agentOutput: agentOutput({
thematicNarrative: [
{
id: "career",
theme: "career",
title: "事业",
narrative: "你一定会患上心脏病。",
actions: [],
caveats: [],
claimStatus: "single_system_inference",
evidenceRefs: ["ev-audit-1"],
},
],
}),
});
const guarded = applyReportGuard(document, packet);
assert.equal(guarded.ok, false);
if (!guarded.ok) assert.match(guarded.reason, /deterministic_medical_claim/);
});
test("guard rejects investment deterministic claims hidden in actions", () => {
const packet = buildPacket();
const document = assembleReportDocument({
reportId: "22222222-2222-4222-8222-222222222222",
generatedAt: "2026-08-06T00:00:00.000Z",
packet,
agentOutput: agentOutput({
thematicNarrative: [
{
id: "wealth",
theme: "wealth",
title: "财富",
narrative: "财富结构稳定。",
actions: ["买入股票必然大涨"],
caveats: [],
claimStatus: "single_system_inference",
evidenceRefs: ["ev-audit-1"],
},
],
}),
});
const guarded = applyReportGuard(document, packet);
assert.equal(guarded.ok, false);
if (!guarded.ok) assert.match(guarded.reason, /deterministic_investment_claim/);
});
test("guard forces blocked when every evidence ref is blocked", () => {
const workflow = pythonStyleChartPayload() as Record<string, unknown>;
const consumer = workflow.consumer_context as Record<string, unknown>;
consumer.hard_blockers = ["Narayana"];
consumer.available_layers = ["D1", "Vimshottari"];
const packet = buildPacket({ workflow });
const blockedRef = packet.evidenceRefs.find((ref) => ref.status === "blocked");
assert.ok(blockedRef);
const document = assembleReportDocument({
reportId: "22222222-2222-4222-8222-222222222222",
generatedAt: "2026-08-06T00:00:00.000Z",
packet,
agentOutput: agentOutput({
thematicNarrative: [
{
id: "timing",
theme: "timing",
title: "时机",
narrative: "该部分仅保留方向性说明。",
actions: [],
caveats: [],
claimStatus: "single_system_inference",
evidenceRefs: [blockedRef.id],
},
],
}),
});
const guarded = applyReportGuard(document, packet);
assert.equal(guarded.ok, true);
if (!guarded.ok) return;
const section = (guarded.document as unknown as { thematicNarrative: { id: string; claimStatus: string }[] })
.thematicNarrative[0];
assert.equal(section.claimStatus, "blocked");
});
test("guard redacts timing from the summary and blocks the report-level status", () => {
const packet = buildPacket();
const timingBlockedPacket = { ...packet, answerPolicy: { ...packet.answerPolicy, canAnswerPreciseTiming: false } };
const document = assembleReportDocument({
reportId: "22222222-2222-4222-8222-222222222222",
generatedAt: "2026-08-06T00:00:00.000Z",
packet: timingBlockedPacket,
agentOutput: agentOutput({
executiveSummary: {
headline: "综合盘面以事业发展为主线",
summary: "明年3月将迎来关键转折,整体结构稳定。",
priorities: ["先聚焦职业方向"],
},
}),
});
const guarded = applyReportGuard(document, timingBlockedPacket);
assert.equal(guarded.ok, true);
if (!guarded.ok) return;
const summary = (guarded.document as unknown as { executiveSummary: { summary: string; overallClaimStatus: string } })
.executiveSummary;
assert.doesNotMatch(summary.summary, /明年3月/);
assert.equal(summary.overallClaimStatus, "blocked");
});
test("findForbiddenDeterministicClaims and redaction are deterministic", () => {
assert.ok(findForbiddenDeterministicClaims("2027年3月会发生转折").some((claim) => claim.domain === "timing"));
assert.ok(findForbiddenDeterministicClaims("投资必然赚钱").some((claim) => claim.domain === "investment"));
assert.equal(findForbiddenDeterministicClaims("方向性判断稳定").length, 0);
const redacted = redactDeterministicSentences("方向稳定。2027年3月转折。", PRECISE_TIMING_PATTERNS);
assert.equal(redacted.removedCount, 1);
assert.doesNotMatch(redacted.text, /2027年3月/);
});
// ---------------------------------------------------------------------------
// Generation pipeline (fake agent, real canonical parse)
// ---------------------------------------------------------------------------
function fakeAgent(output: PersonalReportAgentOutput, calls: { count: number }): ReportAgentPort {
return {
modelId: "test-model",
async generate() {
calls.count += 1;
return output;
},
};
}
test("generatePersonalReport returns a ready document that passes the server parse", async () => {
const bundle = bundleV2Fixture();
const calls = { count: 0 };
const result = await generatePersonalReport({
reportId: "22222222-2222-4222-8222-222222222222",
bundle,
depth: "standard",
agent: fakeAgent(agentOutput({
thematicNarrative: [{
id: "theme-career",
theme: "career",
title: "事业",
narrative: "事业主题存在可审慎表达的结构性线索。",
actions: [],
caveats: [],
claimStatus: "multi_system_consensus",
evidenceRefs: ["ev-tech-d1", "ev-tech-d10"],
}],
}), calls),
now: () => new Date("2026-08-06T00:00:00.000Z"),
});
assert.equal(calls.count, 1);
assert.equal(result.status, "ready");
if (result.status !== "ready") return;
assert.match(result.evidenceHash, /^[0-9a-f]{64}$/);
const reparsed = safeParseServerReportDocument(result.document);
assert.equal(reparsed.ok, true);
});
test("generatePersonalReport fails with report_guard_rejected on guard rejection", async () => {
const bundle = bundleV2Fixture();
const result = await generatePersonalReport({
reportId: "22222222-2222-4222-8222-222222222222",
bundle,
depth: "standard",
agent: fakeAgent(agentOutput({
thematicNarrative: [
{
id: "theme-career",
theme: "career",
title: "事业",
narrative: "你必定会胜诉。",
actions: [],
caveats: [],
claimStatus: "multi_system_consensus",
evidenceRefs: ["ev-tech-d1", "ev-tech-d10"],
},
],
}), { count: 0 }),
});
assert.deepEqual(result, { status: "failed", failureCode: "report_guard_rejected" });
});
test("generatePersonalReport fails with report_schema_invalid when the writer emits an unplanned theme", async () => {
const bundle = bundleV2Fixture();
const blockedRef = bundle.evidenceRefs.find((ref) => ref.status === "blocked");
assert.ok(blockedRef);
const result = await generatePersonalReport({
reportId: "22222222-2222-4222-8222-222222222222",
bundle,
depth: "standard",
agent: fakeAgent(agentOutput({
thematicNarrative: [
{
id: "timing",
theme: "timing",
title: "时机",
narrative: "该部分必然会成功,结构稳定。",
actions: [],
caveats: [],
claimStatus: "single_system_inference",
evidenceRefs: [blockedRef.id],
},
],
}), { count: 0 }),
now: () => new Date("2026-08-06T00:00:00.000Z"),
});
assert.equal(result.status, "failed");
if (result.status === "failed" && result.failureCode === "report_schema_invalid") {
assert.equal(result.innerReason, "report_writer_unplanned_theme:timing");
}
});
test("skill snapshot is the real packaged report manifest sha256, never the literal unknown", async () => {
const snapshot: SkillSnapshot = resolveSkillSnapshot();
assert.equal(snapshot.name, "jyotish-personal-report");
// was "1.0.0"; bumped 2026-09-01 when the skill contract gained the
// interpretive fact layer and the static interpretation packs.
assert.equal(snapshot.version, "1.1.0");
assert.match(snapshot.sha256, /^[0-9a-f]{64}$/);
assert.notEqual(snapshot.sha256, "unknown");
const again: SkillSnapshot = resolveSkillSnapshot();
assert.equal(snapshot.sha256, again.sha256);
});
test("stable codes include the request-conflict mapping", () => {
assert.equal(REPORT_STABLE_CODES.requestConflict, "report_request_conflict");
});
// ---------------------------------------------------------------------------
// ReportEvidenceBundle v2 contract: hash, coverage and closed references
// ---------------------------------------------------------------------------
function bundleV2Fixture(): ReportEvidenceBundleV2 {
return finalizeReportEvidenceBundleV2({
schemaVersion: "report_evidence_bundle.v2",
// v2 bundle gained the server-owned interpretive layer (2026-09-01);
// fixtures that carry no engine snapshot declare it empty.
interpretiveFacts: emptyReportInterpretiveFacts(),
themeNarrativeSeeds: [],
subject: {
displayName: "测试用户",
birthTimeStatus: "accepted",
birthPlaceLabel: "北京",
},
requestedThemes: ["career", "wealth"],
reportType: "personal_full",
presentationMode: "default",
calculationProfile: {
calculationHash: "c".repeat(64),
calculationHashDerived: false,
birthTimeStatus: "accepted",
ayanamsa: "Lahiri",
nodeMode: "true",
houseSystem: "whole_sign",
vimshottari: [{ lord: "Moon", start: "2019-01-01", end: "2029-01-01" }],
narayana: [{ lord: "Sun", start: "2023-01-01", end: "2026-01-01" }],
},
skill: {
name: "jyotish-personal-report",
version: "1.0.0",
sha256: "a".repeat(64),
sourceCommit: "b".repeat(40),
},
charts: [
{
id: "D1",
title: "D1 Rasi",
ascendant: { sign: "Aries", degree: 12.5 },
houses: Array.from({ length: 12 }, (_, index) => ({
number: index + 1,
sign: ["Aries", "Taurus", "Gemini", "Cancer", "Leo", "Virgo", "Libra", "Scorpio", "Sagittarius", "Capricorn", "Aquarius", "Pisces"][index],
signDerived: false,
occupants: index === 0 ? ["Sun", "Mars"] : [],
})),
planets: [
{ id: "Sun", sign: "Aries", degree: 12.5, house: 1, retrograde: false },
{ id: "Mars", sign: "Aries", degree: 18.25, house: 1, retrograde: false },
],
},
{
id: "D10",
title: "D10 Dasamsa",
ascendant: { sign: "Capricorn", degree: 4.5 },
houses: Array.from({ length: 12 }, (_, index) => ({
number: index + 1,
sign: ["Capricorn", "Aquarius", "Pisces", "Aries", "Taurus", "Gemini", "Cancer", "Leo", "Virgo", "Libra", "Scorpio", "Sagittarius"][index],
signDerived: false,
occupants: [],
})),
planets: [],
},
],
claimCards: [{
id: "ev-claim-career",
theme: "career",
section: "事业与方向",
conclusion: "事业主题存在可审慎陈述的结构性线索。",
supportingFacts: [
{
id: "ev-fact-career-d1",
label: "D1 十宫结构",
value: "十宫结构已由正式计算结果投影。",
evidenceRef: "ev-tech-d1",
status: "verified",
},
{
id: "ev-fact-career-d10",
label: "D10 事业分盘",
value: "D10 已由正式计算结果投影。",
evidenceRef: "ev-tech-d10",
status: "verified",
},
],
counterFacts: [],
executedTechniqueRefs: ["ev-tech-d1", "ev-tech-d10"],
assertionLevel: "multi_system_consensus",
timingBoundary: null,
verificationQuestions: [],
}],
blockedSections: [{
id: "ev-blocked-wealth",
theme: "wealth",
section: "财富结构",
reason: "缺少 D2 与 D11 正式分盘,不能形成财富结论。",
missingTechniqueRefs: ["ev-tech-d2", "ev-tech-d11"],
}],
conflicts: [],
executionLedger: [
{ id: "ev-tech-d1", technique: "D1", status: "verified", executed: true, note: "基础盘已执行" },
{ id: "ev-tech-d10", technique: "D10", status: "verified", executed: true, note: "事业分盘已执行" },
{ id: "ev-tech-d2", technique: "D2", status: "blocked", executed: false, note: "未提供" },
{ id: "ev-tech-d11", technique: "D11", status: "blocked", executed: false, note: "未提供" },
],
evidenceRefs: [
{ id: "ev-tech-d1", technique: "D1", status: "verified" },
{ id: "ev-tech-d10", technique: "D10", status: "verified" },
{ id: "ev-tech-d2", technique: "D2", status: "blocked" },
{ id: "ev-tech-d11", technique: "D11", status: "blocked" },
],
answerPolicy: {
canAnswerPreciseTiming: false,
birthTimePolicy: "accepted_directional_only",
deterministicClaimsForbiddenFor: ["timing", "medical", "investment"],
},
});
}
function withoutBundleHash(bundle: ReportEvidenceBundleV2): Omit<ReportEvidenceBundleV2, "bundleHash"> {
const { bundleHash, ...content } = bundle;
void bundleHash;
return content;
}
test("bundle v2 covers every requested theme with a claim card or blocked section", () => {
const bundle = bundleV2Fixture();
const coveredThemes = new Set([
...bundle.claimCards.map((card) => card.theme),
...bundle.blockedSections.map((section) => section.theme),
]);
assert.deepEqual([...coveredThemes].sort(), [...bundle.requestedThemes].sort());
assert.deepEqual(validateReportEvidenceBundleV2(bundle), bundle);
});
test("bundle v2 accepted birth time has an explicit directional policy and no fake candidateRange", () => {
const bundle = bundleV2Fixture();
assert.equal(bundle.subject.birthTimeStatus, "accepted");
assert.equal(bundle.calculationProfile.birthTimeStatus, "accepted");
assert.equal(bundle.answerPolicy.birthTimePolicy, "accepted_directional_only");
assert.equal(bundle.answerPolicy.canAnswerPreciseTiming, false);
assert.equal("candidateRange" in bundle, false);
assert.doesNotMatch(JSON.stringify(bundle), /candidateRange/);
});
test("bundle v2 schema rejects opaque techniques, calculation labels and fake dasha coordinates", () => {
const bundle = bundleV2Fixture();
const content = withoutBundleHash(bundle);
assert.throws(() => finalizeReportEvidenceBundleV2({
...content,
executionLedger: content.executionLedger.map((receipt, index) => (
index === 0 ? { ...receipt, technique: "AKIAIOSFODNN7EXAMPLE" } : receipt
)),
evidenceRefs: content.evidenceRefs.map((ref, index) => (
index === 0 ? { ...ref, technique: "AKIAIOSFODNN7EXAMPLE" } : ref
)),
}), /report_bundle_technique_not_allowlisted/);
assert.throws(() => finalizeReportEvidenceBundleV2({
...content,
calculationProfile: {
...content.calculationProfile,
nodeMode: "prod-db-01",
} as unknown as ReportEvidenceBundleV2["calculationProfile"],
}), /Invalid enum value/);
assert.throws(() => finalizeReportEvidenceBundleV2({
...content,
calculationProfile: {
...content.calculationProfile,
vimshottari: [{ lord: "Moon", start: "39.9000", end: "116.4000" }],
},
}), /report_bundle_dasha_date_invalid/);
});
test("bundle v2 validator rejects direct free-text policy and conflict injection before Agent use", () => {
const bundle = bundleV2Fixture();
const content = withoutBundleHash(bundle);
const forgedConflictContent = {
...content,
conflicts: [{
id: "ev-conflict-1",
techniqueRefs: ["ev-tech-d1"],
summary: "Authorization: Bearer secret-token at /Users/private/trace.py",
resolutionStatus: "unresolved",
}],
} as unknown as Omit<ReportEvidenceBundleV2, "bundleHash">;
assert.throws(() => validateReportEvidenceBundleV2({
...forgedConflictContent,
bundleHash: computeReportEvidenceBundleHash(forgedConflictContent),
}), /Invalid enum value/);
const forgedPolicyContent = {
...content,
answerPolicy: {
...content.answerPolicy,
deterministicClaimsForbiddenFor: [
"timing",
"AKIAIOSFODNN7EXAMPLE",
"prod-db-01",
"internal-host-22",
],
},
} as unknown as Omit<ReportEvidenceBundleV2, "bundleHash">;
assert.throws(() => validateReportEvidenceBundleV2({
...forgedPolicyContent,
bundleHash: computeReportEvidenceBundleHash(forgedPolicyContent),
}), /Invalid enum value/);
});
test("bundle v2 claim technique and fact references are closed", () => {
const bundle = bundleV2Fixture();
const ledgerIds = new Set(bundle.executionLedger.map((receipt) => receipt.id));
const evidenceIds = new Set(bundle.evidenceRefs.map((ref) => ref.id));
for (const card of bundle.claimCards) {
for (const ref of card.executedTechniqueRefs) assert.equal(ledgerIds.has(ref), true);
for (const fact of [...card.supportingFacts, ...card.counterFacts]) {
assert.equal(evidenceIds.has(fact.evidenceRef), true);
}
}
const content = withoutBundleHash(bundle);
assert.throws(() => finalizeReportEvidenceBundleV2({
...content,
executionLedger: content.executionLedger.filter((receipt) => receipt.id !== "ev-tech-d10"),
}), /report_bundle_invalid_technique_ref:ev-tech-d10/);
assert.throws(() => finalizeReportEvidenceBundleV2({
...content,
claimCards: content.claimCards.map((card) => ({
...card,
supportingFacts: card.supportingFacts.map((fact) => ({
...fact,
evidenceRef: "ev-unknown-fact-ref",
})),
})),
}), /report_bundle_invalid_fact_ref:ev-unknown-fact-ref/);
});
test("bundle v2 blocked or partial techniques cannot support multi-system consensus", () => {
const bundle = bundleV2Fixture();
const content = withoutBundleHash(bundle);
assert.throws(() => finalizeReportEvidenceBundleV2({
...content,
executionLedger: content.executionLedger.map((receipt) => (
receipt.id === "ev-tech-d10" ? { ...receipt, status: "partial" as const } : receipt
)),
evidenceRefs: content.evidenceRefs.map((ref) => (
ref.id === "ev-tech-d10" ? { ...ref, status: "partial" as const } : ref
)),
}), /report_bundle_invalid_consensus:ev-claim-career/);
assert.throws(() => finalizeReportEvidenceBundleV2({
...content,
executionLedger: content.executionLedger.map((receipt) => (
receipt.id === "ev-tech-d10"
? { ...receipt, status: "blocked" as const, executed: false }
: receipt
)),
evidenceRefs: content.evidenceRefs.map((ref) => (
ref.id === "ev-tech-d10" ? { ...ref, status: "blocked" as const } : ref
)),
}), /report_bundle_invalid_technique_ref:ev-tech-d10/);
});
test("bundle v2 hash is collection-order stable, content-sensitive and excludes bundleHash itself", () => {
const bundle = bundleV2Fixture();
const content = withoutBundleHash(bundle);
const reordered = {
...content,
requestedThemes: [...content.requestedThemes].reverse(),
calculationProfile: {
...content.calculationProfile,
vimshottari: content.calculationProfile.vimshottari
? [...content.calculationProfile.vimshottari].reverse()
: null,
narayana: content.calculationProfile.narayana
? [...content.calculationProfile.narayana].reverse()
: null,
},
charts: [...content.charts].reverse().map((chart) => ({
...chart,
houses: [...chart.houses].reverse().map((house) => ({
...house,
occupants: [...house.occupants].reverse(),
})),
planets: [...chart.planets].reverse(),
})),
claimCards: [...content.claimCards].reverse().map((card) => ({
...card,
supportingFacts: [...card.supportingFacts].reverse(),
counterFacts: [...card.counterFacts].reverse(),
executedTechniqueRefs: [...card.executedTechniqueRefs].reverse(),
verificationQuestions: [...card.verificationQuestions].reverse(),
})),
blockedSections: [...content.blockedSections].reverse().map((section) => ({
...section,
missingTechniqueRefs: [...section.missingTechniqueRefs].reverse(),
})),
conflicts: [...content.conflicts].reverse().map((conflict) => ({
...conflict,
techniqueRefs: [...conflict.techniqueRefs].reverse(),
})),
executionLedger: [...content.executionLedger].reverse(),
evidenceRefs: [...content.evidenceRefs].reverse(),
answerPolicy: {
...content.answerPolicy,
deterministicClaimsForbiddenFor: [...content.answerPolicy.deterministicClaimsForbiddenFor].reverse(),
},
};
assert.equal(computeReportEvidenceBundleHash(content), bundle.bundleHash);
assert.equal(computeReportEvidenceBundleHash(reordered), bundle.bundleHash);
const changed = {
...content,
claimCards: content.claimCards.map((card) => ({
...card,
conclusion: `${card.conclusion} 内容已改变。`,
})),
};
assert.notEqual(computeReportEvidenceBundleHash(changed), bundle.bundleHash);
const forgedHash = { ...bundle, bundleHash: "f".repeat(64) };
assert.equal(computeReportEvidenceBundleHash(withoutBundleHash(forgedHash)), bundle.bundleHash);
assert.throws(() => validateReportEvidenceBundleV2(forgedHash), /report_bundle_hash_mismatch/);
});
test("bundle v2 builder blocks incomplete wealth evidence without leaking workflow internals", () => {
const generalWorkflow = pythonStyleChartPayload() as Record<string, unknown>;
const generalChart = generalWorkflow.chart as Record<string, unknown>;
const generalPlanets = generalChart.planets as Record<string, unknown>;
generalPlanets["AuthorizationSecretPlanet"] = {
sign: "Leo",
degree: 142.5,
house: 1,
retrograde: false,
};
const generalDasha = generalChart.dasha as Record<string, unknown>;
(generalDasha.mahadashas as Record<string, unknown>[]).push(
{
lord: "/opt/private/dasha-secret",
start: "2029-01-01",
end: "2030-01-01",
},
{
lord: "Moon",
start: "39.9000",
end: "116.4000",
},
);
generalWorkflow.calculation_profile = {
ayanamsa: "/opt/private/calculation-secret",
node_mode: "prod-db-01",
house_system: "internal-host-22",
};
const wealthWorkflow = pythonStyleChartPayload() as Record<string, unknown>;
wealthWorkflow.api_key = "sk-private-report-secret";
wealthWorkflow.authorization = "Bearer private-token";
wealthWorkflow.database_url = "postgres://private-db";
wealthWorkflow.internal_path = "/Users/private/project/engine.py";
wealthWorkflow.runtime_path = "/opt/jyotisha-production/private.py";
wealthWorkflow.latitude = 39.9;
wealthWorkflow.longitude = 116.4;
wealthWorkflow.prompt = "raw hidden prompt";
wealthWorkflow.traceback = "Traceback: raw tool failure";
const wealthConsumer = wealthWorkflow.consumer_context as Record<string, unknown>;
const wealthPolicy = (wealthConsumer.answer_policy ?? {}) as Record<string, unknown>;
wealthPolicy.deterministic_claims_forbidden_for = [
"timing",
"/opt/private/policy-secret",
"secret_token",
];
wealthConsumer.answer_policy = wealthPolicy;
const wealthMachine = wealthWorkflow.machine_evidence_packet as Record<string, unknown>;
wealthMachine.conflicts = [{
techniques: ["D1"],
summary: "Authorization: Bearer secret-token at /Users/private/trace.py",
}];
const wealthSections = wealthMachine.sections as Record<string, unknown>;
wealthSections["Authorization: Bearer section-secret at /Users/private/tool.py"] = {
status: "used",
source_path: "modules.secret",
};
wealthSections.AKIAIOSFODNN7EXAMPLE = {
status: "used",
source_path: "modules.internal",
};
const bundle = buildReportEvidenceBundleV2({
workflows: [
{ theme: "general", workflow: generalWorkflow },
{ theme: "wealth", workflow: wealthWorkflow },
],
subject: {
displayName: "测试用户",
birthTimeStatus: "accepted",
birthPlaceLabel: "北京",
},
requestedThemes: ["general", "wealth"],
reportType: "personal_full",
presentationMode: "default",
skillSnapshot: {
name: "jyotish-vedic-astrology",
version: "6.9.14",
sha256: "a".repeat(64),
sourceCommit: "b".repeat(40),
},
});
assert.equal(bundle.schemaVersion, "report_evidence_bundle.v2");
assert.ok(bundle.claimCards.some((card) => card.theme === "general"));
assert.ok(bundle.blockedSections.some((section) => section.theme === "wealth"));
assert.equal("candidateRange" in bundle, false);
assert.equal(bundle.answerPolicy.birthTimePolicy, "accepted_directional_only");
assert.equal(bundle.answerPolicy.canAnswerPreciseTiming, false);
assert.equal(bundle.calculationProfile.ayanamsa, null);
assert.equal(bundle.calculationProfile.nodeMode, null);
assert.equal(bundle.calculationProfile.houseSystem, null);
assert.equal(bundle.charts.some((chart) => (
chart.planets.some((planet) => planet.id.includes("AuthorizationSecretPlanet"))
|| chart.houses.some((house) => house.occupants.includes("AuthorizationSecretPlanet"))
)), false);
assert.equal(bundle.calculationProfile.vimshottari?.some((period) => (
period.lord.includes("dasha-secret")
)), false);
assert.deepEqual(bundle.answerPolicy.deterministicClaimsForbiddenFor, ["timing"]);
assert.ok(bundle.conflicts.some((conflict) => (
conflict.techniqueRefs.length === 1
&& conflict.techniqueRefs[0] === "ev-tech-d1"
)));
assert.ok(bundle.conflicts.every((conflict) => (
conflict.summary === "所列技法的结果存在未闭合冲突,报告不得据此作确定性提升。"
&& conflict.resolutionStatus === "unresolved"
)));
assert.equal(
bundle.executionLedger.some((receipt) => receipt.technique.includes("section-secret")),
false,
);
for (const technique of ["D2", "D11"]) {
const receipts = bundle.executionLedger.filter((entry) => (
entry.technique.toUpperCase() === technique
|| entry.technique.toUpperCase().endsWith(`:${technique}`)
));
assert.ok(receipts.length > 0, `${technique} must have an explicit receipt`);
assert.equal(receipts.some((receipt) => receipt.executed || receipt.status === "verified"), false);
}
const ledgerIds = new Set(bundle.executionLedger.map((receipt) => receipt.id));
const evidenceIds = new Set(bundle.evidenceRefs.map((ref) => ref.id));
for (const card of bundle.claimCards) {
for (const ref of card.executedTechniqueRefs) assert.equal(ledgerIds.has(ref), true);
for (const fact of [...card.supportingFacts, ...card.counterFacts]) {
assert.equal(evidenceIds.has(fact.evidenceRef), true);
}
}
const serialized = JSON.stringify(bundle);
for (const forbidden of [
"sk-private-report-secret",
"Bearer private-token",
"postgres://private-db",
"/Users/private",
"/opt/jyotisha-production",
"39.9",
"116.4",
"raw hidden prompt",
"Traceback: raw tool failure",
"secret-token",
"/opt/private",
"secret_token",
"section-secret",
"AuthorizationSecretPlanet",
"dasha-secret",
"calculation-secret",
"AKIAIOSFODNN7EXAMPLE",
"prod-db-01",
"internal-host-22",
]) {
assert.equal(serialized.includes(forbidden), false, `bundle leaked ${forbidden}`);
}
});
test("bundle v2 never treats an available-only machine section as executed evidence", () => {
const workflow = pythonStyleChartPayload() as Record<string, unknown>;
const consumer = workflow.consumer_context as Record<string, unknown>;
consumer.available_layers = [
...(consumer.available_layers as string[]),
"Transit",
];
const machine = workflow.machine_evidence_packet as Record<string, unknown>;
const sections = machine.sections as Record<string, unknown>;
sections.Transit = { status: "available", source_path: "modules.transit" };
const bundle = buildReportEvidenceBundleV2({
workflows: [{ theme: "timing", workflow }],
subject: {
displayName: "测试用户",
birthTimeStatus: "confirmed",
birthPlaceLabel: "北京",
},
requestedThemes: ["timing"],
reportType: "personal_thematic",
presentationMode: "default",
skillSnapshot: {
name: "jyotish-vedic-astrology",
version: "6.9.14",
sha256: "a".repeat(64),
sourceCommit: "b".repeat(40),
},
});
assert.equal(bundle.claimCards.some((card) => card.theme === "timing"), false);
assert.equal(bundle.blockedSections.some((section) => section.theme === "timing"), true);
const transitReceipts = bundle.executionLedger.filter((receipt) => (
receipt.technique === "Transit" || receipt.technique.endsWith(":Transit")
));
assert.ok(transitReceipts.length > 0);
assert.equal(transitReceipts.some((receipt) => receipt.executed), false);
});