fix(report): give the interpretive fixture structured D2/D11/D24
Independent Staging Quality Gate / validate (push) Successful in 8m22s
Independent Staging Quality Gate / publish (push) Successful in 1m47s

BUG-486 demotion dropped wealth and education cards when the snapshot only had machine receipts, which failed Gitea validate. Keep write-theme cards only when varga_full is present.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Jesse_Chen
2026-09-02 15:27:59 +08:00
parent 45f190a1d0
commit 643f6f7a15
2 changed files with 38 additions and 2 deletions
+2 -2
View File
@@ -7483,8 +7483,8 @@
- 触发条件:请求含财富主题;引擎 `modules.varga_full` 使用 `D2_Hora` / `D11_Rudramsa` 这类带流派后缀的键;工作流把 D2/D11 标成已执行。财富章因此被标成 write,终态文档却没有 `charts[].id` 为 D2/D11。
- 根因:两层叠加。提取只把 `^D\d{1,3}$` 当成分盘 id,丢掉 `D2_Hora` / `D11_Rudramsa``readVargaHouses` 也只认 D9/D10。主题计划仍可用 D2/D11 回执闭合财富章。组装后 `findThemeCoverageViolations` 要求财富主题必须带结构化 D2 与 D11,最终 parse 失败,公开码压成 `report_schema_invalid`
- 修复:把文档允许的分盘键归一成 D2/D9/D10/D11/D24(含 `D2_Hora` 等别名),占据星体走 celestial allowlist。write 主题若缺少对应结构化分盘,降成 blocked disclosure,不再组装成会解析失败的文档。终态 parse 失败时把缺分盘与 hash 失配从笼统的 `final_parse_rejected` 分出来;日志仍只写 allowlist token,不含用户或模型原文。不放宽 `.strict()` 文档 schema,也不改 assertionLevel 规则。
- 验证:`frontend/tests/personal-report-generation.test.ts``D2_Hora` / `D11_Rudramsa` 抽出 D2/D11 且财富章成为 claim`frontend/tests/personal-report-generation-v2.test.ts` 财富 write 只有 D1 时 ready 且财富进 blocked`frontend/tests/personal-report-contract.test.ts` 财富主题缺 D2/D11 仍拒绝。三组 focused 测试 85/85`./node_modules/.bin/tsc --noEmit` 通过。未用登录会话在 staging 重生。
- 防复发:文档分盘 id 必须从引擎 `varga_full` 别名归一,不得只认 `D2` 这种短键。财富/事业/婚恋/教育 write 主题缺对应结构化分盘时必须降级 blocked,不得把 `final_parse_rejected` 当正常完成。新增分盘别名必须同时覆盖提取与文档 `CHART_IDS`
- 验证:`frontend/tests/personal-report-generation.test.ts``D2_Hora` / `D11_Rudramsa` 抽出 D2/D11 且财富章成为 claim`frontend/tests/personal-report-generation-v2.test.ts` 财富 write 只有 D1 时 ready 且财富进 blocked`frontend/tests/personal-report-contract.test.ts` 财富主题缺 D2/D11 仍拒绝。Gitea run 2298 在 `report-interpretive-facts.test.ts` 6 fail:夹具只有 D2/D11/D24 回执、没有 `varga_full`,降级后 wealth/education 卡被拿掉。补上结构化 D2/D11/D24 后该文件本地绿,并锁「仅回执则 demote」。未用登录会话在 staging 重生。
- 防复发:文档分盘 id 必须从引擎 `varga_full` 别名归一,不得只认 `D2` 这种短键。财富/事业/婚恋/教育 write 主题缺对应结构化分盘时必须降级 blocked,不得把 `final_parse_rejected` 当正常完成。新增分盘别名必须同时覆盖提取与文档 `CHART_IDS``REQUIRED_THEME_CHARTS` 或 demote 后必须跑 `frontend/tests/report-interpretive-facts.test.ts`;期望 write 主题 claim card 的夹具必须带对应结构化分盘,不能只标 machine section used。
- 相关记录:BUG-352、BUG-451、BUG-467
- 复发自:无
- 修复版本:待发布
@@ -91,10 +91,21 @@ function machineSections(): JsonRecord {
};
}
function engineVarga(ascIndex: number): JsonRecord {
return {
_meta: { fixture: true },
Ascendant: { sign: SIGNS[ascIndex], sign_idx: ascIndex, degree_in_sign: 10.5 },
Sun: { sign: SIGNS[ascIndex], sign_idx: ascIndex },
Moon: { sign: SIGNS[(ascIndex + 1) % 12], sign_idx: (ascIndex + 1) % 12 },
Mars: { sign: SIGNS[(ascIndex + 2) % 12], sign_idx: (ascIndex + 2) % 12 },
};
}
type FixtureOptions = Readonly<{
interpretive?: boolean;
financeNarrative?: boolean;
pollutedNames?: boolean;
structuredVargas?: boolean;
}>;
function workflowFixture(options: FixtureOptions = {}): JsonRecord {
@@ -112,6 +123,16 @@ function workflowFixture(options: FixtureOptions = {}): JsonRecord {
modules: {},
};
const modules = chart.modules as JsonRecord;
// Machine sections already mark D2/D11/D24 executed. Without the actual
// varga_full objects, demoteThemesMissingRequiredCharts strips wealth and
// education claim cards (Gitea run 2298).
if (options.structuredVargas !== false) {
modules.varga_full = {
D2: engineVarga(ASC_INDEX),
D11: engineVarga((ASC_INDEX + 2) % 12),
D24: engineVarga((ASC_INDEX + 4) % 12),
};
}
if (interpretive) {
chart.yogas = [
{ name: "Amala Yoga", planets: ["Mercury"], category: "extended" },
@@ -294,6 +315,21 @@ test("bundle carries the interpretive layers the engine actually computes", () =
assert.deepEqual([...facts.convergenceDomains], []);
});
test("machine-section D2/D11/D24 receipts without varga_full demote wealth and education", () => {
const bundle = buildBundle({ structuredVargas: false });
assert.equal(bundle.claimCards.some((card) => card.theme === "wealth"), false);
assert.equal(bundle.claimCards.some((card) => card.theme === "education"), false);
assert.ok(bundle.blockedSections.some((section) => section.theme === "wealth"));
assert.ok(bundle.blockedSections.some((section) => section.theme === "education"));
assert.ok(bundle.claimCards.some((card) => card.theme === "general"));
assert.deepEqual(
bundle.themeNarrativeSeeds.map((seed) => seed.theme).filter((theme) => (
theme === "wealth" || theme === "education"
)),
[],
);
});
test("interpretive extraction rejects polluted planet names and over-long narrative text", () => {
const bundle = buildBundle({ pollutedNames: true, financeNarrative: true });
const benefics = bundle.interpretiveFacts.functionalRoles