Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
57 changes: 57 additions & 0 deletions src/services/score-breakdown.ts
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,62 @@ function timeDecayBreakdown(preview: ScorePreviewResult): ScoreMultiplierBreakdo
};
}

// Issue-discovery validity floor (upstream MIN_VALID_SOLVED_ISSUES + MIN_ISSUE_CREDIBILITY; constants.py
// mirror #808): a preview that engages the issue-discovery lane but has fewer solved-issues or lower
// credibility than the upstream floors has the multiplier zeroed. Mirrors mergedHistoryBreakdown /
// openPr / openIssue so the public projection explains the gate cleanly without collapsing the two
// evidence dimensions into a single phrase (the nit that closed the previous attempt at this surface).
function issueDiscoveryHistoryBreakdown(preview: ScorePreviewResult): ScoreMultiplierBreakdown {
const { issueDiscoveryHistoryMultiplier } = preview.scoreEstimate;
const { validSolvedIssues, validSolvedIssuesFloor, issueCredibility, issueCredibilityFloor } = preview.gates;
const relevant = preview.linkedIssueMultiplier.mode !== "none";
const known = validSolvedIssues !== undefined && issueCredibility !== undefined;
if (issueDiscoveryHistoryMultiplier >= 1) {
if (!relevant) {
return {
component: "issueDiscoveryHistoryMultiplier",
band: "neutral",
summary: "Issue-discovery lane is not engaged for this preview (linked-issue mode is none), so the validity floor multiplier is 1 by intent.",
lever: "Select a linked-issue mode (standard or maintainer) on subsequent previews to engage the issue-discovery lane.",
leverageScore: 0,
};
}
if (!known) {
const dimClauses: string[] = [];
if (validSolvedIssues !== undefined) dimClauses.push(`${validSolvedIssues} valid solved-issue(s) observed`);
else dimClauses.push("valid solved-issue count still missing");
if (issueCredibility !== undefined) dimClauses.push(`${(issueCredibility * 100).toFixed(0)}% credibility observed`);
else dimClauses.push("issue credibility still missing");
return {
component: "issueDiscoveryHistoryMultiplier",
band: "neutral",
summary: `Issue-discovery validity floor is partially observed for this preview (${dimClauses.join("; ")}; upstream floors are ${validSolvedIssuesFloor} solved and ${(issueCredibilityFloor * 100).toFixed(0)}% credibility).`,
lever: "No action needed; the floor is not enforced until both dimensions are observed in a subsequent preview.",
leverageScore: 0,
};
}
return {
component: "issueDiscoveryHistoryMultiplier",
band: "full",
summary: `Issue-discovery evidence meets both upstream floors (${validSolvedIssues} solved / ${(issueCredibility! * 100).toFixed(0)}% credibility vs floors ${validSolvedIssuesFloor} / ${(issueCredibilityFloor * 100).toFixed(0)}%).`,
lever: "Keep issue-discovery activity healthy: file solvable issues and resolve them with validated PRs.",
leverageScore: 4,
};
}
const failSolved = validSolvedIssues !== undefined && validSolvedIssues < validSolvedIssuesFloor;
const failCredibility = issueCredibility !== undefined && issueCredibility < issueCredibilityFloor;
const causes: string[] = [];
if (failSolved) causes.push(`valid solved issues (${validSolvedIssues} < ${validSolvedIssuesFloor})`);
if (failCredibility) causes.push(`issue credibility (${(issueCredibility! * 100).toFixed(0)}% < ${(issueCredibilityFloor * 100).toFixed(0)}%)`);
return {
component: "issueDiscoveryHistoryMultiplier",
band: "blocked",
summary: `Issue-discovery validity floor is failing the preview: ${causes.join(" and ")}.`,
lever: "Land more solved issues (each with a solving-PR token-score ≥ upstream MIN_TOKEN_SCORE_FOR_VALID_ISSUE) and stabilize credibility at or above the floor before relying on this preview.",
leverageScore: 100,
};
}

