Summary
computeScoreCore in src/scoring/preview.ts consumes two repo-configurable scoring inputs without enforcing the ranges upstream documents for them, so an out-of-range value silently distorts the score preview:
fixed_base_score forces base_score to a constant that upstream documents as being in [0.0, 100.0], but the value reaches the preview unbounded above. src/api/routes.ts:567 validates it as z.number().min(0) (no max) and src/registry/normalize.ts:73 accepts any finite number, so a misconfigured fixed_base_score: 150 produces baseScore = 150 — a base component above the model ceiling that then multiplies through to estimatedMergedScore.
SRC_TOK_SATURATION_SCALE is documented as per-repo overridable only within [10, 500] (default 58), but saturationScore only floors it at 1 (Math.max(constant(...), 1)). A snapshot value below 10 (e.g. 3) saturates the base curve almost immediately, well above the documented floor; a value above 500 flattens it.
Area
REST API
Expected behavior
Both inputs are clamped to their upstream-documented ranges at the scoring boundary before they affect the preview:
fixed_base_score is clamped to [0, 100] (a non-finite/absent value falls through to the token-derived base score).
SRC_TOK_SATURATION_SCALE is clamped to [10, 500] before the saturation curve is computed.
Actual behavior
// src/scoring/preview.ts
const fixedBaseScore = input.fixedBaseScore ?? config?.fixedBaseScore ?? undefined;
// ...
const baseScore = fixedBaseScore !== undefined ? fixedBaseScore : /* token-derived */;
function saturationScore(sourceTokenScore, totalTokenScore, constants) {
const scale = Math.max(constant(constants, "SRC_TOK_SATURATION_SCALE"), 1); // only the divide-by-zero floor
return constant(constants, "MERGED_PR_BASE_SCORE") * (1 - Math.exp(-sourceTokenScore / scale)) + /* bonus */;
}
- A repo with
fixed_base_score: 150 previews baseScore = 150 instead of the documented 100 ceiling.
- A snapshot
SRC_TOK_SATURATION_SCALE = 3 previews the same near-saturated base component as 1, instead of being treated as the documented floor 10.
Reproduction
buildScorePreview with registryConfig.fixedBaseScore = 150 → scoreEstimate.baseScore === 150.
buildScorePreview with a pending_saturation_model snapshot whose constants.SRC_TOK_SATURATION_SCALE = 3 → the saturation base component differs from the clamped-floor (10) result.
Validation
Clamp both inputs to their documented ranges in src/scoring/preview.ts. In-range values (e.g. fixed_base_score: 12, SRC_TOK_SATURATION_SCALE: 58) are unchanged, so existing previews are unaffected. Covered by regression tests in test/unit/scoring.test.ts pinning the [0,100] clamp (above-ceiling and below-floor) and the [10,500] saturation-scale clamp (below-floor and above-ceiling), each failing before the fix. Full local npm run test:ci green.
Summary
computeScoreCoreinsrc/scoring/preview.tsconsumes two repo-configurable scoring inputs without enforcing the ranges upstream documents for them, so an out-of-range value silently distorts the score preview:fixed_base_scoreforcesbase_scoreto a constant that upstream documents as being in[0.0, 100.0], but the value reaches the preview unbounded above.src/api/routes.ts:567validates it asz.number().min(0)(no max) andsrc/registry/normalize.ts:73accepts any finite number, so a misconfiguredfixed_base_score: 150producesbaseScore = 150— a base component above the model ceiling that then multiplies through toestimatedMergedScore.SRC_TOK_SATURATION_SCALEis documented as per-repo overridable only within[10, 500](default 58), butsaturationScoreonly floors it at1(Math.max(constant(...), 1)). A snapshot value below 10 (e.g. 3) saturates the base curve almost immediately, well above the documented floor; a value above 500 flattens it.Area
REST API
Expected behavior
Both inputs are clamped to their upstream-documented ranges at the scoring boundary before they affect the preview:
fixed_base_scoreis clamped to[0, 100](a non-finite/absent value falls through to the token-derived base score).SRC_TOK_SATURATION_SCALEis clamped to[10, 500]before the saturation curve is computed.Actual behavior
fixed_base_score: 150previewsbaseScore = 150instead of the documented100ceiling.SRC_TOK_SATURATION_SCALE = 3previews the same near-saturated base component as1, instead of being treated as the documented floor10.Reproduction
buildScorePreviewwithregistryConfig.fixedBaseScore = 150→scoreEstimate.baseScore === 150.buildScorePreviewwith apending_saturation_modelsnapshot whoseconstants.SRC_TOK_SATURATION_SCALE = 3→ the saturation base component differs from the clamped-floor (10) result.Validation
Clamp both inputs to their documented ranges in
src/scoring/preview.ts. In-range values (e.g.fixed_base_score: 12,SRC_TOK_SATURATION_SCALE: 58) are unchanged, so existing previews are unaffected. Covered by regression tests intest/unit/scoring.test.tspinning the[0,100]clamp (above-ceiling and below-floor) and the[10,500]saturation-scale clamp (below-floor and above-ceiling), each failing before the fix. Full localnpm run test:cigreen.