Repository navigation
[Epic A] (#962) Per-key migration timestamp and the stale-blob sweep #965
Copy link
Copy link
Closed
Labels
Description
Epic A of #962. Plan of record: docs/initiatives/standalone/962-stale-blob-reclamation/PLAN.md.
Builds the engine: a per-key migration timestamp and the sweep that uses it. No UI.
Tasks
| Task | Pts |
|---|---|
A1 · Reproduce the diagnosis as failing tests: a key migrated long ago still has its blob; the marker carries no timestamp; DELETE without VACUUM leaves the file size unchanged. Red before anything is built. |
3 |
A2 · Per-key timestamps: a __msgrows_migrated_at blob mapping key to epoch ms, written when a key is marked. Pre-existing migrated keys are stamped on first sight, not treated as eligible, so no blob is deleted the moment a user updates. |
3 |
A3 · The sweep: for each migrated message-history key older than the 30-day constant, re-run the superset check and delete the blob only on a pass. Restricted to channel_messages_* and messages_*. Wired to run once per launch, after startup settles, never mid-session. |
4 |
A4 · One VACUUM, only if the sweep deleted something, with the reclaimed byte count logged. |
3 |
| A5 · Epic verification: the named command, plus the sweep run against a copy of a real database. | 1 |
Decisions this implements
30-day window; sweep once per launch; timestamps in a separate __msgrows_migrated_at blob so the hot __msgrows_migrated path is untouched. Owner, 2026-10-22.
Can't-tell branches, all stopping
| Where | Can't tell what | Does |
|---|---|---|
| A key's re-verify finds a blob record with no row | whether rows hold this key's history | stop — keep the blob, log which records are missing, never retry in a loop |
| The blob will not decode during re-verify | what the blob contains | stop — keep it, log it |
| A migrated key has no timestamp | when it migrated | stop deleting — stamp it now, let it age 30 days |
The key is not channel_messages_* or messages_* |
whether a row equivalent exists | stop — never considered |
VACUUM fails |
whether the file can be compacted | proceeds, logging it. Not fail-open on data: the deletes already happened and were each verified; VACUUM affects file size only. |
Acceptance
- A key stamped older than 30 days whose rows hold every blob record loses its blob, and the file shrinks after
VACUUM. - A key stamped older than 30 days with one blob record missing from rows keeps its blob and names the missing records. This gate must be proved able to fail.
- A key stamped inside 30 days keeps its blob, tested at 29 days so the boundary is covered.
- A migrated key with no timestamp is stamped, not deleted.
contacts,discovered_contactsandpath_history_keys are never considered, asserted by name.- An unmigrated history key keeps its blob.
- Every kept blob is byte-identical after
VACUUM, compared by hash, with the unmigrated history keys asserted present by name and the row count unchanged.
Named command: flutter test test/storage/ plus flutter analyze.
Agent Mail: TealGlen
Reactions are currently unavailable
Activity
feat(#966): a durable audit of what the sweep deleted
feat(#965): per-key migration timestamps and the stale-blob sweep
fix(#965): act on the Gemini gate findings
feat(#966): a durable audit of what the sweep deleted
diffwireauto commentedon Oct 24, 2026
Merged to dev via #973 (dev head
5b061924), CI 10/10 green. Content verified on dev bygit cat-fileand by grep for the substantive symbols, not by merge state.Owner-validated on Windows 2026-10-23, read back from the live database independently of what the UI reported:
He confirmed the channels read normally afterwards.
Agent Mail: TealGlen