From 78ca3f1ee3a0cf7abb16f01e70508545291d58c0 Mon Sep 17 00:00:00 2001 From: API Changelog Bot Date: Mon, 21 Sep 2026 16:23:33 +0800 Subject: [PATCH] =?UTF-8?q?feat(guard):=20E=5FWAIT=5FLANGUAGE=20=E8=A1=A5?= =?UTF-8?q?=E7=AC=AC=E5=9B=9B=E7=B1=BB=E2=80=94=E2=80=94=E6=8A=8A=E3=80=8C?= =?UTF-8?q?=E4=BB=80=E4=B9=88=E6=97=B6=E5=80=99=E4=B8=8A=E7=BA=BF=E3=80=8D?= =?UTF-8?q?=E5=86=99=E6=88=90=E6=B4=BE=E7=BB=99=E5=89=8D=E7=AB=AF=E7=9A=84?= =?UTF-8?q?=E5=8A=A8=E4=BD=9C=E9=A1=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 今天上午装这条守卫时只想到「等/待/另发」这一种形态,当天下午就被另一种形态绕过去了: 20_7443 正文写着「上生产前请与后端确认这个开关的状态」与「生产环境未开」, mmg 据此来问上线时间、并要求「后端把生产开关打开」——而守卫全绿,因为这两句 一个词表词都没用上。它们把不确定性包装成了「请你去确认」,语法换了,作用一样: 读者只能停在那里等一个他查不到的状态。 判据仍是那一句:这条影响他「怎么写代码」,还是只影响他「什么时候开始写」。 上线时点属后者。前端需不需要同步上线,由 frontend_action_required 与模板里 「前端是否必须同步上线」那个结构化字段承载,正文自由文本里不该再出现。 词表先对全仓 1068 份 changelog 实跑,只留零命中且零正当用法的 12 个词。剔除两个: 「何时开」 —— 误伤「保护何时开始生效」「窗口何时开过」 「生产上线」—— 误伤 07_5640「生产上线需配 annual-direct-plan-id」,那是真契约边界 部署时间戳没做成规则:该形态全仓 0 命中,分辨力无从验证,而必须放行的 「带时刻实测取证句」有 690 处——判据的误伤面远大于收益时,门禁只会教人绕开它。 这一类只能靠 §2.1 的条文和复盘接住,机器接不住,如实记在注释里。 测试:新增 2 条阳性 + 1 条阴性对照,阴性那条与阳性只差一个「上生产前」, 用来钉住分界线(「收到 809009 找后端确认该环境的开关」是运维处置,必须放行)。 npm test 59 项 58 绿;唯一的红是存量的 E_ALIAS_STATE(11_7510,与本次无关)。 Co-Authored-By: Claude Opus 5 (1M context) --- scripts/validate-changelog-frontmatter.mjs | 15 +++++++++ tests/validate-changelog-frontmatter.test.mjs | 33 +++++++++++++++++++ 2 files changed, 48 insertions(+) diff --git a/scripts/validate-changelog-frontmatter.mjs b/scripts/validate-changelog-frontmatter.mjs index a12d0f56..2b82914e 100644 --- a/scripts/validate-changelog-frontmatter.mjs +++ b/scripts/validate-changelog-frontmatter.mjs @@ -445,6 +445,21 @@ const WAIT_LANGUAGE_PHRASES = [ // 三、直接叫停对接 '请先等', '暂缓对接', '暂不要对接', '先不要对接', '先别对接', '我们这边还在', '该缺陷已在修', + // 四、把「什么时候上线」写成派给前端的动作项 + // (2026-09-21 复盘补:20_7443 写了「上生产前请与后端确认这个开关的状态」, + // mmg 因此来问上线时间与「请后端把生产开关打开」。上面三类词表一个都没拦住—— + // 它们匹配的是「等/待/另发」,而这句把不确定性包装成了「请你去确认」。 + // 上线时点不影响「怎么写代码」,只影响「什么时候开始写」,属本规则该删的那一半。 + // 前端需不需要同步上线,有 frontend_action_required 与模板里「前端是否必须同步上线」 + // 那个结构化字段承载,正文自由文本里不再重复。) + '上生产前请', '上生产前与', '上线前请', '上线前与', + '上线时间', '上线排期', '上生产时间', '何时上线', + '由谁开', '谁来开', '排期确定后', '排期确认后', + // ⚙ 词表已对全仓 1068 份 changelog 实跑,以上全部零命中。两个候选词被剔除: + // 「何时开」——误伤「保护何时开始生效」「窗口何时开过」 + // 「生产上线」——误伤 07_5640「生产上线需配 annual-direct-plan-id」(真契约边界) + // 部署时间戳没做成规则:该形态全仓 0 命中(分辨力验不了), + // 而必须放行的「带时刻实测取证句」有 690 处,误伤面远大于收益。 // ⚠️ 不要把「暂不可用 / 暂时不可用」加回来:2026-09-21 对全仓 1968 份 changelog 实测, // 它命中的几乎全是错误码表与响应示例里的**文案**(如「584105 结算字典暂时不可用,请稍后重试」), // 那正是本规则必须放行的契约内容。判据的分辨力不够时,门禁只会教人绕开它。 diff --git a/tests/validate-changelog-frontmatter.test.mjs b/tests/validate-changelog-frontmatter.test.mjs index 5e7facd8..b22e6b1b 100644 --- a/tests/validate-changelog-frontmatter.test.mjs +++ b/tests/validate-changelog-frontmatter.test.mjs @@ -187,6 +187,39 @@ test('accepts coverage limits that do not ask the frontend to wait', () => { assert.deepEqual(errors.filter(({ code }) => code === 'E_WAIT_LANGUAGE'), []); }); +test('rejects turning launch timing into an action item for the frontend', () => { + const value = document().replace('## 七、不影响范围', `## 六.8、环境开关 + +- 上生产前请与后端确认这个开关的状态,否则提交会被拒。 + +## 七、不影响范围`); + const errors = validateV2Document(FILE, value, { requireV2: true }); + assert.ok(errors.some(({ code }) => code === 'E_WAIT_LANGUAGE')); +}); + +test('rejects asking who flips the production switch and when', () => { + const value = document().replace('## 七、不影响范围', `## 六.8、环境开关 + +- 生产开关由谁开、何时上线,待排期确认后另行同步。 + +## 七、不影响范围`); + const errors = validateV2Document(FILE, value, { requireV2: true }); + assert.ok(errors.some(({ code }) => code === 'E_WAIT_LANGUAGE')); +}); + +// 阴性对照:撞上错误码时的运维处置建议是「怎么诊断」,不是「什么时候开工」,必须放行。 +// 它与上一条只差一个「上生产前」,正好钉住本规则的分界线。 +test('accepts runbook advice for an error code without launch timing', () => { + const value = document().replace('## 七、不影响范围', `## 六.8、环境开关 + +- 收到 \`809009\` 即表示所在环境的开关没开,找后端确认该环境的开关——这不是数据问题,重试与补数据均无效。 +- 窗口何时开过、保护何时开始生效,都可从时间线事件读到。 + +## 七、不影响范围`); + const errors = validateV2Document(FILE, value, { requireV2: true }); + assert.deepEqual(errors.filter(({ code }) => code === 'E_WAIT_LANGUAGE'), []); +}); + test('rejects backend and gateway pending at publication', () => { const errors = validateV2Document( FILE,