Summary
buildRecommendationChange in src/services/miner-dashboard-recommendations.ts caps change-evidence labels at CHANGE_LABEL_LIMIT = 6, but builds the human-readable summary from the already-truncated limited slice instead of the full labels list. Labels are pushed in a fixed group order (repo_state ×5 → contributor_state ×2 → validation_state ×1 → policy_context ×2), so validation/policy are the first groups dropped — and when dropped, the summary omits them.
Evidence
// src/services/miner-dashboard-recommendations.ts:169
const limited = labels.slice(0, CHANGE_LABEL_LIMIT);
if (limited.length === 0) { /* unchanged */ }
const changedGroups = [...new Set(limited.map((label) => GROUP_TITLES[label.kind]))].join(", ");
// ^^^^^^^ derived from the TRUNCATED slice
return { status: "changed", summary: `Changed since the previous run: ${changedGroups}.`, labels: limited };
Reproduction
A pack run where, for one repo, all of actionKind, lane, recommendation, priority bucket, and queue change (5 repo_state labels) plus contributor PR-state (1 contributor_state label), and validation blockers change:
labels = [5×repo_state, 1×contributor_state, 1×validation_state, …]
limited = labels.slice(0, 6) drops the validation_state label.
changedGroups = "Repo state, Contributor state" — Validation state is silently omitted from both labels and summary even though it changed.
Impact
The summary is the at-a-glance line a miner reads to decide whether to act. Undercounting changed groups — dropping precisely the validation/policy signals that matter most — misleads them into thinking blockers/policy are stable when they aren't. Same defect family as the already-accepted #383 / #398 / #408 truncation-undercount bugs.
Suggested fix
Compute the summary from the full labels set while keeping the display cap on the returned labels:
const limited = labels.slice(0, CHANGE_LABEL_LIMIT);
if (labels.length === 0) {
return { status: "unchanged", summary: "No tracked evidence changed since the previous run.", labels: [] };
}
const changedGroups = [...new Set(labels.map((label) => GROUP_TITLES[label.kind]))].join(", ");
return { status: "changed", summary: `Changed since the previous run: ${changedGroups}.`, labels: limited };
Add a fixture where ≥7 fields change across all four groups and assert the summary still names Validation/Policy.
Summary
buildRecommendationChangeinsrc/services/miner-dashboard-recommendations.tscaps change-evidence labels atCHANGE_LABEL_LIMIT = 6, but builds the human-readablesummaryfrom the already-truncatedlimitedslice instead of the fulllabelslist. Labels are pushed in a fixed group order (repo_state×5 →contributor_state×2 →validation_state×1 →policy_context×2), so validation/policy are the first groups dropped — and when dropped, the summary omits them.Evidence
Reproduction
A pack run where, for one repo, all of
actionKind,lane,recommendation, priority bucket, and queue change (5repo_statelabels) plus contributor PR-state (1contributor_statelabel), and validation blockers change:labels=[5×repo_state, 1×contributor_state, 1×validation_state, …]limited = labels.slice(0, 6)drops thevalidation_statelabel.changedGroups="Repo state, Contributor state"— Validation state is silently omitted from both labels and summary even though it changed.Impact
The summary is the at-a-glance line a miner reads to decide whether to act. Undercounting changed groups — dropping precisely the validation/policy signals that matter most — misleads them into thinking blockers/policy are stable when they aren't. Same defect family as the already-accepted #383 / #398 / #408 truncation-undercount bugs.
Suggested fix
Compute the summary from the full
labelsset while keeping the display cap on the returned labels:Add a fixture where ≥7 fields change across all four groups and assert the summary still names Validation/Policy.