Problem
After vendoring a patch, re-running scan --mode vendored perpetually reports an [UPDATE] / updates[] entry pointing at a patch that is the SAME or OLDER than the one already vendored — a permanent false nag (confirmed in prod on pip + poetry legs for urllib3).
detect_updates (crates/socket-patch-cli/src/commands/scan/discovery.rs:248, pre-fix) flagged an update whenever the top-ranked batch candidate's UUID differed from the recorded one. The candidate is chosen by api::ranking::cmp_batch_infos, but batch endpoint responses omit publishedAt (see ranking.rs:176 and rank_batch_info at ranking.rs:143, which parse an absent date to epoch 0). With severity and merge-coverage tied and the date meaningless, ranking falls through to the arbitrary UUID tiebreak — so an equal-or-older sibling patch can be crowned "top candidate" and flagged as a newer update.
Fix
In detect_updates (crates/socket-patch-cli/src/commands/scan/discovery.rs):
- (a) Same UUID as recorded → never an update (short-circuit,
discovery.rs:249).
- (b) When the recorded patch is still among the offered patches, only surface an update if the candidate GENUINELY supersedes it — new helper
candidate_supersedes (discovery.rs:~271) requires a strictly-better MEANINGFUL axis: worse severity, greater merge coverage, or a real, strictly-greater publishedAt. A missing date parses to None (via the existing RFC-2822 api::date::parse_timestamp_secs) and is never treated as newer, so an equal-or-older patch is never surfaced.
- If the recorded patch is no longer offered at all, ages are uncomparable, so the different currently-available candidate is still flagged (unchanged behavior; also the only thing a manifest-only record can produce).
Note: a strictly-newer sibling can only out-rank the applied patch when the batch actually supplies dates; with dates absent the newest is the applied one and apply (the richer by-package shape) keeps it, so detect_updates and apply stay consistent. Missed (not false) updates only until the endpoint emits publishedAt.
Test
crates/socket-patch-cli/src/commands/scan/discovery.rs — hermetic unit tests alongside the existing detect_updates tests:
detect_updates_no_nag_when_applied_patch_still_offered_and_batch_omits_dates — core regression: applied uuid-b re-listed with sibling uuid-a (sorts first), no dates → NO update. Verified RED before / GREEN after (temporarily disabling the guard makes it panic with "an equal-or-older sibling with no real date must not be an update").
detect_updates_still_flags_a_higher_severity_candidate_offered_alongside_applied — over-suppression guard: a CRITICAL sibling still flags.
detect_updates_flags_a_genuinely_newer_candidate_when_batch_supplies_dates — over-suppression guard: a strictly-newer sibling (real dates) still flags.
cargo test -p socket-patch-cli --lib discovery:: → 23 passed, 0 failed. cargo build -p socket-patch-cli and cargo clippy -p socket-patch-cli clean. All pre-existing detect_updates tests unchanged and green.
Scope
Single-file change to scan/discovery.rs. No API/type changes; reuses public api::ranking::{severity_order, merged_coverage} and api::date::parse_timestamp_secs. Kills sweep findings false-update-nag-older-uuid, false-update-flag-batch-ranking; touches K11. No cross-dependencies with the other parallel bug fixes.
🤖 Generated with Claude Code
Note
Low Risk
Scan update detection only; no API or manifest schema changes; guarded by focused unit tests.
Overview
detect_updates no longer treats every batch top-ranked candidate with a different UUID as an update. When the manifest’s applied patch is still in the batch response, an update is reported only if that candidate genuinely supersedes the applied patch via candidate_supersedes: strictly worse severity, broader merge coverage, or a real, strictly greater publishedAt (missing dates do not count).
This stops perpetual [UPDATE] / updates[] noise when batch responses omit dates and cmp_batch_infos picks a sibling on UUID tiebreak alone. If the recorded patch is no longer offered, behavior is unchanged—a different available UUID still flags an update.
Unit tests cover the no-nag regression, higher-severity updates, and genuinely newer siblings when dates are present.
Reviewed by Cursor Bugbot for commit 221d5d2. Configure here.