fix(profile): merge kind:0 writes instead of replacing them - #3294
Open
bosoud wants to merge 1 commit into
Open
Conversation
kind:0 is replaceable, so publishing a profile built from a fixed set of keys is not a partial update — it deletes every field that was not built. Buzz constructed kind:0 content from a 5-key allowlist, so any identity whose profile carried `bot`, `website`, `banner` or `lud16` lost them the first time Buzz touched the profile, silently and with exit 0. `bot` is the costly one: an agent that quietly loses its NIP-24 flag reads as a human account to every client that renders it. Add `ProfileFields` and `merge_profile_content` to buzz-sdk: a merge over the identity's current content that carries unknown keys forward, so a key from a future NIP also survives contact with Buzz. The merge is plain serde_json with no nostr types, so the desktop crate shares it across the nostr 0.36/0.37 boundary that keeps these builders duplicated — the duplicated content-building logic in desktop/src-tauri/src/events.rs is gone rather than fixed twice. Fix all three write paths: - CLI `set-profile` merges into the full fetched object. This also drops the `display_name` -> `current.display_name` -> `current.name` fallback chain, which promoted a short handle into `display_name` for identities that had `name` but no `display_name`, and stops hardcoding `name` to None on every write. Adds `--username`, `--website` and `--bot`; `--name` keeps mapping to `display_name` for compatibility. - Desktop `update_profile` and the deferred avatar save merge rather than rebuild from per-key fallbacks. - Desktop managed-agent sync reads the agent's live profile and merges into it. This path published a two-field event and is unattended — reached from agent save, model change, persona edit, persona and team snapshot import, profile reconciliation and managed-agent restore — so it erased `about`/`nip05`/`bot` with no user action. The read degrades to an empty base on failure, so a transient query error cannot block the sync. Field semantics are unchanged for callers: `None` leaves a field alone. Clearing a field is still not expressible and is left out deliberately. Fixes block#2534 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0164eVvBNsWPzJZVtkDJAqPx Signed-off-by: Abdulwahab <bosoud@me.com>
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.
Fixes #2534.
kind:0 is replaceable, so a profile built from a fixed set of keys is not a partial update — it deletes everything it did not build.
build_profileconstructed content from a 5-key allowlist, sobot,website,bannerandlud16were dropped the first time Buzz touched a profile that had them, silently and with exit 0.botis the expensive one: an agent that quietly loses its NIP-24 flag reads as a human account to every client that renders it.Approach
buzz-sdkgainsProfileFieldsandmerge_profile_content— a merge over the identity's current content that carries unknown keys forward, so a key from a future NIP also survives a Buzz write.merge_profile_contentis plainserde_jsonwith nonostrtypes in its signature. That is deliberate: it crosses the nostr 0.36/0.37 boundary that forcesdesktop/src-tauri/src/events.rsto duplicate the SDK builders, so the duplicated profile-content logic is deleted rather than fixed in two places (item 5 of the issue). Desktop keeps only its own thinbuild_profile_mergedwrapper for the 0.37EventBuilder, and has no full-replacement variant left to call by accident.The three write paths
CLI
set-profilemerges into the full fetched object instead of rebuilding five keys. That also removes thedisplay_name→current.display_name→current.namefallback chain, which promoted a short handle intodisplay_namefor identities that hadnamebut nodisplay_name, and stops passingname: Noneon every write. New flags:--username(the NIP-01namefield),--website,--bot.--namestill maps todisplay_name, so existing usage is unchanged.Desktop
update_profileand the deferred avatar save merge rather than rebuild from per-key fallbacks.Desktop managed-agent sync now reads the agent's live profile and merges into it. This is the path the issue calls out as worst: it published a two-field event, and it is unattended — reached from agent save, model change, persona edit, persona and team snapshot import, profile reconciliation and managed-agent restore.
AgentProfileInfocarries the full content object so the merge has a base. A failed or absent read degrades to an empty base, so a transient query error cannot block the sync.Semantics
Unchanged for callers:
Noneleaves a field alone;Some(v)sets it. Clearing a field is still not expressible — that needs a distinct "unset" signal and is deliberately left out of this change.Tests
New in
buzz-sdk, including the issue's exact repro (set one field, assert nothing else moved) with an unmodelledsome_future_nip_keypinned to prove passthrough. New indesktop/src-tauri/src/relay.rs: the managed-agent sync preservesabout/nip05/bot/websitewhile updating name and avatar.cargo test --workspace --libandcargo clippy --workspace --all-targetsare clean. The desktop Tauri crate type-checks clean (cargo check --tests, no warnings); I could not execute its test binaries locally becausesherpa-onnx-syshas no prebuilt static lib for macOS x86_64, so CI is the first place those two desktop tests actually run.