From 0511bc48c763cb043c0060f7c1e89b3930701d50 Mon Sep 17 00:00:00 2001 From: Jesse_Chen Date: Wed, 26 Aug 2026 18:32:25 +0800 Subject: [PATCH] fix(web): type holdout followups and stop duplicate core re-exports Staging publish next build failed after validate skipped production TypeScript, so holdout unions and the decision barrel never reached the image. Co-authored-by: Cursor --- docs/BUG_HISTORY.md | 16 ++++++++++++++ .../lib/rectification-agentic/core/index.ts | 6 +++++- .../v9/method-followup.ts | 4 ++-- .../rectification-decide-next-action.test.ts | 21 +++++++++++++++++++ 4 files changed, 44 insertions(+), 3 deletions(-) diff --git a/docs/BUG_HISTORY.md b/docs/BUG_HISTORY.md index 577af860..ae5671b5 100644 --- a/docs/BUG_HISTORY.md +++ b/docs/BUG_HISTORY.md @@ -5771,6 +5771,22 @@ - 复发自:BUG-367(点选身份与滚动);BUG-366(覆盖完成被当成收敛);BUG-393(出卡即完成) - 修复版本:待发布 +## BUG-395 | staging publish 的 next build 被 holdout 类型和重复 re-export 挡住 + +- 状态:resolved +- 首次发现:2026-08-26 +- 最近更新:2026-08-26 +- 影响面:Gitea `backend-quality-gate.yml` `publish`、`deploy/railway-web.Dockerfile` 的 `RUN npm run build`、`core/index.ts`、`method-followup.ts` +- 用户现象:向 `staging` 推送 `a0ce55f0` 后 quality gate run `2088` validate 通过,publish 在 web 镜像 `next build` 失败。没有 dispatch `Deploy staging`。公网仍为上一成功 SHA `7718317d`。第一次失败是 publish exact-SHA checkout 三次超时;重跑后 checkout 成功,类型检查才暴露。 +- 触发条件:staging push 的 validate 跳过 `npm run build`;publish 才在 Docker 里跑 TypeScript。 +- 根因:(1) `core/index.ts` 对 `decide-next-action.ts` 和 `rectification-decision.ts` 都 `export *`,两者都导出 `offerSessionKinds` / `sessionKindFromNextAction`,触发 TS2308。(2) BUG-394 的 holdout 追问写成 `method_id: "holdout_validation"` 和 `ask_theme: "holdout"`,未加入 `MethodFollowup` 联合类型。 +- 修复:barrel 只具名导出 `decideNextAction` 及其输入类型,session helpers 只从 `rectification-decision.ts` 再导出。`MethodFollowup` 联合类型补上 `holdout_validation` 与 `holdout`。不改 Skill `10.0.11`。 +- 验证:`npx tsc --noEmit` 不得再报 `core/index.ts` TS2308 或 `method-followup.ts` TS2322;`frontend/tests/rectification-decide-next-action.test.ts`。 +- 防复发:`core/index.ts` 不得再 `export *` 两个都导出 session helpers 的模块。holdout 追问的 `method_id` / `ask_theme` 必须在 `MethodFollowup` 联合类型里。改决策 barrel 或 holdout followup 后必须跑 `npx tsc --noEmit` 或等价的 `next build`。staging push 的类型回归仍只在 publish Docker 暴露。 +- 相关记录:BUG-309、BUG-365、BUG-371、BUG-387、BUG-394 +- 复发自:BUG-309(validate 跳过 `next build`,publish 才暴露类型错误);BUG-394 重复导出 session helpers 且 holdout 种类未进联合类型 +- 修复版本:待发布 + ## BUG-379 | 生时纠正已记入学后仍编造高考年并再问入学 - 状态:resolved diff --git a/frontend/src/lib/rectification-agentic/core/index.ts b/frontend/src/lib/rectification-agentic/core/index.ts index 75ccd5b5..c7e9e07c 100644 --- a/frontend/src/lib/rectification-agentic/core/index.ts +++ b/frontend/src/lib/rectification-agentic/core/index.ts @@ -11,7 +11,11 @@ export * from "./probes-from-engine.ts"; export * from "./decision-fingerprint.ts"; export * from "./compose-receipt.ts"; export * from "./candidate-separation.ts"; -export * from "./decide-next-action.ts"; +export { + decideNextAction, + type DecideNextActionInput, + type RectificationNextAction, +} from "./decide-next-action.ts"; export * from "./rectification-decision.ts"; export * from "./credible-range.ts"; export * from "./candidate-contrast-packet.ts"; diff --git a/frontend/src/lib/rectification-agentic/v9/method-followup.ts b/frontend/src/lib/rectification-agentic/v9/method-followup.ts index 1cb4a7b2..2f84bf6b 100644 --- a/frontend/src/lib/rectification-agentic/v9/method-followup.ts +++ b/frontend/src/lib/rectification-agentic/v9/method-followup.ts @@ -85,9 +85,9 @@ export type MethodCoverage = Readonly<{ }>; export type MethodFollowup = Readonly<{ - method_id: "dasha_events" | "d9_relationship" | "d10_career" | "d4_home" | "d5_education" | "relatives" | "d2_finance" | "d30_health" | "appearance" | "marks" | "occupation" | "horary" | "active_focus" | "nakshatra_boundary" | "oos_blind" | "reverse_verify"; + method_id: "dasha_events" | "d9_relationship" | "d10_career" | "d4_home" | "d5_education" | "relatives" | "d2_finance" | "d30_health" | "appearance" | "marks" | "occupation" | "horary" | "active_focus" | "nakshatra_boundary" | "oos_blind" | "reverse_verify" | "holdout_validation"; intent: string; - ask_theme: "dated_event" | "relationship_style" | "career_style" | "home_change" | "education_style" | "family_event" | "finance_change" | "health_pressure" | "appearance" | "marks" | "occupation" | "horary" | "active_focus" | "nakshatra_trait" | "oos_blind"; + ask_theme: "dated_event" | "relationship_style" | "career_style" | "home_change" | "education_style" | "family_event" | "finance_change" | "health_pressure" | "appearance" | "marks" | "occupation" | "horary" | "active_focus" | "nakshatra_trait" | "oos_blind" | "holdout"; domain: string | null; kind_hint: string | null; user_prompt_hint: string; diff --git a/frontend/tests/rectification-decide-next-action.test.ts b/frontend/tests/rectification-decide-next-action.test.ts index 816ab2d6..87843529 100644 --- a/frontend/tests/rectification-decide-next-action.test.ts +++ b/frontend/tests/rectification-decide-next-action.test.ts @@ -1,4 +1,5 @@ import assert from "node:assert/strict"; +import { readFileSync } from "node:fs"; import test from "node:test"; import { @@ -351,3 +352,23 @@ test("final report does not claim executed techniques without calculationResultI assert.match(report, /\| D10 \| executed \|/); assert.doesNotMatch(report, /当前推荐/); }); + +test("core barrel re-exports decideNextAction without duplicating session helpers", () => { + const index = readFileSync(new URL("../src/lib/rectification-agentic/core/index.ts", import.meta.url), "utf8"); + assert.match( + index, + /export \{\s*decideNextAction,\s*type DecideNextActionInput,\s*type RectificationNextAction,\s*\} from "\.\/decide-next-action\.ts"/, + ); + assert.doesNotMatch(index, /export \* from "\.\/decide-next-action\.ts"/); + assert.match(index, /export \* from "\.\/rectification-decision\.ts"/); +}); + +test("MethodFollowup unions include holdout validation kinds used by next_followup", () => { + const source = readFileSync(new URL("../src/lib/rectification-agentic/v9/method-followup.ts", import.meta.url), "utf8"); + const methodId = source.match(/export type MethodFollowup = Readonly<\{[\s\S]*?method_id: ([^;]+);/)?.[1] ?? ""; + const askTheme = source.match(/export type MethodFollowup = Readonly<\{[\s\S]*?ask_theme: ([^;]+);/)?.[1] ?? ""; + assert.match(methodId, /"holdout_validation"/); + assert.match(askTheme, /"holdout"/); + assert.match(source, /ask_theme: "holdout"/); + assert.match(source, /method_id: "holdout_validation"/); +});