diff --git a/src/review/linked-issue-label-propagation-fetch.ts b/src/review/linked-issue-label-propagation-fetch.ts index 1db7e1864b..380c6d07c5 100644 --- a/src/review/linked-issue-label-propagation-fetch.ts +++ b/src/review/linked-issue-label-propagation-fetch.ts @@ -148,10 +148,21 @@ export async function fetchLinkedIssueLabelsForPropagation(args: { ); const prAuthorLogin = args.prAuthorLogin?.toLowerCase(); const prMergedAt = args.prMergedAt ?? null; + const mappingsByIssueLabel = new Map(); + for (const mapping of args.mappings ?? []) { + const issueLabel = mapping.issueLabel.toLowerCase(); + mappingsByIssueLabel.set(issueLabel, [...(mappingsByIssueLabel.get(issueLabel) ?? []), mapping]); + } const relaxableLabels = new Set( - (args.mappings ?? []) - .filter((mapping) => mapping.trustMaintainerAuthoredIssue === true || mapping.trustMaintainerAuthoredIssueForReward === true) - .map((mapping) => mapping.issueLabel.toLowerCase()), + [...mappingsByIssueLabel.entries()] + .filter(([, mappings]) => + mappings.every( + (mapping) => + mapping.trustMaintainerAuthoredIssue === true || + mapping.trustMaintainerAuthoredIssueForReward === true, + ), + ) + .map(([issueLabel]) => issueLabel), ); const results = await Promise.all( linkedIssues.map((issueNumber) => diff --git a/test/unit/linked-issue-label-propagation-fetch.test.ts b/test/unit/linked-issue-label-propagation-fetch.test.ts index 7dbbe8c94f..2c95f308c6 100644 --- a/test/unit/linked-issue-label-propagation-fetch.test.ts +++ b/test/unit/linked-issue-label-propagation-fetch.test.ts @@ -548,6 +548,29 @@ describe("fetchLinkedIssueLabelsForPropagation (#priority-linked-issue-gate)", ( expect(result).toEqual(["gittensor:bug"]); }); + it("REGRESSION: mixed-trust duplicate issue labels do not relax strict mappings through a shared label name", async () => { + const mappings = [ + { issueLabel: "shared:gate", prLabel: "safe:type", removeOtherTypeLabels: true, trustMaintainerAuthoredIssue: true }, + { issueLabel: "shared:gate", prLabel: "sensitive:reward", removeOtherTypeLabels: false }, + ]; + stubFetch((url) => { + if (url.includes("/access_tokens")) return Response.json({ token: "installation-token" }); + if (url.endsWith("/issues/3951")) + return Response.json({ number: 3951, state: "open", user: { login: "owner" }, assignees: [], labels: ["shared:gate"] }); + return new Response("not found", { status: 404 }); + }); + const env = createTestEnv({}); + const result = await fetchLinkedIssueLabelsForPropagation({ + env, + repoFullName: "owner/repo", + linkedIssues: [3951], + installationId: 123, + prAuthorLogin: "contrib", + mappings, + }); + expect(result).toEqual([]); + }); + it("still propagates the reward label via trustMaintainerAuthoredIssueForReward when the issue is authored by an ADMIN_GITHUB_LOGINS fleet-operator", async () => { const mappings = [{ issueLabel: "gittensor:priority", prLabel: "gittensor:priority", removeOtherTypeLabels: false, trustMaintainerAuthoredIssueForReward: true }]; stubFetch((url) => {