import assert from "node:assert/strict"; import { readFileSync } from "node:fs"; import test from "node:test"; import { classifyReportEnvelope } from "../src/components/personal-report/personal-report-page.tsx"; import { consultationReportMarkdown } from "../src/lib/consultation-report-export.ts"; import { ordinaryReportDownloadMarkdown } from "../src/lib/personal-report-longform-download.ts"; import { resolveReportRead } from "../src/lib/personal-report-route-core.ts"; import type { PersonalReportRecord } from "../src/lib/personal-report-service-core.ts"; import { stripReportChartBlocks } from "../src/lib/report-chart-block.ts"; import { INTERNAL_REPORT_FIELDS, ORDINARY_LIMITATION_COPY, ORDINARY_PUBLIC_FIELDS, REPORT_DOCUMENT_KINDS, ordinaryOutputLeaks, projectChatExportMarkdown, projectOrdinaryReportDocument, projectOrdinaryReportMarkdown, projectOrdinarySnippet, releaseProfessionalReference, } from "../src/lib/report-public-projection.ts"; const CLEAN_MARKDOWN = "# 长报告\n\n### 摘要\n正文"; const CHART_FENCE = [ "事业先看阶段。", "", "```jyotish-chart", JSON.stringify({ version: 1, id: "D1", title: "本命", layout: "north", ascendant: { sign: "Aries", degree: 1 }, planets: [], }), "```", "", "", ].join("\n"); const CACHED_MARKDOWN = [ "# 旧缓存报告", "", "事业先看阶段。", "", "## Claim boundary", "", "technique_truth: partial", "workflow_route: career", "workflow_status: degraded", "precise_timing: blocked", "missing_layers: MEVG", "score: 0.82", "weight: 1.4", "job_id: job-1", "attempt_count: 3", "provider: openai", "model_debug: true", "tool_call_id: call_1", "", "## 质量验收矩阵", "", "| 项 | 状态 |", "| --- | --- |", "| MEVG | blocked |", "", "## 摘要", "", "不承诺具体日期。", ].join("\n"); const UNSAFE_MARKDOWN = [ "事业方向保持观察。正文里夹了 还能读。", "", "", "", "", "", "\"图\"", "[bad](javascript:alert(1))", "[file](file:///etc/passwd)", "见 http://127.0.0.1:5200/api/secret 与 http://localhost:3000/hidden", "sk-testsecretvalue", "provider_payload: {\"model\":\"hidden\"}", "Bearer abcdefghijklmnop", ].join("\n"); test("ordinary fields are an allowlist and internal fields stay classified", () => { assert.deepEqual(ORDINARY_PUBLIC_FIELDS, [ "title", "prose", "conclusion", "action", "limitation", "chart_fence", "engine_svg", ]); assert.ok(INTERNAL_REPORT_FIELDS.includes("technique_truth")); assert.ok(INTERNAL_REPORT_FIELDS.includes("workflow_route")); assert.ok(INTERNAL_REPORT_FIELDS.includes("secret")); assert.ok(INTERNAL_REPORT_FIELDS.includes("provider")); assert.deepEqual(REPORT_DOCUMENT_KINDS, [ "chat_export", "personal_report_detail", "ordinary_markdown_download", "professional_reference", ]); }); test("clean markdown is unchanged and absent fields are not invented", () => { assert.equal(projectOrdinaryReportMarkdown(CLEAN_MARKDOWN), CLEAN_MARKDOWN); assert.equal(projectOrdinarySnippet("事业方向保持观察"), "事业方向保持观察"); const report = projectChatExportMarkdown({ documentKind: "chat_export", title: "空", prose: "只有正文。", }); assert.match(report, /只有正文/); assert.doesNotMatch(report, /需要知道的限制/); assert.doesNotMatch(report, /technique_truth|unknown|Claim boundary/); assert.equal(ordinaryOutputLeaks(report).length, 0); }); test("chat export rewrites limitation signals and hides internal keys", () => { const report = consultationReportMarkdown({ title: "事业咨询", messages: [ { role: "user", text: "未来一年事业如何?" }, { role: "assistant", text: "先看阶段,不承诺具体日期。", techniqueTruth: "partial", workflowReceipt: { route: "career", status: "ready", preciseTiming: "blocked", missingLayers: ["MEVG"], }, }, ], }); assert.match(report, /先看阶段/); assert.match(report, new RegExp(ORDINARY_LIMITATION_COPY.preciseTiming)); assert.match(report, new RegExp(ORDINARY_LIMITATION_COPY.missingEvidence)); assert.match(report, new RegExp(ORDINARY_LIMITATION_COPY.techniqueOpen)); assert.equal(ordinaryOutputLeaks(report).length, 0); assert.doesNotMatch(report, /career/); }); test("old cached markdown cannot bypass the ordinary projection", () => { const projected = projectOrdinaryReportMarkdown(CACHED_MARKDOWN); assert.match(projected, /事业先看阶段/); assert.match(projected, /不承诺具体日期/); assert.match(projected, new RegExp(ORDINARY_LIMITATION_COPY.preciseTiming)); assert.doesNotMatch(projected, /Claim boundary|质量验收矩阵/); assert.equal(ordinaryOutputLeaks(projected).length, 0, ordinaryOutputLeaks(projected).join(", ")); assert.equal(projectOrdinaryReportMarkdown(projected), projected); }); test("professional reference is an explicit kind and still fail-safes without a separate grant", () => { const released = releaseProfessionalReference(CACHED_MARKDOWN); assert.equal(released.documentKind, "professional_reference"); assert.equal(released.format, "markdown"); assert.equal(ordinaryOutputLeaks(released.markdown).length, 0); assert.notEqual(released.documentKind, "ordinary_markdown_download"); const routeSource = readFileSync( new URL("../src/app/api/reports/[reportId]/professional-reference/route.ts", import.meta.url), "utf8", ); assert.match(routeSource, /releaseProfessionalReference/); assert.doesNotMatch(routeSource, /professionalGrant/); const downloadSource = readFileSync( new URL("../src/lib/personal-report-longform-download.ts", import.meta.url), "utf8", ); assert.doesNotMatch(downloadSource, /professional-reference/); }); test("chart fences stay in ordinary reading output and download still strips them", () => { const projected = projectOrdinaryReportMarkdown(CHART_FENCE); assert.match(projected, /```jyotish-chart/); assert.match(projected, /"id":"D1"/); assert.match(projected, /<\/svg>/); const downloaded = ordinaryReportDownloadMarkdown(CHART_FENCE); assert.equal(downloaded, stripReportChartBlocks(projected)); assert.doesNotMatch(downloaded, /```jyotish-chart/); assert.match(downloaded, /<\/svg>/); const engineSvg = [ "图如下。", "", "", "", "日", "", ].join("\n"); const keptSvg = projectOrdinaryReportMarkdown(engineSvg); assert.match(keptSvg, //); const poisoned = projectOrdinaryReportMarkdown([ "```jyotish-chart", "{\"technique_truth\":\"partial\"}", "```", ].join("\n")); assert.doesNotMatch(poisoned, /jyotish-chart|technique_truth/); }); test("ordinary output drops HTML, dangerous URLs, secrets, and model fields", () => { const projected = projectOrdinaryReportMarkdown(UNSAFE_MARKDOWN); assert.match(projected, /事业方向保持观察/); assert.match(projected, /还能读/); assert.equal(ordinaryOutputLeaks(projected).length, 0, ordinaryOutputLeaks(projected).join(", ")); assert.doesNotMatch(projected, /evil\.example|alert\(1\)|alert\(2\)|sk-testsecretvalue|abcdefghijklmnop/); const svgScript = projectOrdinaryReportMarkdown(""); assert.doesNotMatch(svgScript, / { const detail = classifyReportEnvelope(200, { report: { id: "11111111-1111-4111-8111-111111111111", status: "ready", createdAt: "2026-09-22T00:00:00.000Z", }, longformMarkdown: CACHED_MARKDOWN, }); assert.equal(detail.phase, "markdown-ready"); if (detail.phase === "markdown-ready") { assert.match(detail.markdown, /事业先看阶段/); assert.equal(ordinaryOutputLeaks(detail.markdown).length, 0); } const leakedDocument = { schemaVersion: "report_document.v2", executiveSummary: { headline: "方向", summary: "先看阶段。", priorities: ["先观察"], }, evidenceAppendix: { techniqueAudit: [{ techniqueName: "MEVG", status: "blocked", score: 0.2 }], }, provenance: { skillSnapshotSha256: "ab".repeat(32), calculationHash: "cd".repeat(32), provider: "openai", }, disclaimer: "不把未闭合的判断写成确定结论。", }; const projectedDocument = projectOrdinaryReportDocument(leakedDocument); assert.equal(JSON.stringify(projectedDocument).includes("evidenceAppendix"), false); assert.equal(JSON.stringify(projectedDocument).includes("techniqueAudit"), false); assert.equal(JSON.stringify(projectedDocument).includes("skillSnapshotSha256"), false); assert.equal(ordinaryOutputLeaks(JSON.stringify(projectedDocument)).length, 0); const response = await resolveReportRead({ requestUrl: "https://jyotisha.chat/api/reports/x", origin: null, allowedOrigins: [], userId: "aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa", reportId: "11111111-1111-4111-8111-111111111111", persistence: { async getOwnedById() { return syntheticRecord(); }, }, loadLongformAppendix: async () => ({ status: "ready", lastErrorCode: null, markdown: CACHED_MARKDOWN, }), validateReadyDocument: () => ({ ok: true, document: leakedDocument }), }); assert.equal(response.status, 200); assert.equal(typeof response.body.longformMarkdown, "string"); assert.match(String(response.body.longformMarkdown), /事业先看阶段/); assert.equal(ordinaryOutputLeaks(String(response.body.longformMarkdown)).length, 0); assert.equal(JSON.stringify(response.body.reportDocument).includes("evidenceAppendix"), false); const passthrough = { ok: true }; assert.equal(projectOrdinaryReportDocument(passthrough), passthrough); }); test("markdown view and chart fence contracts are not weakened", () => { const viewSource = readFileSync( new URL("../src/components/personal-report/personal-report-markdown-view.tsx", import.meta.url), "utf8", ); assert.match(viewSource, /skipHtml/); assert.match(viewSource, /disallowedElements=\{\["script", "iframe", "object", "embed", "img"\]\}/); assert.doesNotMatch(viewSource, /rehype-raw/); assert.doesNotMatch(viewSource, /dangerouslySetInnerHTML/); const fenceSource = readFileSync( new URL("../src/lib/report-chart-block.ts", import.meta.url), "utf8", ); assert.match(fenceSource, /```jyotish-chart/); const coreSource = readFileSync( new URL("../src/lib/personal-report-route-core.ts", import.meta.url), "utf8", ); assert.match(coreSource, /projectOrdinaryReportMarkdown/); assert.match(coreSource, /projectOrdinaryReportDocument/); }); test("PL9 audit vocabulary deletes the whole line and the adjacent table caption", () => { const source = [ "# 读者报告", "", "这一句保留。", "未完成对照,所有行保持 parameter_sensitive。", "", "### KP 原始表", "", "本节只解释下面这张表。", "", "| 项 | 状态 |", "| --- | --- |", "| annual_tajika_pack | blocked |", "", "表后的正文还在。", "KP producer failed: 'types.SimpleNamespace' object has no attribute 'year'", "Traceback (most recent call last):", "full-report://profile/hash", "PL9 第 42 页 fixture", "pl9_personal_long_report.v2", "kp_lord_sub_unavailable", ].join("\n"); const projected = projectOrdinaryReportMarkdown(source); assert.match(projected, /这一句保留/); assert.match(projected, /表后的正文还在/); assert.doesNotMatch(projected, /所有行保持/); assert.doesNotMatch(projected, /parameter_sensitive/); assert.match(projected, /### KP 原始表/); assert.doesNotMatch(projected, /本节只解释下面这张表/); assert.doesNotMatch(projected, /annual_tajika_pack/); assert.doesNotMatch(projected, /^- `$/m); assert.doesNotMatch(projected, /object has no attribute|Traceback|full-report:\/\/|PL9 第 42 页|pl9_personal_long_report|_unavailable/); assert.deepEqual(ordinaryOutputLeaks(projected), []); }); test("real reader_main fixture has no ordinary output leaks", () => { const fixture = JSON.parse(readFileSync(new URL("./fixtures/report-reader-main-fictional.json", import.meta.url), "utf8")); assert.equal(fixture.fixtureProvenance.fictional, true); assert.equal(fixture.fixtureProvenance.edition, "reader_main"); const markdown = fixture.markdown as string; assert.equal(ordinaryOutputLeaks(markdown).length, 0, ordinaryOutputLeaks(markdown).join(", ")); for (const chapter of ["出生资料", "Ashtakavarga", "标准大运", "KP", "Year Lord", "Tajika", "Muntha"]) { assert.match(markdown, new RegExp(chapter)); } const projected = projectOrdinaryReportMarkdown(markdown); assert.equal(ordinaryOutputLeaks(projected).length, 0); const bhava = markdown.match(/^### Bhava Bala 十二宫力量\r?\n[\s\S]*?(?=^#{1,3} |$(?![\s\S]))/m)?.[0].trim(); assert.ok(bhava, "real engine Bhava Bala section exists"); assert.ok(projected.includes(bhava), "the complete twelve-house table survives projection"); const lords = [...projected.matchAll(/^\| Varshesha \| ([^|]+) \|$/gm)].map((row) => row[1].trim()); const signs = [...projected.matchAll(/^\| Muntha sign \| ([^|]+) \|$/gm)].map((row) => row[1].trim()); assert.equal(lords.length, 3); assert.equal(signs.length, 3); assert.ok([...lords, ...signs].every((value) => value !== "-")); assert.ok(new Set(lords).size >= 2); assert.ok(new Set(signs).size >= 2); assert.doesNotMatch(projected, /\| Varshapravesha \| - \|/); assert.doesNotMatch(projected, /姓名:-|overall_score|吉性_count|total_elements/); }); test("twelve English words count as a paragraph leak, planet names do not", () => { const planet = projectOrdinaryReportMarkdown("太阳与 Mars 同宫,再看 Venus。"); assert.match(planet, /Mars/); assert.match(planet, /Venus/); const english = [ "This reference export combines local calculations with available external engine", "replays and source comparisons that stay explicitly labeled.", ].join(" "); const projected = projectOrdinaryReportMarkdown(`开头。\n${english}\n结尾。`); assert.match(projected, /开头/); assert.match(projected, /结尾/); assert.doesNotMatch(projected, /This reference export/); assert.deepEqual(ordinaryOutputLeaks(projected), []); }); test("internal table removal keeps its heading and the following public table", () => { const source = [ "### 宫位资料", "", "这段只说明内部表。", "", "| key | value |", "| --- | --- |", "| model_debug | hidden |", "", "| House | Score | Weight |", "| --- | --- | --- |", "| 1 | 8 | 2 |", ].join("\n"); const projected = projectOrdinaryReportMarkdown(source); assert.match(projected, /^### 宫位资料/m); assert.match(projected, /\| House \| Score \| Weight \|/); assert.match(projected, /\| 1 \| 8 \| 2 \|/); assert.doesNotMatch(projected, /这段只说明内部表|model_debug|hidden/); }); test("chat export keeps English prose and chart text under pre-PL9 safety rules", () => { const english = "The producer walked through the blocked street and found another safe route to the station."; const caption = "The annual chart shows how planets move through signs across the following twelve months."; const source = [english, "", "### 资料", "", "这个说明保留。", "", "| key | value |", "| --- | --- |", "| model_debug | hidden |", "", "```jyotish-chart", JSON.stringify({ title: caption, planets: [] }), "```", "", `${caption}`, ].join("\n"); const chat = projectChatExportMarkdown({ title: "英文咨询", prose: source }); assert.ok(chat.includes(english)); assert.match(chat, /这个说明保留/); assert.match(chat, /```jyotish-chart/); assert.ok(chat.includes(`${caption}`)); assert.doesNotMatch(chat, /model_debug|hidden/); const report = projectOrdinaryReportMarkdown(source); assert.ok(!report.includes(english)); assert.match(report, /```jyotish-chart/); assert.ok(report.includes(`${caption}`)); const unsafe = projectChatExportMarkdown({ title: "安全", prose: UNSAFE_MARKDOWN }); assert.equal(ordinaryOutputLeaks(unsafe).length, 0); }); function syntheticRecord(): PersonalReportRecord { return { id: "11111111-1111-4111-8111-111111111111", userId: "aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa", sessionId: null, chartProfileId: null, requestId: "22222222-2222-4222-8222-222222222222", requestFingerprint: "f".repeat(64), reportType: "personal_full", status: "ready", schemaVersion: "report_document.v2", presentationMode: "default", depth: "standard", requestedThemes: ["career"], reportDocument: null, calculationHash: null, evidenceHash: null, skillName: null, skillVersion: null, skillSourceCommit: null, skillSnapshotSha256: "a".repeat(64), failureCode: null, createdAt: "2026-09-22T00:00:00.000Z", updatedAt: "2026-09-22T00:00:00.000Z", completedAt: "2026-09-22T00:00:00.000Z", }; }