fix: search resilience to foreign and corrupt embedding vectors#2
Merged
AmadeusDelfino merged 10 commits intoJul 13, 2026
Conversation
Design for review-batch item #1 (dimension-mismatch crash). Covers both the noisy dimension-crash and the silent same-dimension-different-model mode via a shared model-identity compatibility predicate, a read-only skip-and-defer search guard, a dimension floor as corruption backstop, and a hardened startup migration (observable throwing regen, truthful count, generous init timeout). Hardened through two adversarial read-only review rounds; residual holes (quantized/prefix in the key = item obra#4, large-journal startup latency, concurrent writers) recorded as out-of-scope.
4 TDD tasks: shared isCompatible predicate, dimension floor (never crash), model-identity ceiling + disk-path skip log, hardened migration scan (observable throwing regen, truthful count, per-file isolation, generous init timeout). Derived from the reviewed spec (5c81036).
…/spec - add spec-required listRecent provenance-blindness test to Task 3 - add corrupt-vector regen test; extend needsRegen to section vectors - empty-source stale .embedding is now deleted (unrecoverable state) - fix Task 4 edit boundary (duplicate return risk; no-unreachable is off) - fix Task 2 import instruction (EmbeddingService lives in src/embeddings) - restore the fs.readFile spy (jest config has no restoreMocks) - record branch/PR decision: new branch, PR based on the feat branch - spec: honest residuals (wrong-length vectors; per-boot empty-file log)
…s instead of crashing
…egacy-vector floor
…n/v1 vectors from ranking Replaces the v1 whole-entry-fallback test added by aa585a2: per decision (ii) in the design spec, v1 files are foreign by provenance and are skipped from ranking; the startup scan converts v1 -> v2.
…lation, generous init timeout in migration scan
AmadeusDelfino
added a commit
that referenced
this pull request
Jul 13, 2026
…lience fix: search resilience to foreign and corrupt embedding vectors
5 tasks
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes review finding #1 on PR #1 — and the worse silent variant it implied.
Problem
A single
.embeddingproduced by a different model breaks search in two ways:cosineSimilaritythrowVectors must have same length, rejecting the entiresearch()— good entries disappear and no file is named.The startup re-index that should heal this couldn't observe its own failures: the swallowing regen path made
count++fire on silent failures, one bad file aborted the rest of a directory, and a cold-cache model swap always timed out (30s query default vs a ~465MB download).What changed
EmbeddingService.isCompatible(entry)— one shared predicate (version+model) used by both the search guard and the scan's staleness check, so batch item Convert to esm package obra/private-journal-mcp#4 (quantized/prefix in the key) extends both in lockstep.isCompatiblerows are ranked. Foreign/v1 rows are skipped and surfaced once per search:search_journal: skipped N entries (incompatible embedding model); run a re-index. Examples: <on-disk paths>.listRecentstays provenance-blind (chronological browse still shows everything).null,nullsection elements, non-arraysectionEmbeddingscontainer) are skipped per-vector, never thrown past. ThethrowincosineSimilaritystays — it's guarded at the call site.regenerateEmbedding) so the scan observes failures, whilewriteThoughtskeeps its non-fatal wrapper; truthful count (only real writes); per-file failure isolation; structurally-corrupt vectors (whole-entry or per-section) trigger regen; the orphan.embeddingof an empty source is deleted (unrecoverable state); single generous 120s init attempt with abort-on-failure — no retry, because transformers.js v2.17.2 neither resumes nor dedupes downloads (verified inhub.js).Design spec and plan are included in the diff under
docs/superpowers/{specs,plans}/2026-07-13-dimension-mismatch-*, including the accepted residuals (same-modelquantized/prefix divergence stays for item obra#4; same-model wrong-length vectors are floor-skipped but not scan-detectable).Verification
tsc --noEmitclean, eslint clean. TDD red→green per task; suite output pristine (all intentional logs asserted via the global spy)..mdmade unreadable, a same-model corrupt vector, an empty source with a stale orphan): scan healed the corrupt entry, deleted the orphan, isolated the unreadable file with oneFailed to migrateline, reportedGenerated embeddings for 1(previously would have claimed 3); a real MCPsearch_journalcall returned the 2 healthy entries (top score 0.898) and logged exactly one skip line naming the foreign file's on-disk path.embedding model unavailable — aborting re-indexline, zero per-file spam, server stayed up.