From 85c15d2c06ca0e593892126d5fe0be5cdce87159 Mon Sep 17 00:00:00 2001 From: shin-core <153108882+shin-core@users.noreply.github.com> Date: Wed, 29 Jul 2026 17:40:35 +0900 Subject: [PATCH] fix(content-lane): canonicalize the issue-body path token in checkContentLaneDeliverable MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `checkContentLaneDeliverable` tested each `extractPathTokens` token against `matchesSpec` RAW, but the spec patterns are compiled by `globToRegExp` against a canonicalized (lowercased) path with no `i` flag — so they only match lowercase input. An issue body naming its target with any capitalization (`Registry/Subnets/Foo.json`) produced no `mentionedPath`, and the deliverable gate silently returned `not-applicable` — the exact "test-only PR closes a content issue without delivering the content" gap this check exists to close. The `changedFiles` side already canonicalizes; the issue-body side never did. Canonicalize the token at the point of test (mirroring `classifyRegistryPrScope`'s `matchesPattern`) while keeping the ORIGINAL token for `mentionedPath`, which is quoted verbatim into the public PR comment. Already-lowercase paths are unchanged. Closes #9667 --- src/review/content-lane/registry-logic.ts | 5 +++- test/unit/content-lane-registry-logic.test.ts | 29 +++++++++++++++++++ 2 files changed, 33 insertions(+), 1 deletion(-) diff --git a/src/review/content-lane/registry-logic.ts b/src/review/content-lane/registry-logic.ts index 16b3b1df8..d55a2a94d 100644 --- a/src/review/content-lane/registry-logic.ts +++ b/src/review/content-lane/registry-logic.ts @@ -930,7 +930,10 @@ export function checkContentLaneDeliverable( issueTitle?: string, ): ContentLaneDeliverableCheck { const matchesSpec = (candidate: string): boolean => spec.entryFilePattern.test(candidate) || (spec.providerFilePattern?.test(candidate) ?? false); - const mentionedPath = extractPathTokens(issueText).find(matchesSpec); + // globToRegExp compiles the spec patterns against a CANONICALIZED (lowercased) path, so test canonicalize(token) + // — exactly as the changedFiles side below and classifyRegistryPrScope's matchesPattern already do — while + // keeping the ORIGINAL token for mentionedPath, which is quoted verbatim into the public PR comment (#9667). + const mentionedPath = extractPathTokens(issueText).find((token) => matchesSpec(canonicalize(token))); const titleImplies = !mentionedPath && Boolean(issueTitle && spec.issueTitleImpliesEntryPattern?.test(issueTitle)); if (!mentionedPath && !titleImplies) return { verdict: "not-applicable" }; const delivered = changedFiles.some((file) => matchesSpec(canonicalize(file))); diff --git a/test/unit/content-lane-registry-logic.test.ts b/test/unit/content-lane-registry-logic.test.ts index f017c0798..8327d1ec3 100644 --- a/test/unit/content-lane-registry-logic.test.ts +++ b/test/unit/content-lane-registry-logic.test.ts @@ -676,6 +676,35 @@ describe("checkContentLaneDeliverable (generic, spec-driven — #content-lane-de expect(checkContentLaneDeliverable(entryOnlySpec, issueText, ["tests/foo.test.mjs"]).verdict).toBe("missing"); }); + describe("canonicalizes the issue-body path token so mixed-case paths still fire the gate (#9667)", () => { + it("is 'missing' for a mixed-case issue path against a non-delivering PR, quoting the ORIGINAL token", () => { + const issueText = "Add the surfaces to Registry/Subnets/Foo.json."; + const result = checkContentLaneDeliverable(spec, issueText, ["tests/foo-verify.test.mjs"]); + // The gate fires (was silently not-applicable before #9667), and mentionedPath stays verbatim for the + // public comment — NOT lowercased. + expect(result).toEqual({ verdict: "missing", mentionedPath: "Registry/Subnets/Foo.json" }); + }); + + it("is 'delivered' for that same mixed-case issue path when the PR changes the canonical file", () => { + const issueText = "Add the surfaces to Registry/Subnets/Foo.json."; + expect(checkContentLaneDeliverable(spec, issueText, ["registry/subnets/foo.json"]).verdict).toBe("delivered"); + }); + + it("leaves already-lowercase path behaviour byte-identical (delivered and missing)", () => { + const issueText = "Add registry/subnets/foo.json."; + expect(checkContentLaneDeliverable(spec, issueText, ["registry/subnets/foo.json"]).verdict).toBe("delivered"); + expect(checkContentLaneDeliverable(spec, issueText, ["tests/foo.test.mjs"])).toEqual({ + verdict: "missing", + mentionedPath: "registry/subnets/foo.json", + }); + }); + + it("still returns not-applicable for an issue path that matches no spec pattern (not over-broadened)", () => { + const issueText = "Update the docs at Docs/Readme.md — nothing registry-shaped here."; + expect(checkContentLaneDeliverable(spec, issueText, ["registry/subnets/foo.json"])).toEqual({ verdict: "not-applicable" }); + }); + }); + // #content-lane-deliverable follow-up (metagraphed #7060-class gap): the literal-path scan alone is BLIND // to metagraphed's own ~120 "MCP execute: verify + wire SN*" issues, whose bodies write the registry path // with a generic `` documentation placeholder rather than the real resolved filename — confirmed