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}`));