Split out of #17428 at delivery time, not at filing time. That ticket's detector half shipped — rows now carry the provider-input format's identity, and npm run ai:stale-embedding-census reports the affected population per tenant. What it cannot do is fix anything: nothing in the repository re-embeds a row whose marker is absent or superseded.
Live latest-open sweep at 2026-08-20T23:1xZ: checked the latest 20 open issues; #17439 (resumed repo slices forget previously settled embeddings) is the nearest neighbour and is a different subject — resume bookkeeping, keyed on slice state rather than on format identity. No equivalent found.
Why this is a separate ticket rather than a second commit on #17428. A read-only census is a leaf. Re-embedding is a plane operation: on an external tenant deployment a full corpus rebuild costs days of provider compute, and when to spend that is an operator decision informed by the census, not a property of the code. The two halves have different delivery authority, which is the axis this repository splits on.
The Problem
The census on this deployment reports 68,039 rows scanned, 68,039 pre-marker, 0 current. Every one of those rows holds a vector built from a string nobody stored, and the only mechanism that would rebuild them is a parserVersion advance — which re-mints every chunk id in the repository, re-embeds the whole corpus rather than the affected subset, and which nothing schedules.
So the state after #17428 is: the population is measurable and unrepairable. That is strictly better than invisible, and it is not done.
The Architectural Reality
Carried forward from #17428, verified there rather than assumed here:
mechanism
re-embeds a marked-stale row?
why
parserVersion advance
yes, and far too much
parserVersion IS a hashInputs member, so ids change and the whole corpus re-embeds. Nothing schedules it.
EMBEDDING_POISON_STRATEGY_FAMILY bump
no
it scopes poison/suppression evidence only — resolveEmbeddingPoisonGeneration's own docblock, and the falsifier is one grep for a call site
ordinary re-ingestion
no
the provider input is derived and not a hashInputs member, so ids are unchanged, the row classifies as present, and incremental selection skips it
takes stale ids, re-embeds them, and re-upserts with the current marker
dry-run by default; --apply commits
its own module docblock
ai/scripts/migrations/backfillChromaSharedUserId.mjs is the runner precedent: dry-run default, explicit apply, per-collection summary
helpers/staleEmbeddingCensus
unchanged
already exposes the ids and the cause split the runner consumes
—
—
shipped in #17428; classifyRowFormat returning null for a current row is the idempotence primitive
the before/after count
the census
the runner reports both numbers, from the same instrument
a run that repairs nothing reports zero repaired rather than succeeding silently
—
"repaired" must be a measurement, which is the AC #17428 could only half-close
provider compute
the operator
the runner is never scheduled automatically
—
—
days of compute on a live plane is not a leaf's decision
Acceptance Criteria
Four of these are carried verbatim from #17428, where they could not be closed by a detector. They keep their original wording so nothing is quietly weakened in transit.
RED-PROOF, and it must not be the mistake #17428 was filed with. The arm must observe a row actually being re-embedded — not that an id or a generation hash changed. A hash-difference assertion is what let a docblock read as a wired mechanism in the first place; the falsifier is "does a present, stale row get a new vector", and it must fail against today's tree.
The absence is asserted too. An arm confirms that bumping EMBEDDING_POISON_STRATEGY_FAMILY re-embeds nothing, so #17428's corrected finding is pinned as behaviour and cannot be re-mis-read by the next reader.
No silent partial repair. If a run repairs some rows and stops, the remainder is reported with its count; a partial rebuild that reads as complete is the failure mode this whole lane keeps hitting.
The affected population is counted before and after, per tenant, so "repaired" is a measurement rather than an inference.
The repaired row carries the CURRENT marker, and a second run skips it. Idempotence end to end, not only at the classifier: re-selecting its own output turns a bounded repair into a loop, which on a plane paying days of compute is worse than not repairing.
Dry-run is the default. A runner that spends provider compute on an accidental invocation is not shippable; --apply is the only path that writes.
A repair that fails mid-row leaves no half-written row. Either the new vector and the new marker land together, or neither does — a row carrying a current marker over an old vector is undetectable by construction, which is worse than the state this lane started in.
Adding the provider input to hashInputs. It would make future format changes self-repairing and re-mint every chunk id in existence; noted there as the general fix with its own blast radius.
Scheduling. The runner is invoked by an operator; wiring it into the orchestrator is a separate decision with its own cost argument.
Avoided Traps
Treating a generation bump as a repair.#17428 shipped exactly that error in its most load-bearing section, and the falsifier was one grep for a call site. Nothing here may assert re-embedding by observing a hash.
A green run that re-embedded nothing. The census returning zero stale rows is a valid outcome AND the signature of a broken selector; the before/after pair is what distinguishes them.
Claiming a corpus is repaired from a completed run. Completion is not coverage — the remainder count is the claim, not the exit code.
Related
#17428 — the detector half: the marker, the census, and the measured 68,039.
Parent epic: #17411. Sibling leaves: #17412 (throughput), #17413 (the lane guide).
#17439 — resumed slices forget settled embeddings; adjacent, keyed on slice state rather than format identity.
Retrieval Hint: query_raw_memories("stale embedding rows are counted by the census but nothing re-embeds them; delete-and-re-embed by id driven off the marker absence; dry-run default and before/after counts")
tobiu referenced in commit 089411c - "feat(kb): repair stale embeddings by id, dry-run by default (#17443) (#17768) on Aug 25, 2026, 8:43 PM
Context
Split out of #17428 at delivery time, not at filing time. That ticket's detector half shipped — rows now carry the provider-input format's identity, and
npm run ai:stale-embedding-censusreports the affected population per tenant. What it cannot do is fix anything: nothing in the repository re-embeds a row whose marker is absent or superseded.Live latest-open sweep at 2026-08-20T23:1xZ: checked the latest 20 open issues; #17439 (resumed repo slices forget previously settled embeddings) is the nearest neighbour and is a different subject — resume bookkeeping, keyed on slice state rather than on format identity. No equivalent found.
Why this is a separate ticket rather than a second commit on #17428. A read-only census is a leaf. Re-embedding is a plane operation: on an external tenant deployment a full corpus rebuild costs days of provider compute, and when to spend that is an operator decision informed by the census, not a property of the code. The two halves have different delivery authority, which is the axis this repository splits on.
The Problem
The census on this deployment reports 68,039 rows scanned, 68,039 pre-marker, 0 current. Every one of those rows holds a vector built from a string nobody stored, and the only mechanism that would rebuild them is a
parserVersionadvance — which re-mints every chunk id in the repository, re-embeds the whole corpus rather than the affected subset, and which nothing schedules.So the state after #17428 is: the population is measurable and unrepairable. That is strictly better than invisible, and it is not done.
The Architectural Reality
Carried forward from #17428, verified there rather than assumed here:
parserVersionadvanceparserVersionIS ahashInputsmember, so ids change and the whole corpus re-embeds. Nothing schedules it.EMBEDDING_POISON_STRATEGY_FAMILYbumpresolveEmbeddingPoisonGeneration's own docblock, and the falsifier is one grep for a call sitehashInputsmember, so ids are unchanged, the row classifies as present, and incremental selection skips itThe repair therefore needs to delete-and-re-embed by id, driven off the census, rather than to invalidate a generation and hope.
Contract Ledger Matrix
--applycommitsai/scripts/migrations/backfillChromaSharedUserId.mjsis the runner precedent: dry-run default, explicit apply, per-collection summaryhelpers/staleEmbeddingCensusclassifyRowFormatreturningnullfor a current row is the idempotence primitiveAcceptance Criteria
Four of these are carried verbatim from #17428, where they could not be closed by a detector. They keep their original wording so nothing is quietly weakened in transit.
EMBEDDING_POISON_STRATEGY_FAMILYre-embeds nothing, so #17428's corrected finding is pinned as behaviour and cannot be re-mis-read by the next reader.--applyis the only path that writes.Out of Scope
hashInputs. It would make future format changes self-repairing and re-mint every chunk id in existence; noted there as the general fix with its own blast radius.Avoided Traps
Related
Origin Session ID: 046f993e-13ba-47dd-827d-d786428e318b
Retrieval Hint:
query_raw_memories("stale embedding rows are counted by the census but nothing re-embeds them; delete-and-re-embed by id driven off the marker absence; dry-run default and before/after counts")