Skip to content

fix(store): fill in missing span timestamps on every open - #11

Merged
Harshith029 merged 2 commits into
mainfrom
fix-ts-backfill
Oct 24, 2026
Merged

Harshith029 merged 2 commits into
mainfrom
fix-ts-backfill

Conversation

@Harshith029

Copy link
Copy Markdown
Owner

What and why

Retention could keep a trace forever. It deletes a trace once MAX(julianday(ts)) is older than the cutoff; a trace whose spans all have a NULL ts gives NULL, so it was never deleted.

That state is reachable: 0.2.3 fills in ts only while adding the column, but 0.2.2 can still write to a migrated database (its INSERT names its columns) and leaves ts empty. After a rollback to 0.2.2 and an upgrade again, those traces outlived any retention window. Found while writing the 0.2.3 upgrade note.

The backfill now runs on every open. A partial index of just the spans missing ts (empty in normal use, since every insert sets it) keeps the check from scanning the table.

Testing

  • New test: write a span, empty its ts as 0.2.2 would, reopen, purge with a future cutoff. Fails on the previous code (nothing purged), passes now. It also asserts the startup UPDATE uses spans_missing_ts; without the index the plan is SCAN spans (checked).
  • Full suite: 689 passed, 12 skipped, 1 expected xfail. ruff and mypy --strict clean.

Security invariants

  • Forensic spans stay immutable: only the derived ts column is written, from the span's own timestamp. Retention still deletes whole traces only.

🤖 Generated with Claude Code

Retention deletes a trace once MAX(julianday(ts)) is older than the
cutoff. A trace whose spans all have a NULL ts gives NULL, so it was
never deleted.

That state is reachable. 0.2.3 adds the ts column and fills it in, but
only while adding the column. 0.2.2 can still write to a migrated
database (its INSERT names its columns) and leaves ts empty. So after a
rollback to 0.2.2 and an upgrade again, the spans 0.2.2 wrote kept no
timestamp, and their traces outlived any retention window.

The backfill now runs every time the store opens. A partial index of
just the spans missing ts keeps that from scanning the table: inserts
always set ts, so the index stays empty (the query plan goes from SCAN
spans to the index, which the test checks).

The new test writes a span, empties its ts as 0.2.2 would, reopens the
store and purges with a cutoff in the future. It fails on the previous
code (nothing is purged) and passes with this change.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@Harshith029
Harshith029 merged commit dc2ea9b into main Oct 24, 2026
7 checks passed
@Harshith029 Harshith029 mentioned this pull request Oct 24, 2026
1 task done
@Harshith029
Harshith029 deleted the fix-ts-backfill branch October 24, 2026 15:41
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant