镜像站点 · 本页由第三方 GitHub 只读镜像提供,非 GitHub 官方站点,不接受任何登录或凭据输入。前往 github.com
Skip to content

fix(messages): show verified workflow ownership separately from signer - #663

Merged
wesbillman merged 2 commits into
mainfrom
carl/workflow-identity
Oct 6, 2026
Merged

wesbillman merged 2 commits into
mainfrom
carl/workflow-identity

Conversation

@wesbillman

@wesbillman wesbillman commented Oct 6, 2026 •

Copy link
Copy Markdown
Collaborator

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.

  • Carry a display-only workflowOwnerId through 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-11 self, with canonical, unambiguous workflow tags. Admission verification is reused, not repeated during folds.
  • Integrate main through fc594b28964a39e401f2c86c3d89bfd14f9c2686, preserving fix: restore workflow controls, run feedback and workflow message authors #661’s effective-author attribution and owner edit/delete permissions. Channel/thread authorId can now be the owner; a separate signerId preserves the actual relay key in the disclosure. Workflow rows do not group into the owner’s ordinary messages.
  • Fix direct thread/media opens to request workflow-owner profiles through rowProfileIds. Add cold-cache integration regressions and a Search spoof case that must display the ordinary signer, while preserving signer-based from: 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, tree 388ff73336dd7aad2378b495adecee919508bf81.

  • Full Vitest: 554 files / 8,039 tests passed at this commit. Production build, app types, design types and design/message-gallery builds passed. Mandatory commit/push hooks passed, including 292 related files / 5,402 tests and design guards. No hooks bypassed; no Rust changes relative to main.
  • Cold thread/media tests use real sessions with a gated owner-profile fetch and no incidental owner p tag. 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.
  • Current-head Chromium and WebKit gallery smoke checks, at 1100px and 390px, passed automation labelling, owner-link callbacks and distinct owner/signer disclosure. Fixtures use main’s leading-owner-p shape and signingAuthority option. 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.
  • Independent source review covered the review fixes and semantic merge. Disclosure tests assert exact owner and signer npubs; fold regressions preserve owner edits/deletes. The proof-policy limitation above is explicitly documented.

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.

  1. View an existing workflow post. Its owner link should open the owner’s profile; details must show a distinct relay signer and state that the owner did not sign the message.
  2. Open a thread/media review directly with a cold owner-profile cache; the owner name should load without visiting the timeline first. Check Search, Inbox/activity and a message-link preview for the same automation label without borrowing the owner’s photo. No workflow run is needed.
  3. Confirm the added surfaces, current-head required CI/DCO and human/code-owner approval before readiness. Packaged-native notification presentation and the broad browser journey suite have not been run locally for this change. The media-review marker retains the existing generic “W” avatar and has no new dedicated marker-label test.

Screenshots

Local signed gallery fixtures with generated example keys. No private messages or live-account screenshots.

Workflow attribution and missing-owner fallback

Workflow owner versus relay signer disclosure

Signed-off-by: Carl <32a2e2c9d428ee08902cab75d956da2c1d235a22d4766b0dd4138bf6e2e5db1d@buzz.block.builderlab.xyz>
@wesbillman
wesbillman marked this pull request as ready for review October 6, 2026 19:00
@wesbillman
wesbillman requested review from a team and comp615 as code owners October 6, 2026 19:00

@kalvinnchau kalvinnchau left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤖 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.

Comment thread src/features/messages/ThreadPanel.tsx Resolved

@wpfleger96 wpfleger96 left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤖 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 from rowProfileIds() fixes it. The thread-view half of the inline P3 comment is already covered in practice: relay workflow posts always carry an owner p tag, and ThreadPanel fetches mentions.
  • No test catches a consumer that reads the raw owner tag instead of the verified field. Changing Search to read buzz:workflow-owner straight 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).

@wesbillman
wesbillman marked this pull request as draft October 6, 2026 21:40
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>
@wesbillman

Copy link
Copy Markdown
Collaborator Author

Addressed the review follow-ups in 9917f0a and merged current main (fc594b2) to resolve the conflicts.

  • Fixed cold-owner profile loading in both direct thread and media views; replied to and resolved the inline thread.
  • Added the non-relay Search spoof regression, with mutation evidence. Preserved fix: restore workflow controls, run feedback and workflow message authors #661 owner permissions while disclosing the separate relay signer.
  • PR remains draft, and the description now reflects the merged behavior, current validation and remaining human checks. Full Vitest: 8,039 passed; builds, mandatory hooks and Chromium/WebKit gallery checks passed. Hosted checks are still the merge gate.

Carl, an automated reviewer, commenting via Wes’s GitHub account.

@wesbillman
wesbillman marked this pull request as ready for review October 6, 2026 22:10
@wesbillman
wesbillman merged commit 5feaddb into main Oct 6, 2026
22 checks passed
@wesbillman
wesbillman deleted the carl/workflow-identity branch October 6, 2026 22:25
johnmatthewtennant pushed a commit that referenced this pull request Oct 6, 2026
…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
johnmatthewtennant pushed a commit that referenced this pull request Oct 7, 2026
…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>
matt2e added a commit that referenced this pull request Nov 4, 2026
…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

![The open workflow ⓘ card before and after, side by side in #Alpha.
Before, 224 × 438 CSS px: a relay-attribution paragraph, a Workflow
owner row repeating Priya Shah, the full owner public key, the full
relay npub and a run-details footer, with the card reaching down over
the composer. After, 224 × 138 CSS px: the title Workflow message and
one sentence saying a workflow sent it and its owner didn't send it
personally.](https://github.com/user-attachments/assets/f4b66a6a-d6c7-4a58-bd6b-5e352bbd80cf)

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:

![Before: the ⓘ card opened by click, 224 × 438 CSS px, showing the
attribution paragraph, the Workflow owner row, the owner public key, the
relay npub and the run-details footer, covering the reply and the
composer.](https://github.com/user-attachments/assets/e39267f7-867a-484c-adcd-c1c3b6280888)

![After: the ⓘ card opened by resting the pointer on ⓘ, 224 × 138 CSS
px, showing only the title Workflow message and one sentence; the
composer stays
clear.](https://github.com/user-attachments/assets/b22ed0e8-79e2-43c8-be10-bc77925bc1d6)

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>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants