Commit e92e2c3
`MetadataManager.unregister()` dropped the registry entry and called
`invalidateListCache(type)` BEFORE awaiting `loader.delete()`. Those two steps
are separated by a real await window (one DB round-trip per writable loader),
and inside it the manager held a state that exists nowhere else: registry
already empty, loader not yet empty. `list()` merges the two, so a read
arriving in that window missed the just-cleared cache, assembled the
still-stored row into its answer, and memoized it as a COMPLETE read — the full
30s healthy TTL, because no loader threw and #5184's 2s degraded TTL therefore
never applied. Nothing invalidated again once the delete landed
(`notifyWatchers()` does not touch `listCache`), so an item gone from storage
kept being enumerated for up to half a minute while `get()` said it was gone.
Fixed by ordering, not by a second invalidation. `register()` never had this
defect because it writes the registry first and the registry outranks every
loader in the merge, so its save window already shows the post-write state. The
invariant is therefore not "invalidate early" but invalidate LAST, once every
store already holds the state being announced. `unregister()` now deletes from
storage first, then drops the registry entry and invalidates with nothing
awaited between them, then publishes and announces (#5219's
invalidate-before-notify bar, unchanged).
Composes with #5253's single-flight rather than duplicating it: a read still in
flight when the delete lands cannot be reached by dropping `listCache` — it has
not written its entry yet and would write the pre-delete answer afterwards.
`invalidateListCache()` also retracts that read's `inflightListReads`
registration, so it resolves for the callers already waiting on it but loses
the right to memoize, while a caller arriving later starts a fresh read.
A failing `loader.delete()` used to `logger.warn` and continue. Per AGENTS.md
"Degradation log levels" that is durability degradation, not functional:
`unregister()` resolves normally, the caller is told the delete succeeded, and
the surviving row is read straight back out of storage — permanently, since
nothing retries it. It now logs at `error`, once per un-deleted item, naming
the consequence and the fix, and the seam is named `deleteMetaItemFromLoader`
so `check:durability-log-level` covers it (22 -> 23 seams, all loud). The
registry entry is still dropped in that case, deliberately: the loader still
holds the row so the item is served either way, and keeping the entry would
only pin an in-memory copy on top of a stored row nobody maintains.
Claude-Session: https://claude.ai/code/session_01Pbu27iNUfQCHeuS551Rqo7
Co-authored-by: Claude <noreply@anthropic.com>
1 parent 0f2fdcd commit e92e2c3
4 files changed
Lines changed: 762 additions & 13 deletions
File tree
- .changeset
- packages/metadata/src
- scripts
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
| 31 | + | |
| 32 | + | |
| 33 | + | |
| 34 | + | |
| 35 | + | |
| 36 | + | |
| 37 | + | |
| 38 | + | |
| 39 | + | |
| 40 | + | |
| 41 | + | |
| 42 | + | |
| 43 | + | |
| 44 | + | |
| 45 | + | |
| 46 | + | |
| 47 | + | |
| 48 | + | |
| 49 | + | |
| 50 | + | |
| 51 | + | |
| 52 | + | |
| 53 | + | |
| 54 | + | |
| 55 | + | |
| 56 | + | |
0 commit comments