Repository navigation
Ship record version history to production (staging port of #135) #136
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,5 @@ | ||
| kind: added | ||
| body: Add opt-in record version history. Collections listed in RECORD_HISTORY_COLLECTIONS (exact NSIDs or prefix.* patterns, Tap mode) keep every observed version and delete in a new record_version table, seeded on startup with a baseline row per existing record, and the new root recordHistory(uri, first, after) GraphQL query pages through them oldest first. History is off by default; migration 015 adds the table. | ||
| time: 2026-09-23T03:10:00.000000+02:00 | ||
| custom: | ||
| Affects: operator |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1 @@ | ||
| DROP TABLE IF EXISTS record_version; |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,25 @@ | ||
| -- Append-only version history for opted-in collections | ||
| -- (RECORD_HISTORY_COLLECTIONS). One row per distinct record version (CID) the | ||
| -- indexer observes, plus delete tombstones. `baseline` rows seed the version | ||
| -- that was current when history was switched on for a collection. | ||
| CREATE TABLE IF NOT EXISTS record_version ( | ||
| id BIGSERIAL PRIMARY KEY, | ||
| uri TEXT NOT NULL, | ||
| cid TEXT NOT NULL, | ||
| -- Dedupe identity: the CID, `sha256:<hex>` of the body when Tap omits the | ||
| -- CID, or `delete:<cid>` for a tombstone of that version. | ||
| version_key TEXT NOT NULL, | ||
| did TEXT NOT NULL, | ||
| collection TEXT NOT NULL, | ||
| action TEXT NOT NULL CHECK (action IN ('baseline', 'create', 'update', 'delete')), | ||
| json JSONB, | ||
| live BOOLEAN, | ||
| observed_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW() | ||
| ); | ||
|
|
||
| -- A version is recorded once, however often Tap redelivers or resyncs it. | ||
| CREATE UNIQUE INDEX IF NOT EXISTS idx_record_version_uri_key | ||
| ON record_version(uri, version_key); | ||
|
Comment on lines
+20
to
+22
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
When a URI is deleted and later recreated with the same content, ATProto produces the same content-addressed CID, but this URI/CID uniqueness constraint discards both the later create and its subsequent AGENTS.md reference: AGENTS.md:L94-L94 Useful? React with 👍 / 👎. |
||
| CREATE INDEX IF NOT EXISTS idx_record_version_uri_id ON record_version(uri, id); | ||
| CREATE INDEX IF NOT EXISTS idx_record_version_collection_observed | ||
| ON record_version(collection, observed_at DESC); | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1 @@ | ||
| DROP TABLE IF EXISTS record_version; |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,23 @@ | ||
| -- Append-only version history for opted-in collections | ||
| -- (RECORD_HISTORY_COLLECTIONS). See the PostgreSQL migration for details. | ||
| CREATE TABLE IF NOT EXISTS record_version ( | ||
| id INTEGER PRIMARY KEY AUTOINCREMENT, | ||
| uri TEXT NOT NULL, | ||
| cid TEXT NOT NULL, | ||
| -- Dedupe identity: the CID, `sha256:<hex>` of the body when Tap omits the | ||
| -- CID, or `delete:<cid>` for a tombstone of that version. | ||
| version_key TEXT NOT NULL, | ||
| did TEXT NOT NULL, | ||
| collection TEXT NOT NULL, | ||
| action TEXT NOT NULL CHECK (action IN ('baseline', 'create', 'update', 'delete')), | ||
| json TEXT, | ||
| live INTEGER, | ||
| observed_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%SZ', 'now')) | ||
| ); | ||
|
|
||
| -- A version is recorded once, however often Tap redelivers or resyncs it. | ||
| CREATE UNIQUE INDEX IF NOT EXISTS idx_record_version_uri_key | ||
| ON record_version(uri, version_key); | ||
| CREATE INDEX IF NOT EXISTS idx_record_version_uri_id ON record_version(uri, id); | ||
| CREATE INDEX IF NOT EXISTS idx_record_version_collection_observed | ||
| ON record_version(collection, observed_at DESC); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
When an admin invokes
PurgeActoror Tap receives a deleted, deactivated, suspended, or taken-down identity,RecordsRepository.PurgeActorDatadeletes onlyrecordandactor; this independent table has no cascade and neither purge path removes its rows. Because the new publicrecordHistoryresolver reads this table directly, tracked record bodies remain publicly retrievable after an operation documented as removing all indexed data for the DID. Include these rows in the same purge transaction.Useful? React with 👍 / 👎.