Files
Jyotisha/frontend/tests/personal-report-api.test.ts
T
jesse-ux fabe0114dd
Independent Staging Quality Gate / validate (push) Successful in 10m5s
Independent Staging Quality Gate / publish (push) Successful in 3m23s
fix(chart): keep subject-scoped failures visible and follow the new routes
A 401 for the current person was stored, then hidden because the subject stamp only happened on success. Session, report, and birth contracts now follow the subject query and the shared birth lookup. Admin reset fixtures can no longer insert a retired self mirror.
2026-09-25 04:51:49 +08:00

1450 lines
54 KiB
TypeScript

import assert from "node:assert/strict";
import { readFileSync } from "node:fs";
import test from "node:test";
import { computeRequestFingerprint } from "../src/lib/personal-report-generation.ts";
import {
buildPersonalReportSectionPlan,
type PersonalReportSectionPlan,
} from "../src/lib/personal-report-plan.ts";
import { safeParseServerReportDocument } from "../src/lib/personal-report-contract.server-core.ts";
import {
reportListTimestamp,
resolveReportCreate,
resolveReportDelete,
resolveReportRead,
type ReportCreateCoreDeps,
type ReportServicePort,
} from "../src/lib/personal-report-route-core.ts";
import type {
CreateGeneratingInput,
CreateGeneratingResult,
PersonalReportRecord,
} from "../src/lib/personal-report-service-core.ts";
import type { ReportEvidenceBundleV2 } from "../src/lib/report-evidence-bundle-v2.ts";
import type { ReportAgentPort, PersonalReportAgentOutput } from "../src/mastra/personal-report.ts";
import type { ReportBillingPort } from "../src/lib/personal-report-billing.ts";
import type { UsageAuthorization } from "../src/lib/consultation-billing.ts";
const createRoute = readFileSync(
new URL("../src/app/api/reports/route.ts", import.meta.url),
"utf8",
);
const itemRoute = readFileSync(
new URL("../src/app/api/reports/[reportId]/route.ts", import.meta.url),
"utf8",
);
const generationSource = readFileSync(
new URL("../src/lib/personal-report-generation.ts", import.meta.url),
"utf8",
);
const codesSource = readFileSync(
new URL("../src/lib/personal-report-codes.ts", import.meta.url),
"utf8",
);
const coreSource = readFileSync(
new URL("../src/lib/personal-report-route-core.ts", import.meta.url),
"utf8",
);
// ---------------------------------------------------------------------------
// Fixtures
// ---------------------------------------------------------------------------
const UUID_A = "11111111-1111-4111-8111-111111111111";
const UUID_B = "22222222-2222-4222-8222-222222222222";
const REPORT_ID = "33333333-3333-4333-8333-333333333333";
const SESSION_ID = "44444444-4444-4444-8444-444444444444";
function chartPayload() {
const planets = ["Sun", "Moon", "Mars", "Mercury", "Jupiter", "Venus", "Saturn", "Rahu", "Ketu"]
.map((name, index) => ({
id: name,
sign: ["Aries", "Taurus", "Gemini", "Cancer", "Leo", "Virgo", "Libra", "Scorpio", "Sagittarius"][index],
degree: 12.5 + index * 10,
house: index + 1,
retrograde: index === 6,
}));
const houses = Array.from({ length: 12 }, (_, index) => ({
number: index + 1,
sign: ["Aries", "Taurus", "Gemini", "Cancer", "Leo", "Virgo", "Libra", "Scorpio", "Sagittarius", "Capricorn", "Aquarius", "Pisces"][index],
}));
return {
success: true,
chart: {
ascendant: { sign: "Leo", degree: 12.5 },
planets,
houses,
dasha: { mahadashas: [{ lord: "Moon", start: "2019-01-01", end: "2029-01-01" }] },
modules: { varga_full: { d9: { houses } }, narayana_dasha: { periods: [] } },
},
consumer_context: {
route: "general",
core_status: "ready",
available_layers: ["D1", "Vimshottari"],
missing_route_layers: [],
hard_blockers: [],
answer_policy: { can_answer_precise_timing: true, deterministic_claims_forbidden_for: [] },
},
machine_evidence_packet: {
conflicts: [],
sections: [{ name: "Functional Benefic/Malefic", status: "verified", note: "" }],
},
};
}
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-2"],
},
],
...overrides,
};
}
function agentOutputForBundle(
bundle: ReportEvidenceBundleV2,
plan: PersonalReportSectionPlan = buildPersonalReportSectionPlan(bundle, "standard"),
): PersonalReportAgentOutput {
return agentOutput({
thematicNarrative: plan.sections
.filter((section) => section.kind === "thematic" && section.disposition === "write")
.map((section) => {
const claim = bundle.claimCards.find((entry) => entry.theme === section.theme);
assert.ok(claim && section.theme, "a writable plan section must have a claim card");
return {
id: section.id,
theme: section.theme,
title: claim.section,
narrative: claim.conclusion,
actions: [],
caveats: [],
claimStatus: claim.assertionLevel,
evidenceRefs: [...section.evidenceRefs],
};
}),
});
}
const fakeAgent: ReportAgentPort = {
modelId: "test-model",
async generate(bundle, plan) {
return agentOutputForBundle(bundle, plan);
},
};
function capturingAgent(capture: { input: unknown }): ReportAgentPort {
return {
modelId: "test-model",
async generate(bundle, plan) {
capture.input = bundle;
return agentOutputForBundle(bundle, plan);
},
};
}
const SKILL_SNAPSHOT = {
name: "jyotish-personal-report",
version: "1.0.0",
sha256: "a".repeat(64),
sourceCommit: "b".repeat(40),
};
function profileFixture(overrides: Record<string, unknown> = {}) {
return {
name: "测试用户",
birth_date: "1997-08-08",
active_birth_time: "05:30:00",
birth_time_status: "confirmed",
latitude: 39.9,
longitude: 116.4,
timezone_offset: 8,
birth_place_label: "北京",
...overrides,
};
}
class MemoryPersistence implements ReportServicePort {
rows = new Map<string, PersonalReportRecord>();
constructor(seed: PersonalReportRecord[] = []) {
for (const row of seed) this.rows.set(row.id, row);
}
record(input: CreateGeneratingInput, id: string, status: "generating" | "failed"): PersonalReportRecord {
return {
id,
userId: input.userId,
sessionId: input.sessionId ?? null,
chartProfileId: input.chartProfileId ?? null,
requestId: input.requestId,
requestFingerprint: input.requestFingerprint,
reportType: input.reportType,
status,
schemaVersion: "report_document.v1",
presentationMode: input.presentationMode,
depth: input.depth,
requestedThemes: input.requestedThemes ?? [],
reportDocument: null,
calculationHash: null,
evidenceHash: null,
skillName: input.skillName,
skillVersion: input.skillVersion,
skillSourceCommit: input.skillSourceCommit ?? null,
skillSnapshotSha256: input.skillSnapshotSha256,
failureCode: status === "failed" ? "calculation_unavailable" : null,
createdAt: "2026-08-06T00:00:00.000Z",
updatedAt: "2026-08-06T00:00:00.000Z",
completedAt: null,
};
}
async getByUserAndRequestId(userId: string, requestId: string) {
for (const row of this.rows.values()) {
if (row.userId === userId && row.requestId === requestId) return row;
}
return null;
}
async createGenerating(input: CreateGeneratingInput): Promise<CreateGeneratingResult> {
const existing = await this.getByUserAndRequestId(input.userId, input.requestId);
if (existing) {
if (existing.requestFingerprint === input.requestFingerprint) {
return { kind: "replayed", record: existing };
}
return { kind: "request_conflict", record: existing };
}
const inFlight = [...this.rows.values()].find(
(row) => row.userId === input.userId && row.status === "generating",
);
if (inFlight) return { kind: "generation_in_progress", record: inFlight };
const row = this.record(input, REPORT_ID, "generating");
this.rows.set(row.id, row);
return { kind: "created", record: row };
}
async completeReady(userId: string, reportId: string, document: unknown) {
const row = this.rows.get(reportId);
assert.ok(row && row.userId === userId && row.status === "generating");
const readyRow: PersonalReportRecord = {
...row,
status: "ready",
reportDocument: document as PersonalReportRecord["reportDocument"],
completedAt: "2026-08-06T00:01:00.000Z",
};
this.rows.set(reportId, readyRow);
return readyRow;
}
async markFailed(userId: string, reportId: string, failureCode: string) {
const row = this.rows.get(reportId);
assert.ok(row && row.userId === userId);
const failedRow: PersonalReportRecord = {
...row,
status: "failed",
failureCode: failureCode as PersonalReportRecord["failureCode"],
completedAt: "2026-08-06T00:01:00.000Z",
};
this.rows.set(reportId, failedRow);
return failedRow;
}
async getOwnedById(userId: string, reportId: string) {
const row = this.rows.get(reportId);
return row && row.userId === userId ? row : null;
}
async deleteOwned(userId: string, reportId: string) {
const row = this.rows.get(reportId);
if (!row || row.userId !== userId) return false;
this.rows.delete(reportId);
return true;
}
}
function billingHarness(overrides: Partial<UsageAuthorization> = {}): {
billing: ReportBillingPort;
readonly reservations: number;
readonly completions: number;
releases: string[];
} {
let reservations = 0;
let completions = 0;
const releases: string[] = [];
const authorization: UsageAuthorization = {
success: true, reservation_id: null, source: "credits", credits: 0,
subscription_id: null, reason: null, retry_after_seconds: null, ...overrides,
};
return {
billing: {
async reserve() { reservations += 1; return authorization; },
async complete() { completions += 1; return true; },
async release({ reason }) { releases.push(reason); return true; },
},
get reservations() { return reservations; },
get completions() { return completions; },
releases,
};
}
function baseDeps(overrides: Partial<ReportCreateCoreDeps> = {}): ReportCreateCoreDeps {
const persistence = new MemoryPersistence();
return {
requestUrl: "https://jyotisha.chat/api/reports",
origin: "https://jyotisha.chat",
allowedOrigins: [],
userId: UUID_A,
rawBody: {
requestId: UUID_B,
reportType: "personal_full",
presentationMode: "default",
themes: ["career", "marriage", "wealth", "timing"],
},
profile: profileFixture(),
checkSessionOwned: async () => true,
checkChartProfileOwned: async () => true,
featureEnabled: true,
dailyLimit: 5,
counts: {
countGenerating: async () => 0,
countCreatedToday: async () => 0,
},
persistence,
model: { id: "test-model" },
runWorkflow: async () => chartPayload(),
createAgent: () => fakeAgent,
skillSnapshot: SKILL_SNAPSHOT,
now: () => new Date("2026-08-06T00:00:00.000Z"),
...overrides,
};
}
// ---------------------------------------------------------------------------
// Executable route core behavior (no network, no model)
// ---------------------------------------------------------------------------
test("core create: billing denial returns 402 before persistence", async () => {
const persistence = new MemoryPersistence();
const billing = billingHarness({ success: false, reason: "insufficient_credits" });
const response = await resolveReportCreate(baseDeps({ persistence, billing: billing.billing }));
assert.equal(response.status, 402);
assert.equal(response.body.code, "insufficient_credits");
assert.equal(persistence.rows.size, 0);
assert.equal(billing.reservations, 1);
});
test("core create: non-credit billing denial returns 503", async () => {
const billing = billingHarness({ success: false, reason: "fair_use_minute" });
const response = await resolveReportCreate(baseDeps({ billing: billing.billing }));
assert.equal(response.status, 503);
assert.equal(response.body.code, "fair_use_limited");
});
test("core create: inline success completes once and replay does not complete twice", async () => {
const persistence = new MemoryPersistence();
const billing = billingHarness();
const deps = baseDeps({ persistence, billing: billing.billing });
const first = await resolveReportCreate(deps);
const second = await resolveReportCreate(deps);
assert.equal(first.status, 201);
assert.equal(second.status, 200);
// Replay is resolved before billing, so the idempotency key reserves once.
assert.equal(billing.reservations, 1);
assert.equal(billing.completions, 1);
assert.deepEqual(billing.releases, []);
});
test("core create: inline generation failure releases the reservation", async () => {
const billing = billingHarness();
const response = await resolveReportCreate(baseDeps({
billing: billing.billing,
runWorkflow: async () => { throw new Error("engine down"); },
}));
assert.equal(response.status, 502);
assert.deepEqual(billing.releases, ["calculation_unavailable"]);
assert.equal(billing.completions, 0);
});
test("core create: enqueue failure releases the reservation", async () => {
const billing = billingHarness();
const response = await resolveReportCreate(baseDeps({
billing: billing.billing,
jobs: {
async enqueue() { throw new Error("queue down"); },
},
}));
assert.equal(response.status, 503);
assert.deepEqual(billing.releases, ["report_generation_failed"]);
});
test("core create: 401 when not logged in", async () => {
const response = await resolveReportCreate(baseDeps({ userId: null }));
assert.equal(response.status, 401);
});
test("core create: 403 on cross-origin", async () => {
const response = await resolveReportCreate(baseDeps({ origin: "https://evil.example" }));
assert.equal(response.status, 403);
assert.equal(response.body.code, "report_resource_forbidden");
});
test("core create: accepts staging same-origin POST behind the trusted reverse proxy", async () => {
const response = await resolveReportCreate({
...baseDeps({
requestUrl: "http://staging.jyotisha.chat/api/reports",
origin: "https://staging.jyotisha.chat",
}),
requestHeaders: new Headers({
host: "staging.jyotisha.chat",
"x-forwarded-host": "staging.jyotisha.chat",
"x-forwarded-proto": "https",
}),
});
assert.equal(response.status, 201);
assert.equal(response.body.report && typeof response.body.report, "object");
});
test("core create: 400 on invalid payload", async () => {
const response = await resolveReportCreate(baseDeps({ rawBody: { requestId: "not-a-uuid" } }));
assert.equal(response.status, 400);
assert.equal(response.body.code, "invalid_request");
});
test("core create: 422 profile_incomplete without a profile", async () => {
const response = await resolveReportCreate(baseDeps({ profile: null }));
assert.equal(response.status, 422);
assert.equal(response.body.code, "profile_incomplete");
});
test("core create: 422 birth_time_not_usable without a concrete reported minute", async () => {
const response = await resolveReportCreate(baseDeps({
profile: profileFixture({
birth_time_status: "reported",
birth_time_source: "period_only",
reported_birth_time: null,
active_birth_time: null,
}),
}));
assert.equal(response.status, 422);
assert.equal(response.body.code, "birth_time_not_usable");
});
test("core create: 201 for a reported minute uses that clock and directional policy", async () => {
const hours: number[] = [];
const minutes: number[] = [];
const capture: { input: unknown } = { input: null };
const response = await resolveReportCreate(baseDeps({
profile: profileFixture({
birth_time_status: "reported",
birth_time_source: "hospital_record",
reported_birth_time: "08:16:00",
active_birth_time: "05:30:00",
}),
runWorkflow: async (input) => {
hours.push(input.hour);
minutes.push(input.minute);
return chartPayload();
},
createAgent: () => capturingAgent(capture),
}));
assert.equal(response.status, 201);
assert.deepEqual([...new Set(hours)], [8]);
assert.deepEqual([...new Set(minutes)], [16]);
const bundle = capture.input as ReportEvidenceBundleV2;
assert.equal(bundle.subject.birthTimeStatus, "reported");
assert.equal(bundle.answerPolicy.birthTimePolicy, "reported_directional_only");
});
test("core create: confirmed reports do not query rectification ranges", async () => {
const response = await resolveReportCreate(baseDeps({
loadCandidateRange: async () => { throw new Error("must not query"); },
}));
assert.equal(response.status, 201);
});
test("core create: trusted rectification range wins and accepted multi-minute input stays provisional", async () => {
const inputs: Record<string, unknown>[] = [];
const response = await resolveReportCreate(baseDeps({
profile: profileFixture({
birth_time_status: "accepted",
declared_window_start: "09:00",
declared_window_end: "11:00",
uncertainty_before_minutes: 30,
uncertainty_after_minutes: 30,
}),
loadCandidateRange: async () => ({ startTime: "10:00:00", endTime: "10:04:00" }),
runWorkflow: async (input) => {
inputs.push(input as unknown as Record<string, unknown>);
return chartPayload();
},
}));
assert.equal(response.status, 201);
assert.equal(inputs[0].birth_time_accuracy, "provisional");
assert.deepEqual(inputs[0].candidate_range, { start_time: "10:00", end_time: "10:04" });
assert.equal("declared_window_start" in inputs[0], false);
assert.equal("uncertainty_before_minutes" in inputs[0], false);
});
test("core create: accepted single-minute range becomes confirmed without a candidate window", async () => {
const inputs: Record<string, unknown>[] = [];
const response = await resolveReportCreate(baseDeps({
profile: profileFixture({ birth_time_status: "accepted" }),
loadCandidateRange: async () => ({ startTime: "10:00", endTime: "10:00" }),
runWorkflow: async (input) => {
inputs.push(input as unknown as Record<string, unknown>);
return chartPayload();
},
}));
assert.equal(response.status, 201);
assert.equal(inputs[0].birth_time_accuracy, "confirmed");
assert.equal("candidate_range" in inputs[0], false);
});
test("core create: a null candidate window degrades instead of calculation_unavailable", async () => {
const inputs: Record<string, unknown>[] = [];
const response = await resolveReportCreate(baseDeps({
profile: profileFixture({ birth_time_status: "accepted" }),
loadCandidateRange: async () => null,
runWorkflow: async (input) => {
inputs.push(input as unknown as Record<string, unknown>);
return chartPayload();
},
}));
assert.equal(response.status, 201);
assert.equal(response.body.code, undefined);
assert.equal(inputs[0].birth_time_accuracy, "provisional");
assert.equal("candidate_range" in inputs[0], false);
});
test("core create: no adopted range uses the fixed accuracy fallback", async () => {
const inputs: Record<string, unknown>[] = [];
const response = await resolveReportCreate(baseDeps({
profile: profileFixture({
birth_time_status: "accepted",
declared_window_start: "10:00",
declared_window_end: "10:00",
uncertainty_before_minutes: 0,
uncertainty_after_minutes: 0,
}),
runWorkflow: async (input) => {
inputs.push(input as unknown as Record<string, unknown>);
return chartPayload();
},
}));
assert.equal(response.status, 201);
assert.equal(inputs[0].birth_time_accuracy, "provisional");
assert.equal("declared_window_start" in inputs[0], false);
assert.equal("uncertainty_before_minutes" in inputs[0], false);
});
test("core create: 422 birth_time_not_usable for incomplete profile fields", async () => {
const response = await resolveReportCreate(baseDeps({
profile: profileFixture({ latitude: null, longitude: null }),
}));
assert.equal(response.status, 422);
assert.equal(response.body.code, "birth_time_not_usable");
});
test("core create: 403 when the session or chart profile is not owned", async () => {
const response = await resolveReportCreate(baseDeps({
rawBody: {
requestId: UUID_B,
reportType: "personal_full",
presentationMode: "default",
themes: ["career"],
sessionId: SESSION_ID,
},
checkSessionOwned: async () => false,
}));
assert.equal(response.status, 403);
assert.equal(response.body.code, "report_resource_forbidden");
});
test("core create: 403 when the feature is disabled", async () => {
const response = await resolveReportCreate(baseDeps({ featureEnabled: false }));
assert.equal(response.status, 403);
assert.equal(response.body.code, "report_export_disabled");
});
test("core create: 409 request conflict for a different payload under the same requestId", async () => {
const fingerprintA = computeRequestFingerprint({
reportType: "personal_full",
presentationMode: "default",
depth: "standard",
themes: ["career", "marriage", "wealth", "timing"],
sessionId: null,
chartProfileId: null,
});
const fingerprintB = computeRequestFingerprint({
reportType: "personal_full",
presentationMode: "default",
depth: "standard",
themes: ["career"],
sessionId: null,
chartProfileId: null,
});
assert.notEqual(fingerprintA, fingerprintB);
const seeded = new MemoryPersistence([{
id: REPORT_ID,
userId: UUID_A,
sessionId: null,
chartProfileId: null,
requestId: UUID_B,
requestFingerprint: fingerprintA,
reportType: "personal_full",
status: "ready",
schemaVersion: "report_document.v1",
presentationMode: "default",
depth: "standard",
requestedThemes: ["career", "marriage", "wealth", "timing"],
reportDocument: { ok: true } as unknown as PersonalReportRecord["reportDocument"],
calculationHash: "c".repeat(64),
evidenceHash: "d".repeat(64),
skillName: null,
skillVersion: null,
skillSourceCommit: null,
skillSnapshotSha256: "a".repeat(64),
failureCode: null,
createdAt: "2026-08-06T00:00:00.000Z",
updatedAt: "2026-08-06T00:00:00.000Z",
completedAt: "2026-08-06T00:00:00.000Z",
}]);
const response = await resolveReportCreate(baseDeps({
rawBody: { requestId: UUID_B, reportType: "personal_full", presentationMode: "default", themes: ["career"] },
persistence: seeded,
}));
assert.equal(response.status, 409);
assert.equal(response.body.code, "report_request_conflict");
});
test("core create: 200 replay for a ready row with the same fingerprint", async () => {
const fingerprint = computeRequestFingerprint({
reportType: "personal_full",
presentationMode: "default",
depth: "standard",
themes: ["career", "marriage", "wealth", "timing"],
sessionId: null,
chartProfileId: null,
});
const seeded = new MemoryPersistence([{
id: REPORT_ID,
userId: UUID_A,
sessionId: null,
chartProfileId: null,
requestId: UUID_B,
requestFingerprint: fingerprint,
reportType: "personal_full",
status: "ready",
schemaVersion: "report_document.v1",
presentationMode: "default",
depth: "standard",
requestedThemes: ["career", "marriage", "wealth", "timing"],
reportDocument: { ok: true } as unknown as PersonalReportRecord["reportDocument"],
calculationHash: "c".repeat(64),
evidenceHash: "d".repeat(64),
skillName: null,
skillVersion: null,
skillSourceCommit: null,
skillSnapshotSha256: "a".repeat(64),
failureCode: null,
createdAt: "2026-08-06T00:00:00.000Z",
updatedAt: "2026-08-06T00:00:00.000Z",
completedAt: "2026-08-06T00:00:00.000Z",
}]);
const response = await resolveReportCreate(baseDeps({ persistence: seeded }));
assert.equal(response.status, 200);
assert.deepEqual(response.body.reportDocument, { ok: true });
});
test("core create: 409 when a generation is already in progress", async () => {
const generating = new MemoryPersistence();
generating.rows.set("55555555-5555-4555-8555-555555555555", {
id: "55555555-5555-4555-8555-555555555555",
userId: UUID_A,
sessionId: null,
chartProfileId: null,
requestId: "66666666-6666-4666-8666-666666666666",
requestFingerprint: "e".repeat(64),
reportType: "personal_full",
status: "generating",
schemaVersion: "report_document.v1",
presentationMode: "default",
depth: "standard",
requestedThemes: [],
reportDocument: null,
calculationHash: null,
evidenceHash: null,
skillName: null,
skillVersion: null,
skillSourceCommit: null,
skillSnapshotSha256: "a".repeat(64),
failureCode: null,
createdAt: "2026-08-06T00:00:00.000Z",
updatedAt: "2026-08-06T00:00:00.000Z",
completedAt: null,
});
const response = await resolveReportCreate(baseDeps({ persistence: generating }));
assert.equal(response.status, 409);
assert.equal(response.body.code, "report_generation_in_progress");
});
test("core create: 429 at the daily limit", async () => {
const response = await resolveReportCreate(baseDeps({
counts: { countGenerating: async () => 0, countCreatedToday: async () => 5 },
}));
assert.equal(response.status, 429);
assert.equal(response.body.code, "report_rate_limited");
});
test("core create: 502 model_unavailable when no model is configured", async () => {
const persistence = new MemoryPersistence();
const response = await resolveReportCreate(baseDeps({
model: null,
persistence,
}));
assert.equal(response.status, 502);
assert.equal(response.body.code, "model_unavailable");
const row = persistence.rows.get(REPORT_ID);
assert.equal(row?.status, "failed");
assert.equal(row?.failureCode, "model_unavailable");
});
test("core create: 502 calculation_unavailable when the workflow throws", async () => {
const persistence = new MemoryPersistence();
const response = await resolveReportCreate(baseDeps({
persistence,
runWorkflow: async () => {
throw new Error("engine down");
},
}));
assert.equal(response.status, 502);
assert.equal(response.body.code, "calculation_unavailable");
assert.equal(persistence.rows.get(REPORT_ID)?.status, "failed");
});
test("core create: 502 when the workflow returns no usable chart", async () => {
const response = await resolveReportCreate(baseDeps({
runWorkflow: async () => ({ success: false }),
}));
assert.equal(response.status, 502);
assert.equal(response.body.code, "calculation_unavailable");
});
test("core create: 422 when the real evidence cannot support a report (fail closed)", async () => {
const persistence = new MemoryPersistence();
const workflow = chartPayload() as Record<string, unknown>;
const chart = workflow.chart as Record<string, unknown>;
chart.houses = (chart.houses as unknown[]).slice(0, 6);
const response = await resolveReportCreate(baseDeps({
persistence,
runWorkflow: async () => workflow,
}));
assert.equal(response.status, 422);
assert.equal(response.body.code, "calculation_unavailable");
assert.equal(persistence.rows.get(REPORT_ID)?.status, "failed");
});
test("core create: 201 ready with a document on the happy path", async () => {
const persistence = new MemoryPersistence();
const response = await resolveReportCreate(baseDeps({ persistence }));
assert.equal(response.status, 201);
assert.ok(response.body.reportDocument);
const row = persistence.rows.get(REPORT_ID);
assert.equal(row?.status, "ready");
assert.ok(row?.reportDocument);
assert.equal(row?.skillName, SKILL_SNAPSHOT.name);
assert.equal(row?.skillVersion, SKILL_SNAPSHOT.version);
assert.equal(row?.skillSnapshotSha256, SKILL_SNAPSHOT.sha256);
assert.equal(row?.skillSourceCommit, SKILL_SNAPSHOT.sourceCommit);
assert.equal(row?.reportDocument?.provenance.skillName, SKILL_SNAPSHOT.name);
assert.equal(row?.reportDocument?.provenance.skillVersion, SKILL_SNAPSHOT.version);
assert.equal(row?.reportDocument?.provenance.skillSnapshotSha256, SKILL_SNAPSHOT.sha256);
assert.equal(row?.reportDocument?.provenance.skillSourceCommit, SKILL_SNAPSHOT.sourceCommit);
});
test("core create: full report runs every requested theme and sends only bundle v2 to the agent", async () => {
const requestedThemes = ["career", "marriage", "wealth", "timing"];
const workflowThemes: string[] = [];
const capture: { input: unknown } = { input: null };
const response = await resolveReportCreate(baseDeps({
rawBody: {
requestId: UUID_B,
reportType: "personal_full",
presentationMode: "default",
themes: requestedThemes,
},
runWorkflow: async (input) => {
workflowThemes.push(input.theme);
const workflow = chartPayload() as Record<string, unknown>;
const consumer = workflow.consumer_context as Record<string, unknown>;
consumer.route = input.theme;
return workflow;
},
createAgent: () => capturingAgent(capture),
}));
assert.equal(response.status, 201);
assert.deepEqual(workflowThemes, requestedThemes);
const bundle = capture.input as ReportEvidenceBundleV2;
assert.equal(bundle.schemaVersion, "report_evidence_bundle.v2");
const coveredThemes = new Set([
...bundle.claimCards.map((card) => card.theme),
...bundle.blockedSections.map((section) => section.theme),
]);
for (const theme of requestedThemes) {
assert.equal(coveredThemes.has(theme), true, `${theme} must have a claim card or blocked section`);
}
});
test("core create: accepted partial wealth evidence stays ready, blocks D2/D11 and leaks no raw internals", async () => {
const capture: { input: unknown } = { input: null };
const persistence = new MemoryPersistence();
const response = await resolveReportCreate(baseDeps({
persistence,
profile: profileFixture({ birth_time_status: "accepted" }),
rawBody: {
requestId: UUID_B,
reportType: "personal_thematic",
presentationMode: "default",
themes: ["wealth"],
},
runWorkflow: async (input) => {
assert.equal(input.theme, "wealth");
const workflow = chartPayload() as Record<string, unknown>;
workflow.api_key = "sk-private-report-secret";
workflow.authorization = "Bearer private-token";
workflow.cookie = "session=private-cookie";
workflow.database_url = "postgres://private-db";
workflow.internal_path = "/Users/private/project/engine.py";
workflow.runtime_path = "/opt/jyotisha-production/private.py";
workflow.latitude = 39.9;
workflow.longitude = 116.4;
workflow.prompt = "raw hidden prompt";
workflow.traceback = "Traceback: raw tool failure";
workflow.calculation_profile = {
ayanamsa: "AKIAIOSFODNN7EXAMPLE",
node_mode: "prod-db-01",
house_system: "internal-host-22",
};
const chart = workflow.chart as Record<string, unknown>;
const dasha = chart.dasha as Record<string, unknown>;
(dasha.mahadashas as Record<string, unknown>[]).push({
lord: "Moon",
start: "39.9000",
end: "116.4000",
});
const consumer = workflow.consumer_context as Record<string, unknown>;
consumer.route = "wealth";
consumer.available_layers = ["D1", "Vimshottari"];
consumer.missing_route_layers = ["D2", "D11"];
consumer.hard_blockers = [];
const answerPolicy = consumer.answer_policy as Record<string, unknown>;
answerPolicy.deterministic_claims_forbidden_for = ["timing", "prod-db-01"];
const machine = workflow.machine_evidence_packet as Record<string, unknown>;
machine.conflicts = [{
techniques: ["D1"],
summary: "Bearer opaque-conflict at /Users/private/trace.py",
}];
const sections = machine.sections as Record<string, unknown>;
sections.AKIAIOSFODNN7EXAMPLE = { status: "used", source_path: "modules.internal" };
return workflow;
},
createAgent: () => capturingAgent(capture),
}));
assert.equal(response.status, 201);
assert.equal(persistence.rows.get(REPORT_ID)?.status, "ready");
const bundle = capture.input as ReportEvidenceBundleV2;
assert.equal(bundle.schemaVersion, "report_evidence_bundle.v2");
assert.equal(bundle.subject.birthTimeStatus, "accepted");
assert.equal(bundle.answerPolicy.birthTimePolicy, "accepted_directional_only");
assert.equal("candidateRange" in bundle, false);
assert.ok(bundle.blockedSections.some((section) => section.theme === "wealth"));
for (const technique of ["D2", "D11"]) {
const receipt = bundle.executionLedger.find((entry) => entry.technique.toUpperCase() === technique);
assert.ok(receipt, `${technique} must have an explicit non-execution receipt`);
assert.equal(receipt.executed, false);
assert.notEqual(receipt.status, "verified");
}
const serialized = JSON.stringify(bundle);
for (const forbidden of [
"sk-private-report-secret",
"Bearer private-token",
"private-cookie",
"postgres://private-db",
"/Users/private",
"/opt/jyotisha-production",
"39.9",
"116.4",
"raw hidden prompt",
"Traceback: raw tool failure",
"AKIAIOSFODNN7EXAMPLE",
"prod-db-01",
"internal-host-22",
"opaque-conflict",
]) {
assert.equal(serialized.includes(forbidden), false, `bundle leaked ${forbidden}`);
}
});
test("core create: durable queue returns 202 without running generation inline", async () => {
const persistence = new MemoryPersistence();
let enqueueCalls = 0;
let workflowCalls = 0;
const response = await resolveReportCreate(baseDeps({
persistence,
runWorkflow: async () => {
workflowCalls += 1;
return chartPayload();
},
jobs: {
async enqueue(input) {
enqueueCalls += 1;
return {
kind: "created" as const,
job: {
id: "55555555-5555-4555-8555-555555555555",
userId: input.userId,
requestId: input.requestId,
requestFingerprint: input.requestFingerprint,
status: "queued" as const,
attemptCount: 0,
maxAttempts: 3,
leaseToken: null,
leaseOwner: null,
leaseAcquiredAt: null,
leaseExpiresAt: null,
heartbeatAt: null,
nextAttemptAt: null,
progressPhase: "queued",
progressPercent: 0,
lastErrorCode: null,
lastErrorAt: null,
startedAt: null,
finishedAt: null,
createdAt: "2026-08-06T00:00:00.000Z",
updatedAt: "2026-08-06T00:00:00.000Z",
},
};
},
},
}));
assert.equal(response.status, 202);
assert.equal(response.body.jobId, "55555555-5555-4555-8555-555555555555");
assert.equal((response.body.report as { status?: string }).status, "generating");
assert.equal(persistence.rows.get(REPORT_ID)?.status, "generating");
assert.equal(enqueueCalls, 1);
assert.equal(workflowCalls, 0);
});
test("core create: 422 report_guard_rejected when the agent output violates the guard", async () => {
const persistence = new MemoryPersistence();
const violatingAgent: ReportAgentPort = {
modelId: "test-model",
async generate(bundle, plan): Promise<PersonalReportAgentOutput> {
const output = agentOutputForBundle(bundle, plan);
return {
...output,
executiveSummary: {
...output.executiveSummary,
summary: "你一定会患上心脏病。",
},
thematicNarrative: output.thematicNarrative.map((section, index) => index === 0
? { ...section, narrative: "你一定会患上心脏病。" }
: section),
};
},
};
const response = await resolveReportCreate(baseDeps({
persistence,
createAgent: () => violatingAgent,
}));
assert.equal(response.status, 422);
assert.equal(response.body.code, "report_guard_rejected");
assert.equal(persistence.rows.get(REPORT_ID)?.status, "failed");
});
test("core read: 401 without user, 404 for non-owned or missing reports", async () => {
const persistence = new MemoryPersistence();
const readyRow = await createReadyRow(persistence);
const unauthenticated = await resolveReportRead({
requestUrl: "https://jyotisha.chat/api/reports/x",
origin: null,
allowedOrigins: [],
userId: null,
reportId: REPORT_ID,
persistence,
validateReadyDocument: acceptAnyDocument,
});
assert.equal(unauthenticated.status, 401);
const otherUser = await resolveReportRead({
requestUrl: "https://jyotisha.chat/api/reports/x",
origin: null,
allowedOrigins: [],
userId: UUID_B,
reportId: REPORT_ID,
persistence,
validateReadyDocument: acceptAnyDocument,
});
assert.equal(otherUser.status, 404);
const missing = await resolveReportRead({
requestUrl: "https://jyotisha.chat/api/reports/x",
origin: null,
allowedOrigins: [],
userId: UUID_A,
reportId: "99999999-9999-4999-8999-999999999999",
persistence,
validateReadyDocument: acceptAnyDocument,
});
assert.equal(missing.status, 404);
assert.equal(missing.body.code, "report_not_found");
assert.equal(readyRow, true);
});
test("core read: ready returns the document, generating returns status only", async () => {
const persistence = new MemoryPersistence();
const row = await createReadyRow(persistence);
const ready = await resolveReportRead({
requestUrl: "https://jyotisha.chat/api/reports/x",
origin: null,
allowedOrigins: [],
userId: UUID_A,
reportId: REPORT_ID,
persistence,
validateReadyDocument: acceptAnyDocument,
});
assert.equal(ready.status, 200);
assert.ok(ready.body.reportDocument);
assert.equal(row, true);
const generatingPersistence = new MemoryPersistence();
generatingPersistence.rows.set(REPORT_ID, {
...seedRecord(),
status: "generating",
reportDocument: null,
completedAt: null,
});
const generating = await resolveReportRead({
requestUrl: "https://jyotisha.chat/api/reports/x",
origin: null,
allowedOrigins: [],
userId: UUID_A,
reportId: REPORT_ID,
persistence: generatingPersistence,
validateReadyDocument: acceptAnyDocument,
});
assert.equal(generating.status, 200);
assert.equal("reportDocument" in generating.body, false);
assert.equal((generating.body.report as { status: string }).status, "generating");
});
test("core read: failed reports include a readable summary from existing section rows", async () => {
const persistence = new MemoryPersistence();
persistence.rows.set(REPORT_ID, {
...seedRecord(),
status: "failed",
failureCode: "report_schema_invalid",
requestedThemes: ["career", "marriage", "wealth", "timing"],
});
const response = await resolveReportRead({
requestUrl: "https://jyotisha.chat/api/reports/x",
origin: null,
allowedOrigins: [],
userId: UUID_A,
reportId: REPORT_ID,
persistence,
listSections: async () => [
{ status: "blocked", lastErrorCode: "section_refs_mismatch" },
{ status: "pending", lastErrorCode: null },
{ status: "pending", lastErrorCode: null },
{ status: "pending", lastErrorCode: null },
],
validateReadyDocument: () => ({ ok: false }),
});
assert.equal(response.status, 200);
const report = response.body.report as {
failureSummary?: string;
innerReason?: string;
sectionErrorCodes?: string[];
};
assert.equal(report.failureSummary, "4 个主题中 1 个写作失败:证据引用未对齐,3 个未开始");
assert.equal(report.innerReason, "section_generation_incomplete");
assert.deepEqual(report.sectionErrorCodes, ["section_refs_mismatch"]);
});
test("core read: failed longform reports expose appendix last_error_code without bodies", async () => {
const persistence = new MemoryPersistence();
persistence.rows.set(REPORT_ID, {
...seedRecord(),
status: "failed",
failureCode: "calculation_unavailable",
requestedThemes: ["career", "marriage", "wealth", "health", "spirituality"],
});
const response = await resolveReportRead({
requestUrl: "https://jyotisha.chat/api/reports/x",
origin: null,
allowedOrigins: [],
userId: UUID_A,
reportId: REPORT_ID,
persistence,
listSections: async () => [],
loadLongformAppendix: async () => ({
status: "unavailable",
lastErrorCode: "appendix_persist_failed",
markdown: null,
}),
validateReadyDocument: () => ({ ok: false }),
});
assert.equal(response.status, 200);
const report = response.body.report as {
failureSummary?: string;
appendixLastErrorCode?: string;
};
assert.equal(report.appendixLastErrorCode, "appendix_persist_failed");
assert.equal(report.failureSummary, "报告正文未能保存,请稍后重试");
assert.equal("markdown" in response.body, false);
});
test("core read: rejects a polluted stored ready document via canonical re-validation", async () => {
const persistence = new MemoryPersistence();
persistence.rows.set(REPORT_ID, {
...seedRecord(),
reportDocument: { hacked: true } as unknown as PersonalReportRecord["reportDocument"],
});
const response = await resolveReportRead({
requestUrl: "https://jyotisha.chat/api/reports/x",
origin: null,
allowedOrigins: [],
userId: UUID_A,
reportId: REPORT_ID,
persistence,
validateReadyDocument: (document) => {
const parsed = safeParseServerReportDocument(document);
return parsed.ok ? { ok: true, document: parsed.document } : { ok: false };
},
});
assert.equal(response.status, 422);
assert.equal(response.body.code, "report_schema_invalid");
assert.equal("reportDocument" in response.body, false);
assert.equal((response.body.report as { status: string }).status, "ready");
});
test("core read: returns a legitimate ready document after canonical re-validation", async () => {
const persistence = new MemoryPersistence();
await createReadyRow(persistence);
const response = await resolveReportRead({
requestUrl: "https://jyotisha.chat/api/reports/x",
origin: null,
allowedOrigins: [],
userId: UUID_A,
reportId: REPORT_ID,
persistence,
validateReadyDocument: (document) => {
const parsed = safeParseServerReportDocument(document);
return parsed.ok ? { ok: true, document: parsed.document } : { ok: false };
},
});
assert.equal(response.status, 200);
assert.ok(response.body.reportDocument);
});
test("core read: markdown loader returns longform and hides the five-chapter body when it is missing", async () => {
const persistence = new MemoryPersistence();
persistence.rows.set(REPORT_ID, seedRecord());
const withMarkdown = await resolveReportRead({
requestUrl: "https://jyotisha.chat/api/reports/x",
origin: null,
allowedOrigins: [],
userId: UUID_A,
reportId: REPORT_ID,
persistence,
loadLongformMarkdown: async () => "# 长报告\n\n### 摘要\n正文",
validateReadyDocument: acceptAnyDocument,
});
assert.equal(withMarkdown.status, 200);
assert.equal(withMarkdown.body.longformMarkdown, "# 长报告\n\n### 摘要\n正文");
assert.ok(withMarkdown.body.reportDocument);
const missing = await resolveReportRead({
requestUrl: "https://jyotisha.chat/api/reports/x",
origin: null,
allowedOrigins: [],
userId: UUID_A,
reportId: REPORT_ID,
persistence,
loadLongformMarkdown: async () => null,
validateReadyDocument: acceptAnyDocument,
});
assert.equal(missing.status, 200);
assert.equal(missing.body.longformMarkdown, null);
assert.equal("reportDocument" in missing.body, false);
});
test("core read: appendix loader supplies ready markdown without the markdown-only fallback", async () => {
const persistence = new MemoryPersistence();
persistence.rows.set(REPORT_ID, seedRecord());
const response = await resolveReportRead({
requestUrl: "https://jyotisha.chat/api/reports/x",
origin: null,
allowedOrigins: [],
userId: UUID_A,
reportId: REPORT_ID,
persistence,
loadLongformAppendix: async () => ({
status: "ready",
lastErrorCode: null,
markdown: "# 附录长报告\n\n### 摘要\n正文",
}),
validateReadyDocument: acceptAnyDocument,
});
assert.equal(response.status, 200);
assert.equal(response.body.longformMarkdown, "# 附录长报告\n\n### 摘要\n正文");
});
test("core delete: owner-only, 200 ok for the owner and 404 otherwise", async () => {
const persistence = new MemoryPersistence();
await createReadyRow(persistence);
const otherUser = await resolveReportDelete({
requestUrl: "https://jyotisha.chat/api/reports/x",
origin: null,
allowedOrigins: [],
userId: UUID_B,
reportId: REPORT_ID,
persistence,
});
assert.equal(otherUser.status, 404);
const owner = await resolveReportDelete({
requestUrl: "https://jyotisha.chat/api/reports/x",
origin: null,
allowedOrigins: [],
userId: UUID_A,
reportId: REPORT_ID,
persistence,
});
assert.equal(owner.status, 200);
assert.deepEqual(owner.body, { ok: true });
assert.equal(persistence.rows.has(REPORT_ID), false);
});
function acceptAnyDocument(document: unknown): { ok: true; document: unknown } | { ok: false } {
return { ok: true, document };
}
async function createReadyRow(persistence: MemoryPersistence): Promise<boolean> {
const fingerprint = computeRequestFingerprint({
reportType: "personal_full",
presentationMode: "default",
depth: "standard",
themes: ["career", "marriage", "wealth", "timing"],
sessionId: null,
chartProfileId: null,
});
const response = await resolveReportCreate(baseDeps({ persistence }));
if (response.status !== 201) return false;
const row = persistence.rows.get(REPORT_ID);
assert.equal(row?.requestFingerprint, fingerprint);
return true;
}
function seedRecord(): PersonalReportRecord {
return {
id: REPORT_ID,
userId: UUID_A,
sessionId: null,
chartProfileId: null,
requestId: UUID_B,
requestFingerprint: "f".repeat(64),
reportType: "personal_full",
status: "ready",
schemaVersion: "report_document.v1",
presentationMode: "default",
depth: "standard",
requestedThemes: ["career", "marriage", "wealth", "timing"],
reportDocument: { ok: true } as unknown as PersonalReportRecord["reportDocument"],
calculationHash: "c".repeat(64),
evidenceHash: "d".repeat(64),
skillName: null,
skillVersion: null,
skillSourceCommit: null,
skillSnapshotSha256: "a".repeat(64),
failureCode: null,
createdAt: "2026-08-06T00:00:00.000Z",
updatedAt: "2026-08-06T00:00:00.000Z",
completedAt: "2026-08-06T00:00:00.000Z",
};
}
// ---------------------------------------------------------------------------
// Source-level production wiring checks
// ---------------------------------------------------------------------------
test("POST route uses dual clients: authenticated reads + admin persistence", () => {
assert.match(createRoute, /createServerSupabaseClient\(\)/);
assert.match(createRoute, /loadSubjectBirth/);
assert.match(createRoute, /createAdminSupabaseClient\(\)/);
assert.match(createRoute, /createSupabasePersonalReportService\(admin\)/);
assert.match(createRoute, /createPersonalReportDataClient\(admin\)/);
assert.doesNotMatch(createRoute, /resolveReportPersistencePort|resolveReportContractPort/);
assert.doesNotMatch(createRoute, /ReportPersistenceUnavailableError|ReportContractUnavailableError/);
assert.doesNotMatch(createRoute, /not wired yet|尚未就绪/);
});
test("POST route loads candidate windows through the shared RPC helper", () => {
assert.match(createRoute, /loadReportCandidateRange/);
assert.doesNotMatch(createRoute, /from\("agentic_rectification_cases"\)/);
assert.doesNotMatch(createRoute, /from\("birth_time_rectification_cases"\)/);
});
test("GET/DELETE use the authenticated client (least privilege) and the core handlers", () => {
assert.match(itemRoute, /createServerSupabaseClient\(\)/);
assert.match(itemRoute, /createSupabasePersonalReportService\(supabase\)/);
assert.match(itemRoute, /resolveReportRead/);
assert.match(itemRoute, /resolveReportDelete/);
assert.match(itemRoute, /loadLongformAppendix/);
assert.doesNotMatch(itemRoute, /createAdminSupabaseClient/);
});
test("route core enforces same-origin and never leaks raw exception text", () => {
assert.match(coreSource, /checkSameOrigin/);
assert.match(coreSource, /REPORT_STABLE_CODES\.resourceForbidden/);
assert.match(createRoute, /requestHeaders:\s*request\.headers/);
assert.match(itemRoute, /requestHeaders:\s*request\.headers/);
assert.doesNotMatch(createRoute, /error\.message\)/);
assert.doesNotMatch(itemRoute, /error\.message\)/);
assert.doesNotMatch(createRoute, /\.stack/);
assert.doesNotMatch(itemRoute, /\.stack/);
});
test("POST route reads the daily limit from env and resolves a real skill snapshot", () => {
assert.match(createRoute, /readPersonalReportDailyLimit\(process\.env\)/);
assert.match(createRoute, /resolveSkillSnapshot\(\)/);
assert.doesNotMatch(createRoute, /每日.*上限.*\d|PERSONAL_REPORT_DAILY_LIMIT.*\?\?\s*["']\d/);
});
test("GET route re-validates stored ready documents through the canonical server parse", () => {
assert.match(itemRoute, /safeParseServerReportDocument\(document\)/);
assert.match(itemRoute, /validateReadyDocument/);
assert.match(coreSource, /validateReadyDocument/);
assert.match(coreSource, /canonical server parse/);
assert.match(coreSource, /REPORT_STABLE_CODES\.schemaInvalid/);
assert.doesNotMatch(coreSource, /client.*validation|validate.*client/i);
});
test("POST route never generates HTML/PDF/base64 or local paths", () => {
assert.doesNotMatch(createRoute, /window\.print|html2canvas|jsPDF|base64|\.pdf/);
assert.doesNotMatch(createRoute, /sendFile|createWriteStream|\/opt\/|\/var\/|\/Users\//);
});
test("POST enqueues durable work without Next.js after and GET lists metadata without report bodies", () => {
assert.match(createRoute, /createSupabasePersonalReportJobService/);
assert.match(createRoute, /jobs:\s*createSupabasePersonalReportJobService\(admin\)/);
assert.doesNotMatch(createRoute, /\bafter\s*\(/);
assert.doesNotMatch(createRoute, /deferGeneration/);
assert.match(createRoute, /export async function GET\(request: Request\)/);
assert.match(createRoute, /REPORT_LIST_COLUMNS/);
const listColumns = createRoute.slice(
createRoute.indexOf("const REPORT_LIST_COLUMNS"),
createRoute.indexOf("function sanitizedErrorCode"),
);
assert.doesNotMatch(listColumns, /calculation_hash|evidence_hash/);
assert.match(listColumns, /"card_summary"/);
assert.doesNotMatch(listColumns, /->|->>|report_document/);
assert.match(createRoute, /sanitizedErrorReason/);
assert.doesNotMatch(createRoute, /STALE_GENERATION_MS|staleBefore/);
assert.match(createRoute, /reportListTimestamp\(row\.created_at\)/);
assert.match(createRoute, /reportListTimestamp\(row\.completed_at\)/);
});
test("list timestamps accept Date objects and ISO strings from self-hosted postgres", () => {
assert.equal(
reportListTimestamp(new Date("2026-08-22T12:34:00.000Z")),
"2026-08-22T12:34:00.000Z",
);
assert.equal(reportListTimestamp("2026-08-22T12:34:00.000Z"), "2026-08-22T12:34:00.000Z");
assert.equal(reportListTimestamp(""), "");
assert.equal(reportListTimestamp(null), "");
assert.match(coreSource, /export function reportListTimestamp/);
assert.match(generationSource, /innerReason/);
assert.match(generationSource, /isPersonalReportGenerationAbort/);
assert.match(generationSource, /parsePaths/);
assert.doesNotMatch(generationSource, /failSchema\([^)]*error\.message/);
});
test("stable error codes live in the dependency-free codes module", () => {
for (const code of [
"profile_incomplete",
"birth_time_not_usable",
"report_generation_in_progress",
"report_rate_limited",
"calculation_unavailable",
"model_unavailable",
"report_schema_invalid",
"report_guard_rejected",
"report_not_found",
"report_request_conflict",
]) {
assert.ok(codesSource.includes(`"${code}"`), `missing stable code ${code}`);
}
// The codes module must stay free of heavy imports so route handlers that
// only need codes never trace the generation/skill-snapshot logic.
assert.doesNotMatch(codesSource, /node:fs|node:path|node:crypto|readdirSync|readFileSync/);
assert.doesNotMatch(createRoute, /dangerouslySetInnerHTML/);
});
test("GET route imports codes from the pure module, never the generation module", () => {
assert.match(itemRoute, /personal-report-codes/);
assert.doesNotMatch(itemRoute, /personal-report-generation/);
assert.doesNotMatch(itemRoute, /resolveSkillSnapshot|buildReportEvidencePacket|canonicalSerialize/);
assert.match(generationSource, /personal-report-codes/);
});
test("generation pipeline re-validates with the canonical server parse after the guard", () => {
assert.match(generationSource, /safeParseServerReportDocument\(guarded\.document\)/);
assert.match(generationSource, /assembleReportDocument/);
});
test("evidence hash is the canonical appendix hash, never a model self-report", () => {
assert.match(generationSource, /computeEvidenceHash\(parsed\.document\.evidenceAppendix\)/);
assert.match(generationSource, /computeEvidenceHash\(appendix\)/);
});
test("skill snapshot resolution is registry-backed and fails closed", () => {
assert.match(generationSource, /SkillSnapshotUnavailableError/);
assert.match(generationSource, /resolveActiveSkillPackage/);
assert.doesNotMatch(generationSource, /source-manifest\.json/);
assert.doesNotMatch(generationSource, /skill_snapshot_unavailable.*digest/);
});
test("generation delegates package verification to the immutable registry", () => {
assert.doesNotMatch(generationSource, /node:fs|node:path|readdirSync|readFileSync/);
assert.doesNotMatch(generationSource, /\bskillDirectory\b|\brepoRoot\b|turbopackIgnore/);
assert.match(generationSource, /resolveActiveSkillPackage\("jyotish-personal-report"\)/);
});
test("failed report reads aggregate existing section error codes without importing generation", () => {
assert.match(itemRoute, /listSections/);
assert.match(itemRoute, /createPersonalReportSectionService\(supabase as never\)/);
assert.match(createRoute, /from\("personal_report_sections"\)/);
assert.match(createRoute, /select\("request_id, section_id, status, last_error_code"\)/);
assert.match(createRoute, /row\.section_id === LONGFORM_SNAPSHOT_SECTION_ID\) continue/);
assert.doesNotMatch(itemRoute, /personal-report-generation/);
});