Summary
buildRegistryChangeReport (the live "what changed between the two latest registry snapshots" report) compares only five of the eight comparable fields on RegistryRepoConfig:
// src/signals/engine.ts:3021-3028
const changes = [
...(repo.emissionShare !== old.emissionShare ? [`emission_share ${old.emissionShare} -> ${repo.emissionShare}`] : []),
...(repo.issueDiscoveryShare !== old.issueDiscoveryShare ? [`issue_discovery_share ${old.issueDiscoveryShare} -> ${repo.issueDiscoveryShare}`] : []),
...(repo.maintainerCut !== old.maintainerCut ? [`maintainer_cut ${old.maintainerCut} -> ${repo.maintainerCut}`] : []),
...(JSON.stringify(repo.labelMultipliers) !== JSON.stringify(old.labelMultipliers) ? ["label_multipliers changed"] : []),
...(repo.trustedLabelPipeline !== old.trustedLabelPipeline ? [`trusted_label_pipeline ${old.trustedLabelPipeline ?? false} -> ${repo.trustedLabelPipeline ?? false}`] : []),
];
return changes.length > 0 ? [{ repoFullName, changes }] : []; // empty changes -> repo dropped from changedRepos
It omits fixedBaseScore, defaultLabelMultiplier, and eligibilityMode. The authoritative drift comparator enumerates all eight:
// src/upstream/ruleset.ts:606-615 (the correct reference)
const REGISTRY_DRIFT_COMPARABLE_FIELDS: RegistryComparableHyperparameterField[] = [
"emissionShare", "issueDiscoveryShare", "maintainerCut",
"fixedBaseScore", "eligibilityMode", "trustedLabelPipeline",
"defaultLabelMultiplier", "labelMultipliers",
];
RegistryRepoConfig (src/types.ts:225-236) defines and the normalizer (src/registry/normalize.ts) parses all eight. Because a repo with an empty changes array is dropped from changedRepos (engine.ts:3029), any registry update that touches only an omitted field is reported as "no change" for that repo, and the summary count is wrong.
Why the omitted fields matter
The three omitted fields are exactly the ones that most directly move scoring:
fixedBaseScore overrides the entire base score — buildScorePreview uses it in place of the computed base when present (src/scoring/preview.ts:276,287-288). A change here changes every score preview for the repo.
defaultLabelMultiplier is the fallback label multiplier (src/scoring/preview.ts:293).
eligibilityMode drives lane-fit / scoreability assumptions (it is a first-class drift area in ruleset.ts).
So the report silently hides the highest-impact registry changes while faithfully reporting the lower-impact ones (maintainer_cut, trusted_label_pipeline).
Failure mode (concrete example)
Between two registry snapshots, upstream changes JSONbored/gittensory from fixed_base_score: 2 to fixed_base_score: 50 and changes nothing else.
- Correct:
changedRepos includes { repoFullName: "JSONbored/gittensory", changes: ["fixed_base_score 2 -> 50"] }, and the summary reads … 1 changed repo(s).
- Current: all five compared fields are equal →
changes is [] → the repo is dropped → changedRepos is empty and the summary reads 0 changed, even though every score preview for that repo now differs (base score 2 → 50).
The same false "no change" happens for a pure default_label_multiplier or eligibility_mode update.
Affected surfaces
buildRegistryChangeReport is public-facing:
GET /v1/registry/changes (src/api/routes.ts:1173).
- The
registry-changes MCP tool (src/mcp/server.ts:762).
Maintainers/contributors using these to see "what changed in the registry" get a false all-clear for the fields that most affect scoring and eligibility.
Steps to reproduce
- Persist two registry snapshots where the newer one differs from the older only in
fixedBaseScore (or defaultLabelMultiplier, or eligibilityMode) for one repo.
- Call
buildRegistryChangeReport(await listLatestRegistrySnapshots(env, 2)) (or hit GET /v1/registry/changes).
- Observe the repo is absent from
changedRepos and the summary says 0 changed, instead of reporting the fixed_base_score change.
Expected behavior
The change report compares the same full set of comparable RegistryRepoConfig fields as REGISTRY_DRIFT_COMPARABLE_FIELDS (ruleset.ts:606-615), so a change to fixedBaseScore, defaultLabelMultiplier, or eligibilityMode produces a changes entry and counts the repo as changed.
Actual behavior
Only emissionShare, issueDiscoveryShare, maintainerCut, labelMultipliers, and trustedLabelPipeline are diffed; changes to the other three comparable fields are invisible and the repo is dropped from changedRepos.
Suggested fix
- Make
buildRegistryChangeReport diff the full comparable-field set. Rather than adding three more ad-hoc branches (which is how the two implementations drifted in the first place), drive the comparison from a single field-descriptor table — { label, render(config) } for each of the eight RegistryRepoConfig comparable fields — and iterate it to build changes. This covers fixed_base_score, default_label_multiplier, and eligibility_mode, and keeps the live report and the upstream drift comparator from diverging again as new config fields are added.
- Add fail-on-revert coverage: a snapshot pair differing only in
fixedBaseScore (and again only in eligibilityMode / defaultLabelMultiplier) must yield a changedRepos entry with the corresponding field old -> new change line and a 1 changed summary. The existing tests only exercise the five currently-compared fields, so they ratify the omission.
Summary
buildRegistryChangeReport(the live "what changed between the two latest registry snapshots" report) compares only five of the eight comparable fields onRegistryRepoConfig:It omits
fixedBaseScore,defaultLabelMultiplier, andeligibilityMode. The authoritative drift comparator enumerates all eight:RegistryRepoConfig(src/types.ts:225-236) defines and the normalizer (src/registry/normalize.ts) parses all eight. Because a repo with an emptychangesarray is dropped fromchangedRepos(engine.ts:3029), any registry update that touches only an omitted field is reported as "no change" for that repo, and the summary count is wrong.Why the omitted fields matter
The three omitted fields are exactly the ones that most directly move scoring:
fixedBaseScoreoverrides the entire base score —buildScorePreviewuses it in place of the computed base when present (src/scoring/preview.ts:276,287-288). A change here changes every score preview for the repo.defaultLabelMultiplieris the fallback label multiplier (src/scoring/preview.ts:293).eligibilityModedrives lane-fit / scoreability assumptions (it is a first-class drift area inruleset.ts).So the report silently hides the highest-impact registry changes while faithfully reporting the lower-impact ones (
maintainer_cut,trusted_label_pipeline).Failure mode (concrete example)
Between two registry snapshots, upstream changes
JSONbored/gittensoryfromfixed_base_score: 2tofixed_base_score: 50and changes nothing else.changedReposincludes{ repoFullName: "JSONbored/gittensory", changes: ["fixed_base_score 2 -> 50"] }, and the summary reads… 1 changed repo(s).changesis[]→ the repo is dropped →changedReposis empty and the summary reads0 changed, even though every score preview for that repo now differs (base score 2 → 50).The same false "no change" happens for a pure
default_label_multiplieroreligibility_modeupdate.Affected surfaces
buildRegistryChangeReportis public-facing:GET /v1/registry/changes(src/api/routes.ts:1173).registry-changesMCP tool (src/mcp/server.ts:762).Maintainers/contributors using these to see "what changed in the registry" get a false all-clear for the fields that most affect scoring and eligibility.
Steps to reproduce
fixedBaseScore(ordefaultLabelMultiplier, oreligibilityMode) for one repo.buildRegistryChangeReport(await listLatestRegistrySnapshots(env, 2))(or hitGET /v1/registry/changes).changedReposand the summary says0 changed, instead of reporting thefixed_base_scorechange.Expected behavior
The change report compares the same full set of comparable
RegistryRepoConfigfields asREGISTRY_DRIFT_COMPARABLE_FIELDS(ruleset.ts:606-615), so a change tofixedBaseScore,defaultLabelMultiplier, oreligibilityModeproduces achangesentry and counts the repo as changed.Actual behavior
Only
emissionShare,issueDiscoveryShare,maintainerCut,labelMultipliers, andtrustedLabelPipelineare diffed; changes to the other three comparable fields are invisible and the repo is dropped fromchangedRepos.Suggested fix
buildRegistryChangeReportdiff the full comparable-field set. Rather than adding three more ad-hoc branches (which is how the two implementations drifted in the first place), drive the comparison from a single field-descriptor table —{ label, render(config) }for each of the eightRegistryRepoConfigcomparable fields — and iterate it to buildchanges. This coversfixed_base_score,default_label_multiplier, andeligibility_mode, and keeps the live report and the upstream drift comparator from diverging again as new config fields are added.fixedBaseScore(and again only ineligibilityMode/defaultLabelMultiplier) must yield achangedReposentry with the correspondingfield old -> newchange line and a1 changedsummary. The existing tests only exercise the five currently-compared fields, so they ratify the omission.