Write the digests the deleter already knew how to drop #6

Merged
LNK merged 1 commit from claude/digests into main 2026-09-04 17:57:23 +00:00
Contributor

The digests table had a deleter and no writer since the first migration: a promise with only its failure mode built.

What changes. The retention sweep writes a day digest for every closed day without one, before it expires anything, so the counts are taken while the records exist: events by signal and verdict, frames stored and their bytes, sessions, apps, first and last record. Counts, never identifiers, so it is plaintext like the index. A session digest is written when a session ends. A read API returns them newest first.

Two deletions, two answers. A record leaving on the retention timer left the device, not the world, and a digest of counts is meant to outlive it. That is how a year stays answerable once the frames are gone. A record a person erased still drops the digest that counted it, as it always did. The deletion path already knew which case it was in because it already knew whether to tell the server; the digest rule reads the same flag. Day digests are kept 365 days, the longest local window, then pruned.

Proof. Store: a closed day gets one digest with the right counts and an open day none; a second sweep writes nothing; expiry keeps a digest and erasure drops it; a session digest at session end. Gate: the day's digest counts both old frames although one expires in the same sweep, which fails with the order swapped, checked by swapping it. Linux verifier green on the pinned toolchain. Not run here: the Windows verifier and the end-to-end suite.

The `digests` table had a deleter and no writer since the first migration: a promise with only its failure mode built. **What changes.** The retention sweep writes a day digest for every closed day without one, before it expires anything, so the counts are taken while the records exist: events by signal and verdict, frames stored and their bytes, sessions, apps, first and last record. Counts, never identifiers, so it is plaintext like the index. A session digest is written when a session ends. A read API returns them newest first. **Two deletions, two answers.** A record leaving on the retention timer left the device, not the world, and a digest of counts is meant to outlive it. That is how a year stays answerable once the frames are gone. A record a person erased still drops the digest that counted it, as it always did. The deletion path already knew which case it was in because it already knew whether to tell the server; the digest rule reads the same flag. Day digests are kept 365 days, the longest local window, then pruned. **Proof.** Store: a closed day gets one digest with the right counts and an open day none; a second sweep writes nothing; expiry keeps a digest and erasure drops it; a session digest at session end. Gate: the day's digest counts both old frames although one expires in the same sweep, which fails with the order swapped, checked by swapping it. Linux verifier green on the pinned toolchain. Not run here: the Windows verifier and the end-to-end suite.
`digests` has been in the schema since `0001_core` — kind, period, json — and the deletion
path has always known to drop a digest that covers a deleted event, so a summary cannot
outlive its source. Nothing ever wrote one. The only insert in the tree was a test's. A table
with a deleter and no writer is a promise with only its failure mode built.

Now the retention sweep writes a day digest for every day that has closed without one,
before it expires anything, so the counts are taken while the records exist: events by
signal and verdict, frames stored and their bytes, sessions, apps, first and last record.
Counts, never identifiers — the json holds nothing the clear index does not already say,
which is why it is plaintext. A day is summarised once, the first sweep after it ends, and
never rewritten: a digest is a statement about a day as it was. A session digest is written
at the one moment a session's counts are final, when it ends.

Two deletions, two answers. A record leaving on the retention timer left the device, not
the world, and a digest of counts is exactly what is meant to outlive it — that is how a
year stays answerable once the frames are gone. A record a person erased is different: the
digest that counted it goes, as it always did. The deletion path already knew which case it
was in, because it already knew whether to tell the server; the digest rule now reads the
same flag. Day digests are kept for 365 days, the longest local window any record has, then
pruned by the same sweep.

Tests: a closed day gets one digest with the right counts and an open day gets none; a
second sweep writes nothing and the next day's closes on time; expiry keeps a digest and
erasure drops it; a session digest is written at the end of a session; and at the gate,
through the write path, the day's digest counts both old frames although one expired in the
same sweep — which fails if the order is swapped, checked by swapping it.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
LNK merged commit df4d50bba3 into main 2026-09-04 17:57:23 +00:00
LNK referenced this pull request from a commit 2026-09-04 17:57:23 +00:00
Sign in to join this conversation.
No reviewers
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
mikebrandon/here-telemetry-rnd!6
No description provided.