diff --git a/src/signals/engine.ts b/src/signals/engine.ts index eddf1a29f7..9cef03fd0e 100644 --- a/src/signals/engine.ts +++ b/src/signals/engine.ts @@ -4870,7 +4870,10 @@ function isPrivateBountyLifecycleFinding(code: string): boolean { } function containsPrivatePublicTerm(value: string): boolean { - return /\b(reward|payout|farming|wallet|hotkey|trust score|raw trust|estimated score|scoreability|likely_duplicate|reviewability\s*\d)\b/i.test(value); + // Plural forms must be caught too: this is the sole public-safe gate at its call sites (no scrub + // partner like the unified-comment bridge's), so a bare-singular term would leak "rewards"/"wallets" + // onto a public comment. Mirror the `s?` plural-aware sibling denylists (advisory.ts, queue-intelligence.ts). + return /\b(rewards?|payouts?|farming|wallets?|hotkeys?|trust scores?|raw trust|estimated scores?|scoreability|likely_duplicate|reviewability\s*\d)\b/i.test(value); } function sanitizePanelText(value: string): string { diff --git a/test/unit/signals-coverage.test.ts b/test/unit/signals-coverage.test.ts index 11f8cfd0ed..c9fef96395 100644 --- a/test/unit/signals-coverage.test.ts +++ b/test/unit/signals-coverage.test.ts @@ -689,6 +689,39 @@ describe("signal coverage edge cases", () => { expect(maintainerComment).not.toMatch(/reward|wallet|hotkey|trust score|farming/i); }); + it("drops PLURAL private terms from public finding titles, not just singular forms (regression: backstop missed plurals)", () => { + const directRepo = repo("owner/plural"); + const prRecord = pr(directRepo.fullName, 77, "Improve docs", { authorLogin: "miner", linkedIssues: [5], body: "Fixes #5" }); + const profile = buildContributorProfile("miner", { login: "miner", topLanguages: [], source: "github" }, [], []); + const basePreflight = buildPreflightResult( + { repoFullName: directRepo.fullName, title: "Improve docs", body: "Fixes #5", changedFiles: ["src/docs.ts"], linkedIssues: [5] }, + directRepo, + [], + [], + ); + const titlesFor = (title: string): string[] => + buildPublicCommentSignalBundle({ + repo: directRepo, + pr: prRecord, + profile, + detection: { detected: true, source: "official_gittensor_api", reason: "Confirmed by official API.", priorPullRequests: 1, priorMergedPullRequests: 0, priorIssues: 0 }, + queueHealth: buildQueueHealth(directRepo, [], [], buildCollisionReport(directRepo.fullName, [], [])), + collisions: buildCollisionReport(directRepo.fullName, [], []), + preflight: { ...basePreflight, findings: [{ code: "info_note", severity: "warning", title, detail: "n/a", action: "n/a" }, ...basePreflight.findings] }, + settings: repoSettings(directRepo.fullName), + }).publicFindingTitles as string[]; + + // The public-safe backstop is the SOLE gate here (unlike the unified-comment bridge, which scrubs + // plural-aware terms BEFORE this drop-test), so it must catch plurals exactly like singulars. + for (const term of ["rewards", "payouts", "wallets", "hotkeys", "trust scores", "estimated scores"]) { + expect(titlesFor(`Quarterly ${term} summary`).some((t) => t.toLowerCase().includes(term))).toBe(false); + } + // Control: singular forms were already dropped — pins that the plural fix did not regress them. + for (const term of ["reward", "payout", "wallet", "hotkey", "trust score", "estimated score"]) { + expect(titlesFor(`Quarterly ${term} summary`).some((t) => t.toLowerCase().includes(term))).toBe(false); + } + }); + it("buildPublicPrPanelSignalRows derives the gate conclusion across provided/fallback paths (#1007 unified-panel extraction)", () => { const directRepo = repo("owner/panel"); const collisions = buildCollisionReport(directRepo.fullName, [], []);