function credibilityBreakdown(preview: ScorePreviewResult): ScoreMultiplierBreakdown {
const { credibilityMultiplier } = preview.scoreEstimate;
const { credibilityObserved, credibilityFloor } = preview.gates;
Expand Down Expand Up @@ -319,6 +375,7 @@ export function explainScoreBreakdown(preview: ScorePreviewResult): ScoreBreakdo
openIssueBreakdown(preview),
mergedHistoryBreakdown(preview),
timeDecayBreakdown(preview),
issueDiscoveryHistoryBreakdown(preview),
].map((entry) => ({
...entry,
summary: sanitizePublicComment(entry.summary),
Expand Down
136 changes: 136 additions & 0 deletions test/unit/score-breakdown.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,7 @@ describe("explainScoreBreakdown", () => {
"openIssueMultiplier",
"mergedHistoryMultiplier",
"timeDecayMultiplier",
"issueDiscoveryHistoryMultiplier",
]),
);
for (const component of breakdown.components) {
Expand Down Expand Up @@ -189,6 +190,141 @@ describe("explainScoreBreakdown", () => {
expect(JSON.stringify(agedBreakdown)).not.toMatch(FORBIDDEN);
});

it("explains the issue-discovery history floor: lane-off / unobserved neutral, meeting-floor full, below-floor blocked", () => {
// Lane not engaged (linkedIssueMode=none) => multiplier is 1 by intent, breakdown neutral + lane-off copy.
const laneOff = explainScoreBreakdown(
buildScorePreview({ repo, snapshot, input: { repoFullName: repo.fullName, contributorLogin: "miner", sourceTokenScore: 40, totalTokenScore: 60, sourceLines: 80, openPrCount: 1, credibility: 0.9, linkedIssueMode: "none" } }),
);
expect(laneOff.components.find((entry) => entry.component === "issueDiscoveryHistoryMultiplier")).toMatchObject({ band: "neutral" });

// Lane engaged + only one dimension observed => partial-evidence neutral that explicitly names the
// missing dim (avoids the collapse-copy nit from #1874).
const partial = explainScoreBreakdown(
buildScorePreview({
repo,
snapshot,
input: {
repoFullName: repo.fullName,
contributorLogin: "miner",
sourceTokenScore: 40,
totalTokenScore: 60,
sourceLines: 80,
openPrCount: 1,
credibility: 0.9,
linkedIssueMode: "standard",
linkedIssueContext: { status: "validated", source: "official_mirror", issueNumbers: [1513], solvedByPullRequests: [22] },
validSolvedIssues: 5,
// issueCredibility intentionally omitted to trigger the partial branch.
},
}),
);
const partialEntry = partial.components.find((entry) => entry.component === "issueDiscoveryHistoryMultiplier")!;
expect(partialEntry).toMatchObject({ band: "neutral" });
// After the sanitizer replaces "credibility" with "private context" (#1874 nit: don't collapse the
// missing dimension into a generic "no solved-issue observed" — the copy must still name it).
expect(partialEntry.summary).toMatch(/still missing.*private context|partial/i);

// Mirror partial: issueCredibility observed, validSolvedIssues missing. Same neutral band,
// copy names the OTHER missing dimension — the two halves of the nit must both be covered.
const partialMirror = explainScoreBreakdown(
buildScorePreview({
repo,
snapshot,
input: {
repoFullName: repo.fullName,
contributorLogin: "miner",
sourceTokenScore: 40,
totalTokenScore: 60,
sourceLines: 80,
openPrCount: 1,
credibility: 0.9,
linkedIssueMode: "standard",
linkedIssueContext: { status: "validated", source: "official_mirror", issueNumbers: [1513], solvedByPullRequests: [22] },
// validSolvedIssues intentionally omitted; issueCredibility observed.
issueCredibility: 0.9,
},
}),
);
const partialMirrorEntry = partialMirror.components.find((entry) => entry.component === "issueDiscoveryHistoryMultiplier")!;
expect(partialMirrorEntry).toMatchObject({ band: "neutral" });
expect(partialMirrorEntry.summary).toMatch(/valid solved-issue count still missing/);

// Lane engaged + both observed + both meet upstream floors => full band with concrete numbers.
const meeting = explainScoreBreakdown(
buildScorePreview({
repo,
snapshot,
input: {
repoFullName: repo.fullName,
contributorLogin: "miner",
sourceTokenScore: 40,
totalTokenScore: 60,
sourceLines: 80,
openPrCount: 1,
credibility: 0.9,
linkedIssueMode: "standard",
linkedIssueContext: { status: "validated", source: "official_mirror", issueNumbers: [1513], solvedByPullRequests: [22] },
validSolvedIssues: 5,
issueCredibility: 0.9,
},
}),
);
expect(meeting.components.find((entry) => entry.component === "issueDiscoveryHistoryMultiplier")).toMatchObject({ band: "full" });

// Lane engaged + validSolvedIssues below upstream floor => blocked band that names the failing dim
// specifically (avoid the same collapse-copy nit).
const belowFloor = explainScoreBreakdown(
buildScorePreview({
repo,
snapshot,
input: {
repoFullName: repo.fullName,
contributorLogin: "miner",
sourceTokenScore: 40,
totalTokenScore: 60,
sourceLines: 80,
openPrCount: 1,
credibility: 0.9,
linkedIssueMode: "standard",
linkedIssueContext: { status: "validated", source: "official_mirror", issueNumbers: [1513], solvedByPullRequests: [22] },
validSolvedIssues: 0,
issueCredibility: 0.9,
},
}),
);
const blockedEntry = belowFloor.components.find((entry) => entry.component === "issueDiscoveryHistoryMultiplier")!;
expect(blockedEntry).toMatchObject({ band: "blocked", leverageScore: 100 });
expect(blockedEntry.summary).toMatch(/valid solved issues/i);
expect(blockedEntry.lever).toMatch(/solved issues|MIN_TOKEN_SCORE_FOR_VALID_ISSUE/i);
expect(JSON.stringify(belowFloor)).not.toMatch(FORBIDDEN);

// Mirror blocked: issueCredibility is below the upstream floor (the OTHER half of the below-floor
// nit must both be covered: both dimensions can independently trigger the gate).
const belowFloorCredibility = explainScoreBreakdown(
buildScorePreview({
repo,
snapshot,
input: {
repoFullName: repo.fullName,
contributorLogin: "miner",
sourceTokenScore: 40,
totalTokenScore: 60,
sourceLines: 80,
openPrCount: 1,
credibility: 0.9,
linkedIssueMode: "standard",
linkedIssueContext: { status: "validated", source: "official_mirror", issueNumbers: [1513], solvedByPullRequests: [22] },
validSolvedIssues: 5,
issueCredibility: 0.5,
},
}),
);
const blockedCredEntry = belowFloorCredibility.components.find((entry) => entry.component === "issueDiscoveryHistoryMultiplier")!;
expect(blockedCredEntry).toMatchObject({ band: "blocked" });
// "credibility" is sanitized to "private context" before reaching the public surface.
expect(blockedCredEntry.summary).toMatch(/issue (credibility|private context)/i);
});

it("includes gate highlights without leaking forbidden language", () => {
const preview = buildScorePreview({
repo,
Expand Down
Loading