Describe the bug
If the relay-signed kind:13535 archived-identities snapshot fails to publish during a NIP-IA archive request, that snapshot is permanently stale for the affected identity — and no number of retries by the client can repair it.
The failure chain:
handle_identity_archive_event calls db.archive(...), which inserts with ON CONFLICT (community_id, pubkey) DO NOTHING and returns changed = rows_affected > 0 (
|
let result = sqlx::query( |
|
"INSERT INTO archived_identities \ |
|
(community_id, pubkey, consent_path, actor, reason, replaced_by, request_event_id) \ |
|
VALUES ($1, $2, $3, $4, $5, $6, $7) \ |
|
ON CONFLICT (community_id, pubkey) DO NOTHING", |
|
) |
|
.bind(community_id.as_uuid()) |
|
.bind(pubkey) |
|
.bind(consent_path) |
|
.bind(actor) |
|
.bind(reason) |
|
.bind(replaced_by) |
|
.bind(request_event_id) |
|
.execute(pool) |
|
.await?; |
|
|
|
Ok(result.rows_affected() > 0) |
|
} |
).
- On the first (successful) archive, the
kind:8002 delta and kind:13535 snapshot publishes are attempted, but their errors are only warn!-logged (
|
if let Err(e) = publish_delta { |
|
warn!(error = %e, "failed to publish NIP-IA delta"); |
|
} |
|
if let Err(e) = publish_nipia_archival_list(tenant, state).await { |
|
warn!(error = %e, "failed to publish NIP-IA archival list"); |
|
} |
). The handler still returns Ok(()).
- Any subsequent archive request for the same identity hits
changed == false and returns early — before publish_nipia_archival_list is ever reached (
|
if !changed { |
|
return Ok(()); |
|
} |
).
So the one code path that could republish the snapshot is gated behind a DB state transition that, by design, can only happen once. Retrying the archive request is an idempotent no-op: the DB row exists, the snapshot stays wrong, and the client sees success every time.
Steps to reproduce
Reproduced live on a hosted relay on 2026-08-03:
- Archive an identity at a moment when the snapshot publish fails transiently (in our case the relay logged
failed to publish NIP-IA archival list; the archived_identities row was committed).
- Observe the divergence: identity
51c6e299… had a DB row (and is_archived returned true), but was absent from the authoritative kind:13535 snapshot (buzz agents archived verified against the raw event).
- Send the same
kind:9035 archive request again — repeatedly. Every attempt is accepted (OK true), and none of them republish the snapshot.
- The only client-side repair we found was a full unarchive → archive cycle: two
changed=true transitions, which forces the delta + snapshot publishes to run again.
Expected behavior
The kind:13535 snapshot should be repairable without destructively cycling the archive state. Two possible fixes (either alone would resolve it):
- Republish on idempotent requests: when
changed == false, skip the delta (there was no state transition to describe) but still call publish_nipia_archival_list. The snapshot is a pure function of the archived_identities table, so republishing is always safe, and it makes "retry the request" an actual repair path.
- Reconcile on read: derive/verify the snapshot from the DB when it's served, so a lost publish self-heals.
Secondary issue: side-effect failures are invisible to the requesting client. The request event itself is accepted by ingest (
|
if is_identity_archive_request_kind(kind_u32) { |
|
crate::handlers::identity_archive::handle_identity_archive_event(tenant, state, &event) |
|
.await |
|
.map_err(|e| IngestError::Rejected(format!("invalid: {e}")))?; |
|
} |
), so the client gets OK
true even when the delta or snapshot publish failed — the errors exist only in relay logs. A client has no signal that the archive it just performed isn't reflected in the authoritative list. Worth considering whether publish failures should surface in the OK message (or at least be queryable), though that may deserve its own issue.
Related but distinct: #3848 covers snapshots lost to same-second created_at collisions between successive publishes. This issue is about a snapshot publish that failed outright and can never be retried because the retry path is gated on changed.
Version and platform
- Buzz version: main @ ac4fa13 (also observed on a hosted relay, 2026-08-03)
- OS: server-side (relay); client on Windows 11
Logs / additional context
Relay-side log lines for the failed publish (only visible server-side):
WARN failed to publish NIP-IA archival list
Client-side, every retry of the archive request returned:
["OK","<event-id>",true,""]
with no change to the kind:13535 snapshot.
Describe the bug
If the relay-signed
kind:13535archived-identities snapshot fails to publish during a NIP-IA archive request, that snapshot is permanently stale for the affected identity — and no number of retries by the client can repair it.The failure chain:
handle_identity_archive_eventcallsdb.archive(...), which inserts withON CONFLICT (community_id, pubkey) DO NOTHINGand returnschanged = rows_affected > 0(buzz/crates/buzz-db/src/archived_identities.rs
Lines 60 to 77 in ac4fa13
kind:8002delta andkind:13535snapshot publishes are attempted, but their errors are onlywarn!-logged (buzz/crates/buzz-relay/src/handlers/identity_archive.rs
Lines 131 to 136 in ac4fa13
Ok(()).changed == falseand returns early — beforepublish_nipia_archival_listis ever reached (buzz/crates/buzz-relay/src/handlers/identity_archive.rs
Lines 100 to 102 in ac4fa13
So the one code path that could republish the snapshot is gated behind a DB state transition that, by design, can only happen once. Retrying the archive request is an idempotent no-op: the DB row exists, the snapshot stays wrong, and the client sees success every time.
Steps to reproduce
Reproduced live on a hosted relay on 2026-08-03:
failed to publish NIP-IA archival list; thearchived_identitiesrow was committed).51c6e299…had a DB row (andis_archivedreturned true), but was absent from the authoritativekind:13535snapshot (buzz agents archivedverified against the raw event).kind:9035archive request again — repeatedly. Every attempt is accepted (OKtrue), and none of them republish the snapshot.changed=truetransitions, which forces the delta + snapshot publishes to run again.Expected behavior
The
kind:13535snapshot should be repairable without destructively cycling the archive state. Two possible fixes (either alone would resolve it):changed == false, skip the delta (there was no state transition to describe) but still callpublish_nipia_archival_list. The snapshot is a pure function of thearchived_identitiestable, so republishing is always safe, and it makes "retry the request" an actual repair path.Secondary issue: side-effect failures are invisible to the requesting client. The request event itself is accepted by ingest (
buzz/crates/buzz-relay/src/handlers/ingest.rs
Lines 2300 to 2304 in ac4fa13
trueeven when the delta or snapshot publish failed — the errors exist only in relay logs. A client has no signal that the archive it just performed isn't reflected in the authoritative list. Worth considering whether publish failures should surface in the OK message (or at least be queryable), though that may deserve its own issue.Related but distinct: #3848 covers snapshots lost to same-second
created_atcollisions between successive publishes. This issue is about a snapshot publish that failed outright and can never be retried because the retry path is gated onchanged.Version and platform
Logs / additional context
Relay-side log lines for the failed publish (only visible server-side):
Client-side, every retry of the archive request returned:
with no change to the
kind:13535snapshot.