From 0eebf9713fbb0b2b2a501ea3f8f40ed8e1040030 Mon Sep 17 00:00:00 2001 From: Jesse_Chen Date: Thu, 20 Aug 2026 12:21:08 +0800 Subject: [PATCH] fix(rectification): unblock next build typecheck for house tables Docker npm run build failed on Failed to type check. The skill-package-registry Import traces were only file-tracing noise; houseTable used && on string|null, and eight-method tests compared impossible literals that tsconfig still typechecks. Co-authored-by: Cursor --- docs/BUG_HISTORY.md | 16 ++++++++++++++++ .../src/lib/rectification-candidate-result.ts | 4 ++-- .../tests/rectification-eight-method.test.ts | 13 +++++++++---- 3 files changed, 27 insertions(+), 6 deletions(-) diff --git a/docs/BUG_HISTORY.md b/docs/BUG_HISTORY.md index f217d512..6e45617f 100644 --- a/docs/BUG_HISTORY.md +++ b/docs/BUG_HISTORY.md @@ -4826,3 +4826,19 @@ - 相关记录:BUG-291、BUG-319 - 复发自:BUG-291(迁居继续不轮询;财务/健康被一并排除后不再被方法层追问) - 修复版本:1b93afc5 + +## BUG-321 | Docker `npm run build` 因宫位表 `&&` 与八方法测试字面量类型检查失败 + +- 状态:resolved +- 首次发现:2026-08-20 +- 最近更新:2026-08-20 +- 影响面:`deploy/railway-web.Dockerfile` 的 `RUN npm run build`、`rectification-candidate-result.ts`、`rectification-eight-method.test.ts` +- 用户现象:staging publish 在 web 镜像构建失败。BuildKit 日志末尾是 `skill-package-registry.ts` 的 Import traces。 +- 触发条件:向 `staging` 推送 Skill 10.0.8 后走 publish 镜像构建。push 上的 validate 跳过 `npm run build`。 +- 根因:日志末尾 Import traces 只是动态文件访问追踪警告,真正失败是 `Failed to type check`。`selectedTime && houseTablesByTime[selectedTime]` 在 `string | null` 下会被推断成 `"" | HouseTable | null`,`??` 不能消掉空字符串。八方法测试对 `do_not_poll` 传入 `"appearance"`,并对观察层比较永远不存在的 `"d24"` / `"d2"`。`tsconfig` 包含 `tests/**/*.ts`,因此这些测试错误也会挡住 `next build`。 +- 修复:宫位表改用三元取值;测试改为断言 `do_not_poll` 恰好是 `horary`,并用完整观察层列表证明 D24/D2 不单独成层。 +- 验证:`./node_modules/.bin/tsc --noEmit`;`npx tsx --test tests/rectification-eight-method.test.ts`。 +- 防复发:对 `string | null` 做查找不得用 `&&` 当存在性守卫。`readonly ["horary"]` 的 `.includes()` 只能传入 `"horary"`。staging push 的 validate 仍不跑 `next build`,类型回归只在 publish Docker 暴露。 +- 相关记录:BUG-309、BUG-315、BUG-320 +- 复发自:BUG-309(validate 跳过 `next build`;日志末尾 Import traces 掩盖类型检查失败) +- 修复版本:待提交 diff --git a/frontend/src/lib/rectification-candidate-result.ts b/frontend/src/lib/rectification-candidate-result.ts index 0ecac9d3..ae401df0 100644 --- a/frontend/src/lib/rectification-candidate-result.ts +++ b/frontend/src/lib/rectification-candidate-result.ts @@ -220,8 +220,8 @@ export function parseRectificationCandidateResult(value: unknown): Rectification const fallbackHouseTable = parseRectificationHouseTable(snapshot.houseTable) ?? parseRectificationHouseTable(snapshot.house_table) ?? parseRectificationHouseTable(receipt?.house_table); - const houseTable = (selectedTime && houseTablesByTime[selectedTime]) - ?? (representativeTime && houseTablesByTime[representativeTime]) + const houseTable = (selectedTime ? houseTablesByTime[selectedTime] : null) + ?? (representativeTime ? houseTablesByTime[representativeTime] : null) ?? fallbackHouseTable; return { diff --git a/frontend/tests/rectification-eight-method.test.ts b/frontend/tests/rectification-eight-method.test.ts index 63c226f9..b8b68fa3 100644 --- a/frontend/tests/rectification-eight-method.test.ts +++ b/frontend/tests/rectification-eight-method.test.ts @@ -221,8 +221,7 @@ test("declined relationship skips to career and still skips horary", () => { }); assert.equal(plan.next_followup?.method_id, "d10_career"); assert.equal(plan.next_followup?.domain, "career"); - assert.equal(plan.do_not_poll.includes("horary"), true); - assert.equal(plan.do_not_poll.includes("appearance"), false); + assert.deepEqual([...plan.do_not_poll], ["horary"]); }); test("D9 differ becomes an internal ask theme without sign labels", () => { @@ -305,7 +304,10 @@ test("D24-only window change folds into education follow-up without a second lay const observations = internalObservationsFromWindowScan(scan); assert.equal(observations.find((item) => item.layer === "d5")?.candidates_differ, true); assert.equal(observations.find((item) => item.layer === "d5")?.ask_theme, "education_style"); - assert.equal(observations.some((item) => item.layer === "d24"), false); + assert.deepEqual( + observations.map((item) => item.layer), + ["d9", "d10", "d4", "d5", "d7", "d12", "d11", "d30"], + ); const plan = buildMethodFollowupPlan({ evidence: [ { status: "confirmed", domain: "relationship", datePrecision: "year", occurredFrom: "2018-01-01", occurredTo: null }, @@ -353,7 +355,10 @@ test("D11-only window change folds into finance follow-up without delaying adopt assert.equal(observations.find((item) => item.layer === "d11")?.candidates_differ, true); assert.equal(observations.find((item) => item.layer === "d11")?.ask_theme, "finance_change"); assert.equal(observations.find((item) => item.layer === "d30")?.ask_theme, "health_pressure"); - assert.equal(observations.some((item) => item.layer === "d2"), false); + assert.deepEqual( + observations.map((item) => item.layer), + ["d9", "d10", "d4", "d5", "d7", "d12", "d11", "d30"], + ); const plan = buildMethodFollowupPlan({ evidence: [ { status: "confirmed", domain: "relationship", datePrecision: "year", occurredFrom: "2018-01-01", occurredTo: null },