feat(desktop): add persona event kind with client publish/read/retain#939
Draft
wpfleger96 wants to merge 13 commits into
Draft
feat(desktop): add persona event kind with client publish/read/retain#939wpfleger96 wants to merge 13 commits into
wpfleger96 wants to merge 13 commits into
Conversation
This was referenced Jun 10, 2026
wpfleger96
added a commit
that referenced
this pull request
Jun 10, 2026
Ties the secrets-exclusion rule to the env_vars field removed in #939, making the doc-to-code mapping unambiguous. Co-authored-by: Will Pfleger <pfleger.will@gmail.com> Signed-off-by: Will Pfleger <pfleger.will@gmail.com>
wpfleger96
added a commit
that referenced
this pull request
Jun 11, 2026
…Body PersonaEventContent is the public struct serialized in kind:30175 events. PR #939 removed env_vars from it (secrets must not travel in plaintext events). This commit removes the re-addition from #945 and instead carries env_vars in PersonaEngramBody, which is NIP-44 encrypted inside the agent's mem/persona engram. Co-authored-by: Will Pfleger <wpfleger@block.xyz> Signed-off-by: Will Pfleger <wpfleger@block.xyz>
wpfleger96
added a commit
that referenced
this pull request
Jun 11, 2026
…Body PersonaEventContent is the public struct serialized in kind:30175 events. PR #939 removed env_vars from it (secrets must not travel in plaintext events). This commit removes the re-addition from #945 and instead carries env_vars in PersonaEngramBody, which is NIP-44 encrypted inside the agent's mem/persona engram. Co-authored-by: Will Pfleger <wpfleger@block.xyz> Signed-off-by: Will Pfleger <wpfleger@block.xyz>
817e596 to
5952ae5
Compare
Squashed for rebase — original commits: - feat(desktop): add persona event kind with client publish/read/retain - fix(desktop): sign every migrated persona event and drop the sentinel file - fix(relay): validate kind:30175 persona d-tag slug grammar on ingest - fix(desktop): drop env_vars from public PersonaEventContent - docs: add NIP-AP spec for kind:30175 persona events - feat(ci): add relay E2E testing job and persona event tests
Missed during rebase: sprout_core→buzz_core_pkg imports in persona_events.rs and migration.rs, sprout-desktop→buzz-desktop in log messages, and cargo fmt on e2e_persona.rs.
cargo fmt requires buzz_core_pkg imports before nostr (alphabetical). Relay E2E git tests need git-credential-nostr built alongside the relay.
…ay behavior The relay now has a generic file upload path that accepts PDFs, random bytes, and unrecognised formats as application/octet-stream downloads. SVG with XML declaration is not detected by `infer` and also routes through the generic path. Updated four content validation tests from expecting rejection (400/415) to expecting acceptance (200). Additionally, three WebSocket imeta tests hit /api/events but the relay route is at /events (no /api prefix). Fixed the URL in all three. Co-authored-by: Will Pfleger <wpfleger@squareup.com> Signed-off-by: Will Pfleger <wpfleger@squareup.com>
infer detects XML-based SVG as text/xml (not image/svg+xml), which is not in the blocked list, so the relay accepts it through the generic file path with that MIME type. Co-authored-by: Will Pfleger <wpfleger96@gmail.com> Signed-off-by: Will Pfleger <wpfleger96@gmail.com>
Same class of issues as e2e_media_extended — tests assumed image-only policy and wrong API path. The relay ignores Content-Type headers (uses magic bytes) and the route is /events not /api/events. Co-authored-by: Will Pfleger <wpfleger96@gmail.com> Signed-off-by: Will Pfleger <wpfleger96@gmail.com>
The live_split_model_completes test is a manual runbook test requiring multiple serve nodes. Replace panic!() with println+return so it skips gracefully when CI runs --ignored tests. Co-authored-by: Will Pfleger <wpfleger96@gmail.com> Signed-off-by: Will Pfleger <wpfleger96@gmail.com>
9b8924a to
6c2e650
Compare
* origin/main: (50 commits) chore(release): release version 0.3.24 (#1074) feat(desktop): refine thread-unread badge to two-token form (#1069) fix(buzz): prevent reconnect storms from reaped ephemeral channels (#1071) fix(buzz-acp): trim oversized observer frames to fit instead of dropping (#1072) perf(ci): speed up PR CI wall clock and local dev builds (#1028) chore(deps): update react monorepo (#1048) Polish desktop visual details (#1067) ci: use running postgres for pgschema desired-state planning (#1070) fix(desktop): anchor active-turn badge to skew-corrected agent start (#1068) feat(desktop): add configurable transport reconnect hook (#1059) Add automatic database migrations (#988) Add composer spoiler formatting (#1055) feat(desktop): in-channel and in-thread unread indicators (#1008) perf(timeline): gate heavy message render behind useDeferredValue (#1022) Add animated profile avatars (#1031) Polish direct message and members modals (#1054) Polish huddles UI (#1041) Fix video review comments in threads (#1056) Polish message reaction tray (#1002) Refine app loading skeletons (#1001) ... Co-authored-by: Will Pfleger <pfleger.will@gmail.com> Signed-off-by: Will Pfleger <pfleger.will@gmail.com> # Conflicts: # desktop/src-tauri/Cargo.lock
The Relay E2E job (new in #939) is the first to run these previously-skipped tests. Two asserted against `/channels/.../threads` and `/channels/.../messages` REST routes the relay never served (permanent 404); a third raced a live kind:44100 fan-out that a sibling subscription's drain silently discarded. Rewrite the thread read-backs against POST /query — the depth_limit + #e extension routes to get_thread_replies, the relay's real thread surface — and reorder the DM test to subscribe after create_dm so the persisted membership and discovery events are served deterministically from history. Co-authored-by: Will Pfleger <pfleger.will@gmail.com> Signed-off-by: Will Pfleger <pfleger.will@gmail.com>
test_nip10_thread_reply_not_in_top_level asserted only that a reply threads under its root — a correlate, not the relay's actual top-level rule. get_channel_messages_top_level surfaces a depth-1 reply iff broadcast = true, so a broadcast=1 depth-1 reply satisfied every old assertion yet IS surfaced at top level: the test greened by data accident. The relay exposes no top-level-queryable surface over POST /query (feed_types routes to feed queries that never read thread_metadata.depth/broadcast), so the rule is pinned via its two test-observable inputs — recorded depth and the broadcast tag — in both directions: a non-broadcast depth-1 reply is excluded, a broadcast=1 depth-1 reply is surfaced. Co-authored-by: Will Pfleger <pfleger.will@gmail.com> Signed-off-by: Will Pfleger <pfleger.will@gmail.com>
cargo fmt --all --check failed Rust Lint on the assertions added in the prior commit (lines 925, 938). Whitespace/reflow only; no assertion or logic change. Co-authored-by: Will Pfleger <pfleger.will@gmail.com> Signed-off-by: Will Pfleger <pfleger.will@gmail.com>
The relay's clock-skew guard rejects events with created_at too far from server time. Replace hardcoded Nov 2023 timestamps (1_700_000_000) with Timestamp::now()-relative values that stay within the skew window while preserving the older-vs-newer replacement semantics. Co-authored-by: Will Pfleger <pfleger.will@gmail.com> Signed-off-by: Will Pfleger <pfleger.will@gmail.com>
…2e_relay tests
Tests assumed HTTP REST endpoints (/api/events, /api/users/{pubkey}/profile,
/api/users/me/channel-add-policy) that the relay does not serve. The relay
router only exposes /events, /query, /count, and a few other paths.
Three fix patterns applied:
- POST /api/events → POST /events (the relay's actual HTTP bridge)
- GET /api/users/{pubkey}/profile → POST /query with kind:0 filter
- PUT /api/users/me/channel-add-policy → submit kind:10100 event via POST /events
Also fixed self-add test that hit nostr crate's default p-tag stripping
(EventBuilder removes p tags matching the signer unless allow_self_tagging
is called), and added a 1s sleep in kind0_nip05_sync to ensure the
replacement event gets a strictly newer created_at timestamp.
Co-authored-by: Will Pfleger <pfleger.will@gmail.com>
Signed-off-by: Will Pfleger <pfleger.will@gmail.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.
Summary
Implements the foundation for the personas-as-events architecture (PR 1 of 2 in the post-852 roadmap). Defines a new Nostr event kind for persona definitions, adds client-side serialization, a local SQLite retention store for offline boot, and migrates existing
personas.jsonentries to events.Stack: this PR → #945
Changes
Relay side
crates/buzz-core/src/kind.rs: RegisterKIND_PERSONA = 30175as NIP-33 parameterized replaceable, keyed by(pubkey, kind, d_tag)whered_tagis the plaintext persona slug. Compile-time assertion verifies range membership.crates/buzz-relay/src/handlers/ingest.rs: Allowlist kind:30175 underScope::UsersWrite, mark as global-only (never channel-scoped). New tests verify scope and global-only classification.Desktop client side
desktop/src-tauri/src/managed_agents/persona_events.rs(new): SerializePersonaRecord↔ kind:30175 event. JSON content body containsdisplay_name,system_prompt,avatar_url,runtime,model,provider,name_pool,env_vars. Publish/fetch functions via relay HTTP API.desktop/src-tauri/src/managed_agents/retention.rs(new): SQLite retention store withINSERT OR REPLACEon(kind, pubkey, d_tag)for NIP-33 latest-wins semantics. Pending-sync queue for deferred relay publish. Enables offline boot when relay is unreachable.desktop/src-tauri/src/managed_agents/personas.rs: Addload_from_retentionhelper (wired up but not yet activated as the primary read path — that is PR 2 territory).desktop/src-tauri/src/migration.rs:migrate_personas_to_eventsruns after identity resolution, signs every retained event with the owner's real keys (no placeholder events). Idempotent via retention-row-exists check (the data is the sentinel). Core logic extracted intomigrate_personas_in_dirfor unit testing.desktop/src-tauri/src/lib.rs: Migration call moved afterresolve_persisted_identityso keys are always available. Owner keys cloned fromAppStateand passed to migration.desktop/src-tauri/Cargo.toml: Addrusqlitedependency with bundled feature.Key design decisions
INSERT OR REPLACEkeyed on(kind, pubkey, d_tag)in SQLite. NIP-33 replaceable semantics handle relay-side idempotency.load_personasstill reads frompersonas.jsonas before. The retention-based load path is a helper for PR 2 (instantiation flow).Not in scope (PR 2 — #945)
mem/personaengram)