fix(registry): converge the identity-index repair pass on twin stores

Drive the RED twin-store pin green: the repair walk now collects its
identity->content mappings into a local last-wins map and appends after
the walk only the entries whose final walk value differs from the
pre-walk snapshot (collect-then-diff-append, decision on #191). The old
per-entry comparison against the mid-walk-stale snapshot appended one
alternating line per scan-triggering lookup while same-identity twins
coexisted — the sidecar now converges to zero growth there too. Lookup
answers (first read_dir match) and verify-on-hit untouched.

Verification: workspace suite 1347 passed / 0 failed; clippy -D
warnings clean.

refs #191
This commit is contained in:
2026-07-17 14:33:49 +02:00
parent 35865bb934
commit aa94f51d8a
+19 -13
View File
@@ -455,6 +455,17 @@ impl Registry {
Err(_) => return Ok(None), // no store yet -> nothing matches
};
let mut found = None;
// Collect-then-diff-append (#191): the walk's last-wins mapping is
// built locally first, and only entries whose FINAL value differs
// from the pre-walk `index` snapshot are appended, once, after the
// walk. This is what makes a repeated scan over an already-converged
// index a no-op even in the duplicate-identity/distinct-content twin
// corner (two stored blueprints sharing an identity id, e.g.
// differing only in debug names): comparing each entry against the
// stale pre-walk snapshot mid-walk would append one alternating line
// per twin every time, since the snapshot never equals both of them
// at once.
let mut walked = std::collections::BTreeMap::new();
for entry in entries {
let entry = entry?;
let json = fs::read_to_string(entry.path())?;
@@ -467,23 +478,18 @@ impl Registry {
else {
continue;
};
if index.get(&entry_identity) != Some(&content_id) {
// absent-or-different only, so a walk over an already-correct
// index appends nothing — *for the common one-content-per-
// identity case*. In the duplicate-identity/distinct-content
// corner (two stored blueprints sharing an identity id, e.g.
// differing only in debug names), `index` is this call's
// snapshot and is never updated mid-walk, so every entry
// whose content id differs from that stale snapshot appends
// a line, one per full-scan lookup, even though each write is
// individually a valid mapping. Best-effort regardless — a
// cache write must never degrade a correct read.
let _ = self.append_identity_index(&entry_identity, &content_id);
}
walked.insert(entry_identity.clone(), content_id);
if found.is_none() && entry_identity == identity_id {
found = Some(json);
}
}
for (walked_identity, content_id) in &walked {
if index.get(walked_identity) != Some(content_id) {
// Best-effort — a cache write must never degrade a correct
// read.
let _ = self.append_identity_index(walked_identity, content_id);
}
}
Ok(found)
}
}