Repository navigation
fix(messages): show verified workflow ownership separately from signer - #663
Conversation
Signed-off-by: Carl <32a2e2c9d428ee08902cab75d956da2c1d235a22d4766b0dd4138bf6e2e5db1d@buzz.block.builderlab.xyz>
kalvinnchau
left a comment
There was a problem hiding this comment.
🤖 One non-blocking profile-loading defect is noted inline. No security or core lifecycle merge blockers were identified. Live relay execution and rendered browser/native flows remain unverified.
wpfleger96
left a comment
There was a problem hiding this comment.
🤖 Thanks for this. No blockers at this head.
Only a message signed by this community's relay can show "owned by". workflowOwner() (src/features/relay/workflow-attribution.ts:9-25) requires kind 9, signed by the session's explicit NIP-11 self, with exactly one ["buzz:workflow","true"] and exactly one lowercase-hex buzz:workflow-owner. That is the same rule block/buzz applies server-side (verified_workflow_owner in crates/buzz-acp/src/lib.rs), and the relay's workflow sender emits exactly those tags (crates/buzz-relay/src/workflow_sink.rs). A workflow author can't add a second owner tag, because duplicates are rejected.
The admission proof can't be borrowed. checkedEvent adds an event to the private WeakSet only after verifying a copied wire payload and freezing it (src/features/relay/events.ts:47-53,109-125). The HTTP verifier's memo also re-hashes and compares the exact signature (events.ts:59-72). Unsigned local intent, spread copies and caller-supplied verifiedSymbol objects all fall back to the signer. The authority is always archiveAuthority: info.self on native (native.ts:284,410), and on the broker path it is validated equal to relayAuthor (transport.ts:412-419). It is never the saved contact key. Offline sessions pass no authority, and each community gets its own transport.
Ownership changes only what is displayed. Every place that computes an owner goes through the same gate: fold, thread view, unread/activity, Inbox, incoming notifications and Search. Edit/delete, Search and Inbox sender filters, mention handling, presence and agent identity all still key on authorId.
The running UI behaves the same way. Headless Chromium and WebKit renders of the message gallery (light/dark, wide/narrow, 8/8 combinations) show the workflow byline and the separate owner and relay keys for a real workflow post. Wrong-signer, unsigned, copied-proof, restored-offline and wrong-community fixtures all show the signer with no ownership claim. The nine touched or new test files pass locally (379 tests). Removing the relay-signer check turns the wrong-signer and missing-authority tests red.
Optional, non-blocking:
- The media-review comment list shows the workflow owner but fetches only signer profiles (
src/features/messages/MediaReviewViewer.tsx:241-250,581-589).useRowProfiles()reads cached profiles only, so opening media comments with a cold cache can leave a key prefix where the owner's name should be. Building that fetch list fromrowProfileIds()fixes it. The thread-view half of the inline P3 comment is already covered in practice: relay workflow posts always carry an ownerptag, andThreadPanelfetches mentions. - No test catches a consumer that reads the raw owner tag instead of the verified field. Changing Search to read
buzz:workflow-ownerstraight from the event (src/app/shell/useSearchMessages.ts:198-202) keeps all 379 tests green. A Search case whose hit is signed by a non-relay key with workflow tags, asserting it renders the signer, fails under that change and passes at head. - The description still says "This is a draft" and lists open manual checks, but the PR is marked ready and has no
buzz-review-completed. Either back to draft or refresh the description.
Reviewed head 43cff266f637f62803b696affa23edb69269ad9a against base 51df533e9e46da6c725bcec97372b13b657225b5. Two independent source reviews, plus headless gallery renders and test-mutation runs at head. Hosted CI is green at this head (Windows native skipped).
Load workflow-owner profiles on direct thread and media opens. Cover non-relay Search spoofing and preserve main's effective-author permissions while disclosing the actual relay signer. Signed-off-by: Carl <32a2e2c9d428ee08902cab75d956da2c1d235a22d4766b0dd4138bf6e2e5db1d@buzz.block.builderlab.xyz>
|
Addressed the review follow-ups in 9917f0a and merged current main (fc594b2) to resolve the conflicts.
Carl, an automated reviewer, commenting via Wes’s GitHub account. |
…ge-delete * origin/main: feat: add mobile pairing Settings plugin (#595) Cache the floating action bar's MediaQueryList (#652) feat(messages): copy and paste mentions, channel refs and links (#579) Remember desktop window size and position across launches (#655) fix(messages): show verified workflow ownership separately from signer (#663) fix(activity): collapse duplicate channel-wide agent indicators (#664) docs: make native development the acceptance path (#666) Sign plugin releases with NIP-PS (#465) Signed-off-by: Luna <0828be588d5c9c4d092c5b1cf01761d5aef63d6b104807fd8e8284d517dd1a99@buzz.block.builderlab.xyz> # Conflicts: # src/features/relay/session.ts # src/features/relay/unread.test.ts # src/features/relay/unread.ts
…iles * origin/main: Install the bundled Buzz CLI skill on desktop startup (#667) refactor: share status, DM and sidebar intent policy across hosts (#683) fix(sessions): show enabled page in navigation (#648) fix(composer): replace exact emoji shortcodes on closing colon (#584) refactor(mentions): let the Mentions plugin own what the chooser offers (#669) Refresh agent cards and move saved teams to Agents (#656) Let the channel "caught up" mark replace ordinary message read marks (#590) test(rust): create test executables without an open write descriptor (#665) feat: add mobile pairing Settings plugin (#595) Cache the floating action bar's MediaQueryList (#652) feat(messages): copy and paste mentions, channel refs and links (#579) Remember desktop window size and position across launches (#655) fix(messages): show verified workflow ownership separately from signer (#663) fix(activity): collapse duplicate channel-wide agent indicators (#664) docs: make native development the acceptance path (#666) Sign plugin releases with NIP-PS (#465) test(browser): explain WebKit fetch refusals during a page reload (#650) Add Claude Code harness setup in Settings (#654) fix: restore workflow controls, run feedback and workflow message authors (#661) Signed-off-by: Luna <0828be588d5c9c4d092c5b1cf01761d5aef63d6b104807fd8e8284d517dd1a99@buzz.block.builderlab.xyz>
…2385) ## Problem A workflow kick-off message has the byline **Workflow · owned by Priya Shah** and an ⓘ button. The ⓘ card opened only on click. It repeated the owner's name from the byline beside it and printed the owner and relay npubs in full. At 224 × 438 CSS px it covered the message it described and reached down over the composer. ## Change The card now shows its title and one sentence: > **Workflow message** > Sent automatically by a workflow. Its owner set it up but didn't send this message personally. - **Removed:** the owner row, the owner and relay npubs, and the "Workflow and run details are not included in this message" footer. The card is now 224 × 138 CSS px and shows no keys. - **Opens on pointer rest:** the trigger uses the shared `PopoverTrigger openOnHover="preview"` bucket, so the card opens after a 0.3 s rest on ⓘ. Click and Enter still open it, and Escape closes it and returns focus to ⓘ. A hover open doesn't move focus. The `IconButton` has no `title`, so no tooltip competes with the card. - `WorkflowByline` no longer takes a `signer` prop, and the unused `.workflowDetails` CSS is deleted. - `docs/workflows.md` describes the new card. It also says the row keeps the signing key for row identity only and doesn't display it. - The message-gallery specimen description (`tests/fixtures/message-gallery/examples.ts`) matches the new card. The owner's npub is still available: the owner's name in the byline opens their profile, which offers **Copy npub**. `signerId` stays on `ChannelMessage` because `sameRow` in `row-identity.ts` still reads it. Removing it is a separate cleanup. ## Decisions to confirm 1. **The relay signing key is no longer shown anywhere in the UI.** This card was the only place it appeared, and #663 added it there on purpose, so this PR reverses that disclosure. The card still says the owner didn't send the message personally. If the relay key is still needed, the smallest addition is a **Copy relay key** action in the card. 2. **Hover-open through `openOnHover="preview"`.** The card used to open only on click. It now uses the same hover bucket as the app's other preview popovers. One behaviour comes from Base UI: a click within 500 ms of a hover open keeps the card open, and a later click toggles it closed. Every other `openOnHover="preview"` popover does the same, so the card keeps the library behaviour rather than overriding it, and a test pins it. Touch input never triggers the hover. Moving keyboard focus onto ⓘ doesn't open the card; Enter does, as with the other hover popovers. ## Before / after  Side-by-side video, 13 s, with the same scripted pointer path in both panes. The pointer glides onto the message, then onto ⓘ at about 3.5 s. **After** opens the card at about 4.8 s from the pointer rest alone. **Before** opens only on the click at 5.5 s. From 7 s the pointer moves down the card, and Escape closes it at 11.5 s. The panes are labelled BEFORE and AFTER and carry no other annotations. https://github.com/user-attachments/assets/9dd8340c-8bf9-42bd-8da7-cbd56aec4b19 Individual screenshots of the open card:   These were recorded in Chromium on the browser-journey fixture app (production frontend, dev broker and modeled relay), light theme, 1024 × 640 CSS px at 2×, pointer only. The npubs are fixture keys. The before take is from the tree before `a06c9808a`. The after take is from `2627d5d70`; the only later commit, `f5672282e`, changes docs and a test constant. ## Tests `src/features/messages/MessageRow.test.tsx`: - **Keyboard and click:** Enter opens the card, which has the new description and no npub or owner name. Escape closes it and returns focus to ⓘ. A click opens it again. The click uses `fireEvent.click`, which moves no pointer, so the hover timer can't open the card in its place. - **Hover:** nothing opens at 299 ms. At 300 ms the card opens, no tooltip appears and focus stays on the element that had it before the hover. A click inside Base UI's patient-click window keeps the card open, and Escape closes it. After a fresh hover open, a click once the 500 ms window has passed closes it. Each new assertion was checked to fail when its behaviour breaks: removing `openOnHover`, blocking Base UI's click handler, moving focus during hover, and clicking at 499 ms. ## Validation - `MessageRow.test.tsx`: 161/161 passed. - Typecheck, lint and `design:check` passed. - The push used `--no-verify`, so the pre-push hook didn't run. **Not tested yet:** dark theme, narrow widths, enlarged text, keyboard-only use, WebKit, and a manual check in the app. One case worth checking by hand: rest on ⓘ until the card opens, wait more than half a second, then click. The card should close. ## Checklist - [x] Agent review ran on `a06c9808a` and `2627d5d70`. Its findings (two loose test assertions, a stale docs passage, a bare `500` in a test) are fixed in `2627d5d70` and `f5672282e`. - [x] An agent exercised the change in the browser fixture app (Chromium, pointer, light theme). See the recordings above. - [ ] A human has tested it in the app. 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Signed-off-by: Matt Toohey <contact@matttoohey.com> Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Summary
Workflow output identifies the automation with Workflow · owned by …, an automation avatar, a linked owner profile, and disclosure of the separate owner and relay public keys.
workflowOwnerIdthrough channel/thread rows, Search, Inbox/activity, message-link previews, reply context, media-review markers and notification text. Ownership requires an admitted immutable kind-9 event signed by the selected community’s explicit NIP-11self, with canonical, unambiguous workflow tags. Admission verification is reused, not repeated during folds.fc594b28964a39e401f2c86c3d89bfd14f9c2686, preserving fix: restore workflow controls, run feedback and workflow message authors #661’s effective-author attribution and owner edit/delete permissions. Channel/threadauthorIdcan now be the owner; a separatesignerIdpreserves the actual relay key in the disclosure. Workflow rows do not group into the owner’s ordinary messages.rowProfileIds. Add cold-cache integration regressions and a Search spoof case that must display the ordinary signer, while preserving signer-basedfrom:filters.Signing, sender filters, mention policy, agent authorization and relay participant summaries are unchanged by this presentation feature. Cached-only startup has no explicit authority and retains the signer fallback. No backend changes, migrations or workflow execution.
Trust boundary: the workflow proof gate controls the automation label, not main’s separate effective-author policy. With explicit authority, an unadmitted copy can receive main’s owner attribution without a workflow label. Production consumers must retain admission-owned events; source review found no production copy path. This repair retains the proof gate and documents that limitation rather than weakening verification or changing #661’s authorization behavior. It does not claim that every rejected proof falls back to the signer.
The FOUNDATION session change passes existing explicit authority into consumers and was human-authorized. Workflow/run navigation and historical execution snapshots remain separate work.
Validation
Checked head:
9917f0a2bfad76a9c3c4edd8c62ba8df7b996462, tree388ff73336dd7aad2378b495adecee919508bf81.ptag. Reverting each profile-demand fix fails its regression. Mutating Search to read the raw owner tag fails the non-relay spoof case. Initial fixture failures were corrected at the admission boundary, without weakening production trust.pshape andsigningAuthorityoption. These are offline renderer checks, not live/native acceptance. Earlier live channel/thread checks were at the original head and are not claimed as a rerun of this update.No browser journey cases were added or removed. Trust/projection and profile-loading coverage stays at the unit/integration layer. Existing screenshots below predate the merge.
Remaining gates / try it
This remains a draft, not a deployment. The human approved the original live channel presentation before compact surfaces were completed.
Screenshots
Local signed gallery fixtures with generated example keys. No private messages or live-account screenshots.