Skip to content

Commit 2524469

Browse files
committed
Fix runtime config database risk calibration
1 parent cbd5c72 commit 2524469

3 files changed

Lines changed: 84 additions & 0 deletions

File tree

docs/scoring.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,10 @@ changes, lockfile-only changes, and package metadata-only changes stay low
2222
unless they are paired with source, dependency, runtime config, or structural
2323
changes.
2424

25+
Runtime config plus database/schema changes receive a small structural lift.
26+
That combination is treated as production-sensitive because it can change
27+
deployment defaults and persisted behavior in the same PR.
28+
2529
## Decay Score
2630

2731
Decay score estimates whether the PR makes the codebase harder to maintain.

packages/core/src/index.ts

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -357,6 +357,17 @@ function calculateScoreBreakdown(
357357
});
358358
}
359359

360+
const runtimePersistenceScore = scoreKind === "merge" ? runtimePersistenceBoundaryScore(contributors) : 0;
361+
if (runtimePersistenceScore > 0) {
362+
contributors.push({
363+
id: "runtime-persistence-boundary",
364+
label: "Runtime config plus persistence boundary",
365+
points: runtimePersistenceScore,
366+
evidence: "structural",
367+
reason: "Runtime configuration and database/schema behavior changed together, which increases production regression risk."
368+
});
369+
}
370+
360371
const rawScore = clampScore(contributors.reduce((score, contributor) => score + contributor.points, 0));
361372
const dampeners: ScoreContributor[] = [];
362373
let adjustedScore = rawScore;
@@ -413,6 +424,14 @@ function createFindingContributor(finding: Finding): ScoreContributor {
413424
};
414425
}
415426

427+
function runtimePersistenceBoundaryScore(contributors: ScoreContributor[]): number {
428+
const hasDatabaseChange = contributors.some((contributor) => contributor.ruleId === "risky-database-change");
429+
const hasConfigChange = contributors.some((contributor) => contributor.ruleId === "risky-config-change");
430+
const hasHighSeveritySignal = contributors.some((contributor) => contributor.severity === "high");
431+
432+
return hasDatabaseChange && hasConfigChange && hasHighSeveritySignal ? 8 : 0;
433+
}
434+
416435
function scoreEvidenceForFinding(finding: Finding): ScoreEvidenceKind {
417436
if ([...DIRECT_FINDING_RULE_IDS].some((ruleId) => finding.ruleId === ruleId || finding.ruleId.startsWith(`${ruleId}-`))) {
418437
return "direct";

packages/core/test/core.test.ts

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -214,6 +214,67 @@ describe("createAnalysisReport", () => {
214214
expect(shouldFailForRisk(report.summary.riskLevel, "high")).toBe(true);
215215
});
216216

217+
it("treats runtime config and database changes together as high production risk", () => {
218+
const report = createAnalysisReport({
219+
changedFiles: [
220+
{
221+
path: "next.config.js",
222+
status: "modified",
223+
additions: 8,
224+
deletions: 6,
225+
addedLines: [{ line: 2, content: "const sessionSecret = env.SESSION_SECRET ?? 'dev-secret';" }]
226+
},
227+
{
228+
path: "src/db/schema.js",
229+
status: "modified",
230+
additions: 4,
231+
deletions: 1,
232+
addedLines: [{ line: 2, content: ' role: "admin",' }]
233+
}
234+
],
235+
analyzerResult: {
236+
impactedAreas: [],
237+
findings: [
238+
{
239+
ruleId: "risky-config-change",
240+
title: "Config area changed",
241+
description: "Runtime config changed.",
242+
severity: "medium",
243+
category: "configuration",
244+
file: "next.config.js",
245+
line: 2
246+
},
247+
{
248+
ruleId: "risky-database-change",
249+
title: "Database area changed",
250+
description: "Database defaults changed.",
251+
severity: "high",
252+
category: "regression",
253+
file: "src/db/schema.js",
254+
line: 2
255+
},
256+
{
257+
ruleId: "missing-nearby-tests",
258+
title: "Risky source changes without changed tests",
259+
description: "Risky source changed without changed tests.",
260+
severity: "high",
261+
category: "coverage",
262+
file: "next.config.js",
263+
line: 2
264+
}
265+
],
266+
recommendedTests: []
267+
},
268+
generatedAt: "2026-06-22T00:00:00.000Z"
269+
});
270+
271+
expect(report.summary.mergeRiskScore).toBeGreaterThanOrEqual(70);
272+
expect(report.summary.riskLevel).toBe("high");
273+
expect(report.summary.mergeRiskBreakdown?.contributors).toEqual(
274+
expect.arrayContaining([expect.objectContaining({ id: "runtime-persistence-boundary", points: 8 })])
275+
);
276+
});
277+
217278
it("caps heuristic-only merge risk below high even with severe findings", () => {
218279
const changedFiles = createSyntheticChanges(6);
219280
const findings = createSyntheticFindings(4, "high", "snapshot-only-test", "coverage");

0 commit comments

Comments
 (0)