From ae069b00a9ffe5158f0c8102b208ca75ca3f766f Mon Sep 17 00:00:00 2001 From: API Changelog Bot Date: Sun, 20 Sep 2026 11:04:32 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20frontmatter=20=E6=9C=AA=E8=BD=AC?= =?UTF-8?q?=E4=B9=89=E7=9A=84=E5=BC=95=E5=8F=B7=E8=AE=A9=E5=85=83=E6=95=B0?= =?UTF-8?q?=E6=8D=AE=E5=AF=B9=E6=89=80=E6=9C=89=E6=B6=88=E8=B4=B9=E6=96=B9?= =?UTF-8?q?=E9=9A=90=E5=BD=A2=E2=80=94=E2=80=94=E4=BF=AE=2019=5F7443=20+?= =?UTF-8?q?=20=E7=BB=99=E6=A0=A1=E9=AA=8C=E5=99=A8=E8=A1=A5=E8=AF=AD?= =?UTF-8?q?=E6=B3=95=E5=B1=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 现象:Gitea 上打开这份 changelog 看不到元数据表,页面表现就是「这份文件没有元数据」, 而文件里其实写着完整的 16 个键。 根因:status_note 是 YAML 双引号标量,正文里夹着未转义的 ASCII 双引号 (...请勿据此对外宣称"确认/改派已验证可用")。YAML 在第一个内部引号处截断, 整块 frontmatter 解析失败,Gitea 于是静默不渲染。 为什么一直没人发现:本仓校验器的 parseFrontmatter 是朴素行解析——按第一个冒号切开、 两端引号成对就剥掉——它对「这份 YAML 下游读不读得了」零分辨力。于是这类文件长期处于 「校验器 PASS,而 Gitea 与任何 YAML 解析器都读不到」的状态,没有任何一处报错。 校验器回答的是自己那套宽松读法,不是消费方的读法。 改动两部分: 一、修 19_7443 那两个引号。转义只改字节不改语义,验过三条:改后 yaml.safe_load 成功、 解析出的值与作者本意逐字相同、正文一个字节没动。 二、给校验器补上它完全没有的那一层:validateFrontmatterSyntax,新错误码 E_FRONTMATTER_SYNTAX。本仓刻意零依赖,所以不引 YAML 库,按实际出过的三类伤写 定向判据:内部未转义双引号 / 收尾引号丢失 / 非法转义序列。语法校验排在所有按键 取值的校验之前——frontmatter 读不了的话,后面那些校验都是在校验一份只有本脚本 能读的东西。 自验两个方向:三种人造伤各自被拦(exit=1);全仓 1030 份逐批跑完,新增错误码 E_FRONTMATTER_SYNTAX 命中 0 次,无连带误伤;独立口径用真 YAML 解析器扫全仓, 546 份带 frontmatter 的全部可解析。 ⚠️ 同一毛病另有 7 份存量文件未修(05_5530 / 08_5592 / 06_7149 / 10_7328 / 13_7625 / 15_7327 / 15_fund-account)。它们的元数据同样对 Gitea 隐形,修法已验证可行, 但每份都带着 1~3 条与本次无关的历史门禁错误(E_FRONTEND_STATE「verified 必须填写 target_release」为主,另有 E_AUTHOR / E_API_TEMPLATE / E_BACKEND_STATUS), 推不上去。补那些字段需要编造内容,不做。 🔴 值得记一笔的因果:正是门禁这个严格度让这 8 份一直坏着——谁想顺手修一下, 就会撞上一堆与自己无关的历史错误然后放弃。修复脚本留在 D:/work2/_scratch/changelog-fm-fix/,等 wx 决定是给存量文件开豁免还是补齐字段。 Co-Authored-By: Claude Opus 5 (1M context) --- ...�¯-订正18_7443的frontend_status判定-修改接口-管理后台.md | 2 +- scripts/validate-changelog-frontmatter.mjs | 70 ++++++++++++++++++- 2 files changed, 69 insertions(+), 3 deletions(-) diff --git a/changelogs-v2/2026-09/19_7443_TRANSFER用车需求提交开关测试服已开启-订正18_7443的frontend_status判定-修改接口-管理后台.md b/changelogs-v2/2026-09/19_7443_TRANSFER用车需求提交开关测试服已开启-订正18_7443的frontend_status判定-修改接口-管理后台.md index 1fd33fdf..857fb988 100644 --- a/changelogs-v2/2026-09/19_7443_TRANSFER用车需求提交开关测试服已开启-订正18_7443的frontend_status判定-修改接口-管理后台.md +++ b/changelogs-v2/2026-09/19_7443_TRANSFER用车需求提交开关测试服已开启-订正18_7443的frontend_status判定-修改接口-管理后台.md @@ -12,7 +12,7 @@ frontend_owner: "" frontend_ref: "" target_release: "" verified_at: "" -status_note: "订正件,不改代码,只改测试服 Nacos 配置 + 重启。18_7443 判 not_required 的理由是「809009 开关关闭,产品上产不出 TRANSFER 需求,4 个新错误码前端不可达」——该理由 2026-09-19 15:33:32 起已不成立:hl.order.requirement.transfer-kind-submit-enabled 在测试服 namespace=test 的 hl-order-service-v3-test.yml 被置 true 并随 order-v3 两实例重启(15:36:32/15:36:46)生效。本文撰写前二次复验(Nacos 直读 17:00:35 CST 仍为 true;SUPER_ADMIN 身份两次真实网关调用穿透了原先必现的 809009 分支,其中一次落库成功)。三条边界必须同时看:①代码默认值仍是 false(@Value 未改),生产环境未动;②RequirementService 无 @RefreshScope,改配置必须重启才生效,这次也确实重启了;③本文撰写过程中意外发现 18_7443/19_7443(未推送草稿) 关于「confirm/change 仍必现 605041」的既有说法本身已经过期——AC-24 修复(PR #7952, commit c527e48c3, 2026-09-18 18:55 合入 dev-v3)已经是当前测试服 order-v3(ca0656f1c)与 fleet(31fd5b5e6) 两个正在跑的字节的祖先提交,即该修复本身也已随各自服务的最近一次部署上线,但本文未对 confirm/change 端点做真实网关复测,这一句只有源码祖先关系与部署记录支撑,不构成端到端功能验证,请勿据此对外宣称"确认/改派已验证可用"。frontend_status 由 not_required 改判 pending:原判定的前提(不可达)已不成立,但生产环境仍不可达、且是否现在启动前端集成属产品排期决定,不是本文档能替 mmg 拍板的,故不写 not_required(意味着无需关注),也不写 claimed/implemented(没有证据),交给 mmg 自行判断是否现在启动。" +status_note: "订正件,不改代码,只改测试服 Nacos 配置 + 重启。18_7443 判 not_required 的理由是「809009 开关关闭,产品上产不出 TRANSFER 需求,4 个新错误码前端不可达」——该理由 2026-09-19 15:33:32 起已不成立:hl.order.requirement.transfer-kind-submit-enabled 在测试服 namespace=test 的 hl-order-service-v3-test.yml 被置 true 并随 order-v3 两实例重启(15:36:32/15:36:46)生效。本文撰写前二次复验(Nacos 直读 17:00:35 CST 仍为 true;SUPER_ADMIN 身份两次真实网关调用穿透了原先必现的 809009 分支,其中一次落库成功)。三条边界必须同时看:①代码默认值仍是 false(@Value 未改),生产环境未动;②RequirementService 无 @RefreshScope,改配置必须重启才生效,这次也确实重启了;③本文撰写过程中意外发现 18_7443/19_7443(未推送草稿) 关于「confirm/change 仍必现 605041」的既有说法本身已经过期——AC-24 修复(PR #7952, commit c527e48c3, 2026-09-18 18:55 合入 dev-v3)已经是当前测试服 order-v3(ca0656f1c)与 fleet(31fd5b5e6) 两个正在跑的字节的祖先提交,即该修复本身也已随各自服务的最近一次部署上线,但本文未对 confirm/change 端点做真实网关复测,这一句只有源码祖先关系与部署记录支撑,不构成端到端功能验证,请勿据此对外宣称\"确认/改派已验证可用\"。frontend_status 由 not_required 改判 pending:原判定的前提(不可达)已不成立,但生产环境仍不可达、且是否现在启动前端集成属产品排期决定,不是本文档能替 mmg 拍板的,故不写 not_required(意味着无需关注),也不写 claimed/implemented(没有证据),交给 mmg 自行判断是否现在启动。" updated_at: "2026-09-19" base: "dev-v3" --- diff --git a/scripts/validate-changelog-frontmatter.mjs b/scripts/validate-changelog-frontmatter.mjs index fcaa1214..50c07973 100644 --- a/scripts/validate-changelog-frontmatter.mjs +++ b/scripts/validate-changelog-frontmatter.mjs @@ -306,7 +306,68 @@ export function parseFrontmatter(text) { } metadata[key] = fieldValue; } - return { metadata, body: value.slice(end + 5) }; + return { metadata, body: value.slice(end + 5), raw: value.slice(4, end) }; +} + +/** + * 校验 frontmatter 是不是**合法 YAML**。 + * + * 为什么需要这一层:上面的 parseFrontmatter 是朴素行解析(按第一个 ':' 切、两端引号成对就剥), + * 它对「这份 YAML 下游读不读得了」零分辨力。2026-09-20 全仓实测 8 份文件因此长期处于 + * 「本校验器 PASS,而 Gitea 与任何 YAML 解析器都读不到元数据」的状态——页面上看起来就是 + * 「这份 changelog 没有元数据」,而没有任何一处报错。校验器回答的是自己那套宽松读法, + * 不是消费方的读法。 + * + * 本仓刻意零依赖,所以不引 YAML 库,改为按实际出过的两类伤写定向判据: + * ① 双引号标量内部出现**未转义**的 ASCII 双引号 ⇒ YAML 在第一个处截断(8 份里 7 份是这个); + * ② 反斜杠后跟着非法转义字符(如 heredoc 残留的 `\【`)⇒ scanner 报错。 + * 外加「以 " 开头却不以 " 结尾」这种收尾引号丢失的形态。 + */ +export function validateFrontmatterSyntax(raw) { + const BACKSLASH = String.fromCharCode(92); + const VALID_ESCAPE = new Set( + ['0', 'a', 'b', 't', 'n', 'v', 'f', 'r', 'e', '"', '/', BACKSLASH, 'N', '_', 'L', 'P', 'x', 'u', 'U', ' '], + ); + const problems = []; + for (const line of String(raw ?? '').split('\n')) { + const separator = line.indexOf(':'); + if (separator < 0) { + continue; + } + const key = line.slice(0, separator).trim(); + const value = line.slice(separator + 1).trim(); + if (!value.startsWith('"')) { + continue; + } + if (value.length < 2 || !value.endsWith('"')) { + problems.push(`${key} 的值以双引号开头却不以双引号结尾(收尾引号丢失?)`); + continue; + } + const inner = value.slice(1, -1); + let bare = 0; + let badEscape = ''; + for (let i = 0; i < inner.length; i += 1) { + const ch = inner[i]; + if (ch === BACKSLASH) { + const next = inner[i + 1] ?? ''; + if (!VALID_ESCAPE.has(next)) { + badEscape ||= next; + } + i += 1; + continue; + } + if (ch === '"') { + bare += 1; + } + } + if (bare > 0) { + problems.push(`${key} 内部有 ${bare} 个未转义的 ASCII 双引号;YAML 会在第一个处截断整块 frontmatter`); + } + if (badEscape) { + problems.push(`${key} 内部有非法转义序列(反斜杠后跟 ${JSON.stringify(badEscape)});要字面反斜杠请写两个`); + } + } + return problems; } export function validateFrontendState(metadata) { @@ -363,7 +424,7 @@ export function validateFrontendTransition(current, target, reason = '') { } export function validateV2Document(file, text, { requireV2 = false } = {}) { - const { metadata, body } = parseFrontmatter(text); + const { metadata, body, raw } = parseFrontmatter(text); if (!metadata) { return requireV2 ? [ruleError('E_FRONTMATTER', file, '新增 changelog 缺少 YAML Front Matter')] : []; } @@ -373,6 +434,11 @@ export function validateV2Document(file, text, { requireV2 = false } = {}) { : []; } const errors = []; + // 语法先于语义:frontmatter 读不了的话,下面所有按键取值的校验都是在校验一份 + // 只有本脚本能读、消费方读不到的东西。这一条对存量文件同样生效(不受 requireV2 限制)。 + for (const problem of validateFrontmatterSyntax(raw)) { + errors.push(ruleError('E_FRONTMATTER_SYNTAX', file, problem)); + } for (const key of REQUIRED_KEYS) { if (!(key in metadata)) { errors.push(ruleError('E_REQUIRED', file, `frontmatter 缺少 ${key}`));