From aa94f51d8a997f6cd5055c42035826de4e5d0f75 Mon Sep 17 00:00:00 2001 From: claude Date: Fri, 17 Jul 2026 14:33:49 +0200 Subject: [PATCH] fix(registry): converge the identity-index repair pass on twin stores MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 --- crates/aura-registry/src/lib.rs | 32 +++++++++++++++++++------------- 1 file changed, 19 insertions(+), 13 deletions(-) diff --git a/crates/aura-registry/src/lib.rs b/crates/aura-registry/src/lib.rs index e3f08e7..6237789 100644 --- a/crates/aura-registry/src/lib.rs +++ b/crates/aura-registry/src/lib.rs @@ -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) } }