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
31 changes: 31 additions & 0 deletions src/services/score-breakdown.ts
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,36 @@ function mergedHistoryBreakdown(preview: ScorePreviewResult): ScoreMultiplierBre
};
}

// Sibling of mergedHistoryBreakdown for the issue-discovery validity floor (upstream
// MIN_VALID_SOLVED_ISSUES and MIN_ISSUE_CREDIBILITY): when linked-issue scoring is active and
// contributor history is observed, falling below either floor zeroes the preview.
function issueDiscoveryHistoryBreakdown(preview: ScorePreviewResult): ScoreMultiplierBreakdown {
const { issueDiscoveryHistoryMultiplier } = preview.scoreEstimate;
const { validSolvedIssues, validSolvedIssuesFloor, issueCredibility, issueCredibilityFloor } = preview.gates;
if (validSolvedIssues === undefined || issueCredibility === undefined) {
return {
component: "issueDiscoveryHistoryMultiplier",
band: "neutral",
summary: `Issue-discovery validity floor is not enforced for this preview (contributor issue-history is unobserved; upstream floors are ${validSolvedIssuesFloor} valid solved and ${issueCredibilityFloor} credibility).`,
lever: "No action needed for this preview; the validity floor applies once issue-discovery history is observed.",
leverageScore: 0,
};
}
const band = bandForMultiplier(issueDiscoveryHistoryMultiplier);
const meetsFloor = validSolvedIssues >= validSolvedIssuesFloor && issueCredibility >= issueCredibilityFloor;
return {
component: "issueDiscoveryHistoryMultiplier",
band,
summary: meetsFloor
? `Issue-discovery history (${validSolvedIssues} valid solved, credibility ${roundBand(issueCredibility)}) meets upstream floors (${validSolvedIssuesFloor} valid solved, ${issueCredibilityFloor} credibility).`
: `Issue-discovery history (${validSolvedIssues} valid solved, credibility ${roundBand(issueCredibility)}) is below upstream floors (${validSolvedIssuesFloor} valid solved, ${issueCredibilityFloor} credibility), so this preview is zeroed.`,
lever: meetsFloor
? "Keep building valid solved-issue history with strong issue credibility."
: "Close more valid solved issues and improve issue credibility before relying on issue-discovery scoring.",
leverageScore: meetsFloor ? 8 : 100,
};
}

// Upstream time-decay (#703), env-gated by SCORING_TIME_DECAY_ENABLED (default OFF) and opted into per-preview
// via input.applyTimeDecay. When the flag is off (the common case) or the PR is fresh, the multiplier is 1 and
// the breakdown reads as "not enabled" / "fresh" — surfacing the value is a no-op for those previews but
Expand Down Expand Up @@ -318,6 +348,7 @@ export function explainScoreBreakdown(preview: ScorePreviewResult): ScoreBreakdo
openPrBreakdown(preview),
openIssueBreakdown(preview),
mergedHistoryBreakdown(preview),
issueDiscoveryHistoryBreakdown(preview),
timeDecayBreakdown(preview),
].map((entry) => ({
...entry,
Expand Down
52 changes: 52 additions & 0 deletions test/unit/score-breakdown.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,7 @@ describe("explainScoreBreakdown", () => {
"openPrMultiplier",
"openIssueMultiplier",
"mergedHistoryMultiplier",
"issueDiscoveryHistoryMultiplier",
"timeDecayMultiplier",
]),
);
Expand Down Expand Up @@ -122,6 +123,57 @@ describe("explainScoreBreakdown", () => {
expect(JSON.stringify(blocked)).not.toMatch(FORBIDDEN);
});

it("explains the issue-discovery validity floor as neutral (unobserved), full (meets floor), and blocked (below floor)", () => {
const issueDiscoveryRepo: RepositoryRecord = {
...repo,
registryConfig: { ...repo.registryConfig!, issueDiscoveryShare: 0.25 },
};
const baseInput = {
repoFullName: issueDiscoveryRepo.fullName,
contributorLogin: "miner",
sourceTokenScore: 40,
totalTokenScore: 60,
sourceLines: 80,
openPrCount: 0,
credibility: 1,
mergedPullRequests: 5,
linkedIssueMode: "standard" as const,
};

const unobserved = explainScoreBreakdown(buildScorePreview({ repo: issueDiscoveryRepo, snapshot, input: baseInput }));
expect(unobserved.components.find((entry) => entry.component === "issueDiscoveryHistoryMultiplier")).toMatchObject({
band: "neutral",
leverageScore: 0,
});

const meets = explainScoreBreakdown(
buildScorePreview({
repo: issueDiscoveryRepo,
snapshot,
input: { ...baseInput, validSolvedIssues: 4, issueCredibility: 0.9 },
}),
);
expect(meets.components.find((entry) => entry.component === "issueDiscoveryHistoryMultiplier")).toMatchObject({
band: "full",
summary: expect.stringMatching(/4 valid solved, private context 0.9/i),
});

const blocked = explainScoreBreakdown(
buildScorePreview({
repo: issueDiscoveryRepo,
snapshot,
input: { ...baseInput, validSolvedIssues: 1, issueCredibility: 0.5 },
}),
);
expect(blocked.components.find((entry) => entry.component === "issueDiscoveryHistoryMultiplier")).toMatchObject({
band: "blocked",
summary: expect.stringMatching(/private context.*zeroed/i),
leverageScore: 100,
});
expect(blocked.highestLeverageLever.lever).toMatch(/valid solved issues|issue credibility/i);
expect(JSON.stringify(blocked)).not.toMatch(FORBIDDEN);
});

it("explains an over-threshold open-issue count as a blocked open-issue spam gate", () => {
const preview = buildScorePreview({
repo,
Expand Down
Loading