Repository navigation
fix(relay): stamp each NIP-IA archive list strictly after the previous one - #9476
Merged
Merged
Conversation
…s one Signed-off-by: Tom Brow <[email protected]>
Move the concurrent-publisher test under postgres_tests with an ignore reason so the PostgreSQL Tests lane discovers it, and fail instead of pass when Postgres or Redis is missing. Qualify the NIP-IA since + 1 reconciliation for a relay restored to an older database. Signed-off-by: Tom Brow <[email protected]>
brow
marked this pull request as ready for review
November 13, 2026 22:26
Codex Review Summary: ✅ CompletedThis comment shows the latest Codex review activity on this pull request.
ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings. |
brow
enabled auto-merge (squash)
November 13, 2026 22:29
kazemek21
approved these changes
Nov 14, 2026
kazemek21
left a comment
Contributor
There was a problem hiding this comment.
Kyle reviewed this PR and the Nexus review-tracker notes and approved with no comments.
Posted by Codex (gpt-6.1-sol) on behalf of kylez
This branch was successfully deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Why
When an admin archives or unarchives an identity, the relay publishes a new archived identities list (kind 13535). This list replaces the previous one. It contains all the identities that are archived in the community. Clients use it to hide archived agents from pickers and search.
The list can be large (about 228 KB on Block's Builderlab), and it seldom changes. Thus the mobile app wants to download it only when it changed. To do this, the app keeps the last list it received, and asks the relay only for a list that is newer than that one (
sinceitscreated_atplus one, in #9445 archive list cache).This works only if each new list has a
created_atthat is later than the previous list's. Today the relay does not always do this:created_atas "the current list'screated_atplus one second". But it reads the current list before it gets the lock that controls replacement.created_at. The store keeps the one with the lower event id.created_at. It continues to hide or show the wrong agents until the next archive change or an app restart.failed to publish kind:13535 after 8 concurrent replacements. Then no list contains the latest archive change.What changes
Db::replace_addressable_event_after_head. It gets the replacement lock first. Then it reads the current list. Then it signs and stores the new list. Thus only one publisher at a time calculates the nextcreated_at, and each list is stamped strictly later than the previous one.publish_nipia_archival_listuses this method. The check that publishes again when the archive state changed during a publish does not change.kind:13535with acreated_atstrictly greater than its previous one. Clients MAY then ask only for listssincetheir list'screated_atplus one.Validation
New relay test: 16 concurrent publishers all complete, and each stored list (superseded rows included) has a strictly later
created_at. On main the same test fails, because a publisher uses all its retries. The test needs Postgres and Redis, so it is inpostgres_testsand runs in the PostgreSQL Tests lane; a missing database or Redis makes it fail, not pass. The existing archive tests pass against a local Postgres, including the stale-publisher test.just testpasses all 160 steps except one NIP-FI session deadline test (deadline_refused_permit_still_queues_one_denial_when_cancel_wins), which this change does not touch and which passes 5 of 5 reruns alone. Clippy passes.Regressions
None expected.
Dbmethod is an addition.replace_addressable_eventand its callers do not change.Accepted limitation: a database restore. The rule holds only while the relay keeps its newest list. If an operator restores the relay from a backup, the relay calculates the next
created_atfrom the restored list (max(restored + 1, now)). If earlier fast publishes went ahead of the clock, that value can be at or below acreated_atthat a client already has. That client then stays stale until a list passes its cachedcreated_at, or the app restarts. This change adds no repair for a restore.