From ec98b12196f1e43812cdc49fe9b14249221dc8dc Mon Sep 17 00:00:00 2001 From: bitfathers94 <237535319+bitfathers94@users.noreply.github.com> Date: Wed, 29 Jul 2026 19:09:07 +0000 Subject: [PATCH] fix(content-lane): canonicalize issue-body path tokens in checkContentLaneDeliverable MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The spec's entry/provider patterns are compiled by globToRegExp against a canonicalized (lowercased, no i flag) path, so an issue body writing the target file with any capitalization produced no mentionedPath and the deliverable gate silently reported not-applicable — letting a test-only PR close a content issue without delivering the content. Test each extracted token via canonicalize(), mirroring classifyRegistryPrScope's matchesPattern, while keeping the original token for the public-comment mentionedPath. Already-lowercase paths are byte-identical to before. --- src/review/content-lane/registry-logic.ts | 7 +++- test/unit/content-lane-registry-logic.test.ts | 32 +++++++++++++++++++ 2 files changed, 38 insertions(+), 1 deletion(-) diff --git a/src/review/content-lane/registry-logic.ts b/src/review/content-lane/registry-logic.ts index 921b018b2e..3a3cfa63d6 100644 --- a/src/review/content-lane/registry-logic.ts +++ b/src/review/content-lane/registry-logic.ts @@ -930,7 +930,12 @@ 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); + // Mirror classifyRegistryPrScope's matchesPattern: the spec patterns are compiled by globToRegExp against a + // CANONICALIZED path (lowercased + `./`-stripped + `\`→`/`, no `i` flag), so an issue body writing the path with + // any capitalization must be matched on canonicalize(token) — otherwise it produces no mentionedPath and the gate + // silently reports not-applicable. Keep the ORIGINAL token for mentionedPath: it is quoted verbatim into the + // public PR comment and must match what the contributor and maintainer see in the issue. + 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 f017c0798b..a4ee8f5449 100644 --- a/test/unit/content-lane-registry-logic.test.ts +++ b/test/unit/content-lane-registry-logic.test.ts @@ -676,6 +676,38 @@ describe("checkContentLaneDeliverable (generic, spec-driven — #content-lane-de expect(checkContentLaneDeliverable(entryOnlySpec, issueText, ["tests/foo.test.mjs"]).verdict).toBe("missing"); }); + // #9667: the issue-body scan must canonicalize each token before testing it against the spec (globToRegExp + // compiles the patterns against a canonicalized/lowercased path with no `i` flag), exactly as the changedFiles + // side already does — while quoting the ORIGINAL, non-canonicalized token in the public-comment mentionedPath. + describe("canonicalizes the issue-body path token before matching (#9667)", () => { + it("is 'missing' for a mixed-case issue path the PR doesn't deliver, quoting the ORIGINAL token verbatim", () => { + const issueText = "Add the missing surfaces to Registry/Subnets/Foo.json please."; + expect(checkContentLaneDeliverable(spec, issueText, ["tests/foo-verify.test.mjs"])).toEqual({ + verdict: "missing", + mentionedPath: "Registry/Subnets/Foo.json", + }); + }); + + it("is 'delivered' for that mixed-case issue path when the PR changes the canonical file", () => { + const issueText = "Add the missing surfaces to Registry/Subnets/Foo.json please."; + expect(checkContentLaneDeliverable(spec, issueText, ["registry/subnets/foo.json"])).toEqual({ verdict: "delivered" }); + }); + + it("keeps the all-lowercase path behaviour byte-identical (both delivered and missing)", () => { + const issueText = "Add the missing surfaces to registry/subnets/foo.json."; + expect(checkContentLaneDeliverable(spec, issueText, ["registry/subnets/foo.json"])).toEqual({ verdict: "delivered" }); + expect(checkContentLaneDeliverable(spec, issueText, ["tests/foo-verify.test.mjs"])).toEqual({ + verdict: "missing", + mentionedPath: "registry/subnets/foo.json", + }); + }); + + it("stays not-applicable for a mixed-case path token that matches NO spec pattern (not over-broadened)", () => { + const issueText = "See the config in Docs/Guide/Setup.md for details."; + 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