Skip to content

feat(workflow): add owner-only agent mentions for workflow messages - #9227

Merged
brow merged 9 commits into
mainfrom
duncan/workflow-secure-mode
Nov 12, 2026
Merged

brow merged 9 commits into
mainfrom
duncan/workflow-secure-mode

Conversation

@wpfleger96

@wpfleger96 wpfleger96 commented Nov 6, 2026 •

Copy link
Copy Markdown
Member

Adds owner-only agent mentions, a relay setting, BUZZ_WORKFLOW_AGENT_MENTIONS. Its values are any, the default and today's behaviour, and owner. Any other value fails startup. With owner, a workflow message can direct an agent only if the run's cause and definition allow it. The setting is relay-wide.

Recording what caused each run. workflow_runs gets cause_source and cause_pubkey columns. Migration 0087 adds them with named checks marked NOT VALID, so it does not scan existing rows under its lock; 0088 validates the checks separately. create_workflow_run now takes a required RunCause, so every place that creates a run has to set one:

  • Event trigger: the cause is the signer of the message, reaction or diff. For a reaction, that is the reactor, not the author of the message being reacted to.
  • Cron and interval schedules: the cause is the timer.
  • Webhook: the caller is unknown.
  • Manual trigger: the cause is the invoking pubkey.

Rows written before this change have no cause. A stored source without the pubkey it requires, or with one it forbids, is read as no cause.

Deciding eligibility. A run's messages may direct agents only when all of these hold:

  • the cause is a schedule, or an event or manual trigger whose pubkey is the workflow owner's;
  • every step of the definition that is running is send_message or delay. This is an allowlist, so any other action, including one added later, makes the definition ineligible, even when an if: skips that step;
  • the run did not resume after an approval.

Webhook runs and runs with no recorded cause are never eligible. The executor decides this from WorkflowConfig::agent_mentions, and passes ActionSink::send_message the owner's stored step text with a may_direct_agents flag. The sink adds buzz:workflow-mention tags only when the flag is set, and returns SentMessage { event_id, agent_mentions_withheld }, which is true only when it left out a tag that a member named in both the stored and rendered text would otherwise have received. Webhook and manual runs start through execute_run, so execute_from_step is used only to resume after an approval.

The gate. With owner, the relay adds buzz:workflow-mention only to messages from eligible runs. Messages from other runs still post with their plain p tags and still notify people. With any, mention tags are added exactly as before.

Making it visible. When a send_message step leaves the tags out:

  • its output in the run trace gets "agent_mentions": {"withheld": true, "reasons": [...]}, listing each reason that applies: cause (not started by the owner or a timer), steps (a step other than send_message or delay), resumed (resumed after an approval). The key is absent when nothing was withheld. The trace is what the app's run history shows;
  • the relay logs it at info level under the buzz_relay::workflow_agent_mentions target (visible under the default filter) with the community, workflow, run, step and reasons;
  • the counter buzz_workflow_agent_mentions_withheld_total increases once per reason, labelled reason.

Discovery. The NIP-11 document always carries "workflow_agent_mentions": "any" | "owner", so a missing field means an older relay.

NOSTR.md now specifies buzz:workflow-mention for clients: what the tag means, the checks an agent makes before acting, that a relay may leave it out under a policy, and the NIP-11 workflow_agent_mentions field. The relay policy itself is in the new operator guide, docs/operator-workflow-agent-mentions.md: the setting and its values, what counts as a run's cause for each trigger, which steps are allowed, the trace key, the log and metric, the NIP-11 field, when to use owner, rollout across pods, and what it doesn't cover. The TESTING.md and .env.example entries link to it.

wpfleger96 and others added 4 commits November 6, 2026 18:40
Record what caused each workflow run and, when BUZZ_SECURE_WORKFLOWS is on,
stamp buzz:workflow-mention only on messages from runs that the owner or a
timer caused and whose definitions use only send_message and delay steps.

Signed-off-by: Will Pfleger <[email protected]>
Co-authored-by: Duncan <dcfd242e557282d7a1e2cf2e6877522682f1e5c6156dc92ca7d90eaedd3b0f95@buzz.block.builderlab.xyz>
… resume

Signed-off-by: Will Pfleger <[email protected]>
Co-authored-by: Duncan <dcfd242e557282d7a1e2cf2e6877522682f1e5c6156dc92ca7d90eaedd3b0f95@buzz.block.builderlab.xyz>
Co-authored-by: Will Pfleger <[email protected]>
Signed-off-by: Will Pfleger <[email protected]>
…e-mode

* origin/main: (36 commits)
  feat(relay): let an operator mint invites for a community owner or admin (#9265)
  fix: inline ARM64 atomics in Sprig image build (#9291)
  fix(mobile): show observed thread replies in Activity (#9286)
  Enable mobile huddles with Codex agents (#9269)
  fix(mobile): read mentions taller than the screen (#9243)
  test(relay): give every media e2e upload its own blob hash (#9272)
  Add invite preview video and fallback poster (#9282)
  NEXUS-4336: Wait for desktop-provider socket ownership proof (#9270)
  fix: unblock image audit with patched MCP SDK (#9287)
  feat(ci): allow admin dispatch of desktop candidate validation (#9288)
  feat(mobile): tag the reacted-to message author on reactions (#9141)
  fix(mobile): steady New DM picker rows and animate picks (#9249)
  perf(mobile): keep remote images on disk and show small ones as thumbnails (#9158)
  fix(relay): deliver slow cross-node events late instead of closing their recipients (#9235)
  fix(mobile): read only shown events from filtered Activity rows (#9215)
  fix(mobile): show the channel name as the notification subtitle (#9173)
  fix(mobile): keep Home's header clean under sheets (#9152)
  fix(mobile): show already-loaded custom emoji in the iOS reactions sheet (#9150)
  mobile: add follow and remind actions to inbox row menu (#9142)
  fix(mobile): start no threads in Solo chats (#9123)
  ...

Signed-off-by: Will Pfleger <[email protected]>
wpfleger96 and others added 2 commits November 9, 2026 16:40
…and NIP-11 field

BUZZ_SECURE_WORKFLOWS=true|false becomes BUZZ_WORKFLOW_AGENT_MENTIONS=any|owner.
A send_message step that withholds buzz:workflow-mention tags records
agent_mentions {withheld, reasons} in its trace output, logs at info and
counts buzz_workflow_agent_mentions_withheld_total by reason. The NIP-11
document always advertises workflow_agent_mentions.

Co-authored-by: Duncan <dcfd242e557282d7a1e2cf2e6877522682f1e5c6156dc92ca7d90eaedd3b0f95@buzz.block.builderlab.xyz>
Signed-off-by: Will Pfleger <[email protected]>
…e-mode

* origin/main:
  ci(docker): skip relay image publishes for main pushes outside the build context (#9310)
  Add manual-only relay image publishing (#9303)
  fix(mobile): bump Flutter to 3.47.9 for Android TLS fix (#9309)
  Serve video and poster previews for invite links (#9306)
  docs: point the README to Buzz 1.0 in block/buzz-app (#9296)
  fix(db): give the replica-fence probe its own pool and instrument it (#9297)
  fix(relay): never close connections when cross-node fan-out falls behind (#9302)
  test: fix timing-dependent unit tests in buzz-cli, buzz-agent and buzz-admin (#9273)
  fix(ci): classify hermit retry timeouts by watchdog kill, not whole seconds (#9274)
  test(ios): wait for a key, foreground-active window before presenting in RunnerTests (#9275)
  test(desktop): wait on real readiness signals in audience and community-rail specs (#9277)

Signed-off-by: Will Pfleger <[email protected]>
@wpfleger96 wpfleger96 changed the title feat(workflow): add secure workflows mode for agent-directing mentions feat(workflow): add owner-only agent mentions for workflow messages Nov 10, 2026
The sink now returns whether it left off any buzz:workflow-mention tag, and
the executor records the trace key, log and counter only then.

Co-authored-by: Duncan <dcfd242e557282d7a1e2cf2e6877522682f1e5c6156dc92ca7d90eaedd3b0f95@buzz.block.builderlab.xyz>
Signed-off-by: Will Pfleger <[email protected]>
@wpfleger96
wpfleger96 force-pushed the duncan/workflow-secure-mode branch from 2a12e7e to a9f0559 Compare November 10, 2026 01:20
@wpfleger96
wpfleger96 marked this pull request as ready for review November 12, 2026 18:18
@wpfleger96
wpfleger96 requested a review from a team as a code owner November 12, 2026 18:18
@wpfleger96
wpfleger96 deployed to codex-review November 12, 2026 18:19 — with GitHub Actions Active
@github-actions github-actions Bot added the codex-security-review-current The posted Codex security review matches its recorded range. label Nov 12, 2026
@github-actions

Copy link
Copy Markdown

🔐 Codex Security Review

Note: This is an automated, security-focused review generated by Codex.
Use it as a supplement to human review; false positives are possible.

Scope

  • Exact PR diff: 0c58b33e64e3c2297dd0c0551d9bff4dacf84082...41f93dbda6516c80e9e15c5bf849938205da174d
  • Model: gpt-6.1-sol

💡 Click "edited" above to see earlier reviews for this PR.


Review Summary

Overall Risk: NONE

No concrete security, correctness, or reliability findings were identified in the authorized PR range.

Findings

No concrete security, correctness, or reliability findings were identified.

Notes

  • Review used read-only source and history inspection. Builds, tests, and repository scripts were not executed.

Generated by Codex Security Review |
Requested by: @wpfleger96 |
Workflow run

@brow
brow merged commit fc8c802 into main Nov 12, 2026
98 checks passed
@brow
brow deleted the duncan/workflow-secure-mode branch November 12, 2026 19:34
TheSentinel454 added a commit that referenced this pull request Nov 12, 2026
…04 (#9418)

## Summary

Two separate CI breaks are hitting every PR right now. Each one blocks a
required check, and neither fix can go green on its own, so this PR
fixes both.

**1. Rust test targets stopped compiling on main at fc8c802.** #9227
added a required `RunCause` argument to `create_workflow_run` and a
`may_direct_agents` flag to `ActionSink::send_message`. Two tests that
merged around the same time still used the old signatures:

- `attach_rejects_run_from_another_workflow` in
`crates/buzz-db/src/store/workflow.rs` (from #8317)
- the agent-DM refusal test in `crates/buzz-relay/src/workflow_sink.rs`
(from #9371)

Each PR passed CI on its own base. Together they break Rust Lint, Unit
Tests, Windows Rust and the Desktop E2E Relay artifact build (main CI
run 43291564834). The fix passes `RunCause::Schedule` and `true`,
matching the neighbouring tests. Neither test is about run cause or
agent mentions, so neither assertion changes.

**2. Desktop E2E Integration fails on GitHub's new Ubuntu 26.04
runners.** `ubuntu-latest` is rolling to 26.04, and Playwright 1.60 (our
lockfile) fails with `Playwright does not support chromium on
ubuntu26.04-x64`. Playwright 1.61 is the first release that knows
ubuntu26.04. This PR bumps `@playwright/test` to `^1.65.0` in
`desktop/`, `web/` and `admin-web/` and regenerates `pnpm-lock.yaml`. It
also pins every `ubuntu-latest` job in `ci.yml` and `_ci-*.yml` to
`ubuntu-26.04`, so the next runner image change arrives in a reviewed
diff. macOS and Windows jobs are unchanged.

The newer Chromium bundled with Playwright 1.65 changed two smoke specs.
It reports Tailwind's `items-end` as `align-items: flow-end`
(`doctor-states` now accepts `flex-end` or `flow-end`), and it draws the
textarea resize grip differently, so the
`workflow-template-variable-autocomplete` baseline is refreshed from the
ubuntu-26.04 CI output.

It also exposed a timing race in `profile.spec.ts` "owned agent profile
stays in parity": when the Message click lands while the profile panel
is still settling, Playwright's scroll-into-view retry scrolls the
profile body, which hides the owned-agent action row (aria-hidden +
inert) for good. The test now pins the body to the top and waits for the
row before clicking (12/12 repeats plus the full profile spec pass on
Linux).

## Verification

- `cargo clippy -p buzz-db -p buzz-relay --tests -- -D warnings` is
clean on fc8c802 with commit 1 applied.
- `pnpm install --frozen-lockfile` succeeds with the new lockfile, and
`playwright --version` reports 1.65.0.
- `tsc --noEmit` passes for `desktop`, `web` and `admin-web`.
- This PR's CI is the E2E check on 26.04.

## Notes

- Workflows that don't run on PRs (release, helm charts, sprig, image
builds, codex-security-review) still use `ubuntu-latest`. This PR can't
exercise them, so they're left for a follow-up.
- The hermit-pinned actionlint (1.7.12) doesn't know the `ubuntu-26.04`
label yet. CI only actionlints `codex-security-review.yml`, which this
PR doesn't touch.

## Simpler

No production code changes. The runner pin swaps an implicit moving
target for an explicit one.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Signed-off-by: Luke Tornquist <[email protected]>
Co-authored-by: Elrond <28d6302a099e5225b02c4155ac4236e4912603df2ab08dbfc2f4fef08ce598c8@buzz.block.builderlab.xyz>
Co-authored-by: Claude Opus 5.5 <[email protected]>
TheSentinel454 added a commit that referenced this pull request Nov 12, 2026
Rebasing onto main picked up #9227, which made `RunCause` a required
argument to `create_workflow_run`. Pass `RunCause::Schedule` in the new
reference and delete-race tests, matching the existing callers.

Co-authored-by: Elrond <28d6302a099e5225b02c4155ac4236e4912603df2ab08dbfc2f4fef08ce598c8@buzz.block.builderlab.xyz>
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Signed-off-by: Luke Tornquist <[email protected]>
ravarora2 added a commit that referenced this pull request Nov 12, 2026
…small

* origin/main:
  feat(workflow): add owner-only agent mentions for workflow messages (#9227)
  Adapt iPhone Duo layouts, rail navigation, and native sheets (#8568)
  End last-human agent Huddles and preserve mobile transcript access (#9375)
Signed-off-by: Ravneet Arora <[email protected]>
matt2e added a commit that referenced this pull request Nov 14, 2026
… closes (#9440)

## Summary
`desktop/src/features/terminal/TerminalSubstrate.test.mjs` can pass
every test and then run the Desktop JS job out of memory several minutes
later. This moves the React Testing Library unmount from `beforeEach` to
`afterEach`, so no tree is still mounted when the jsdom window closes.
Only the test file changes (6 insertions, 2 deletions).

Commit (cherry-picked unchanged from a local flake-fix series):
- `test(desktop): unmount every TerminalSubstrate tree before the window
closes`

### CI sighting

One sighting was found in the CI runs from 7 to 12 November 2026 that
were searched:

- 9 November 2026, "Desktop Domain / Desktop JS", run 42706004493
attempt 1 (on #9227; the rerun passed):
-
https://github.com/block/buzz/actions/runs/42706004493/job/129463488242

In that job the suite reported 6,950 of 6,951 passing. The log shows `An
update to TerminalSubstrate inside a test was not wrapped in act(...)`
at 21:54:33Z, then `FATAL ERROR: Ineffective mark-compacts near heap
limit Allocation failed - JavaScript heap out of memory` at 22:02:17Z,
and the file is reported as failed at file level (`test at
src/features/terminal/TerminalSubstrate.test.mjs:1:1`) after 504.6 s.

### Root cause

The file called `cleanup()` only in `beforeEach`, so the last test's
tree was still mounted when `after(() => dom.window.close())` ran. If
ThemeProvider's asynchronous palette update landed after that close, it
re-rendered the substrate, the splash effect's palette guard passed, and
`requestAnimationFrame` on the closed window started a fresh jsdom
interval that nothing cleared. That 60 Hz loop repainted the banner into
the test's recording `getContext` stub indefinitely, and `paintLog` grew
until the heap limit. No individual test hangs, so a per-test timeout
would not catch it.

### Fix

`afterEach(() => cleanup?.())` unmounts every tree, including the last,
before `after()` closes the window. The `beforeEach` call became
redundant and is removed. No assertions change, and no timeouts, retries
or sleeps are added.

### Related issue
N/A (flake fix). No existing issue or PR for this failure was found.
Three open PRs touch the same test file (#5057, #5485, #7777); none of
them changes where `cleanup` runs.

### Testing
Human testing is still required. This PR does not carry
`buzz-review-completed`; the author has not yet run this test themselves
on this branch.

#### Verification of the fix (before this branch was split out)
- The test file, 5 consecutive runs: 25 of 25 tests passed each time in
about 6.8 s, with no `act` warning.
- The CI out-of-memory failure was not reproduced with the real test
file. Its mechanism was reproduced in a scratch harness with the same
stubs: without cleanup before the window close, 3 of 3 runs left the
palette unresolved at close, added 11,556 `paintLog` entries per 100 ms,
kept one live timer, and never exited. With cleanup before the close, 3
of 3 runs added no entries, left no timers, and exited on their own.

#### Local checks on this branch
The push ran the repository's full pre-push hook set from a checkout of
this branch's head (`7f4117340`), with no lanes excluded and without
`--no-verify`. The branch is one commit behind `main`; that commit
changes only mobile and docs files, none of them touched here.

Ran and passed:
- `branch-skew`, `push-head-scope` and `file-size-check`
- `desktop-check` (`pnpm check`)
- `desktop-typecheck` (`pnpm typecheck`)
- `desktop-test` (`pnpm test`, the full desktop unit suite, which
includes `TerminalSubstrate.test.mjs`)

Did not run:
- `rust-tests`, `desktop-tauri-checks` and `mobile-checks`. The hook
selects lanes by changed path, and this branch changes no Rust, Tauri or
mobile file.
- Desktop builds and the Playwright E2E suites, which are CI-only.
- `just ci`.
- The 5-run repetition and the scratch harness above were not repeated
on this branch.

A green run does not prove the flake is gone: the failure needs the
palette update to land after the window closes.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Signed-off-by: mtoohey <[email protected]>
Signed-off-by: Matt Toohey <[email protected]>
Co-authored-by: Claude Fable 5.1 <[email protected]>
wpfleger96 added a commit that referenced this pull request Nov 14, 2026
#9500)

…er (#9371)"

This reverts commit a09b84d, resolved
against current main (#9227 changed the neighboring workflow_sink
tests).

Supersedes #9393

Signed-off-by: Joah Gerstenberg <[email protected]>
Co-authored-by: Joah Gerstenberg <[email protected]>
Co-authored-by: Hertz <c6741012416ef5c49a865db79682113385c5fb998e8c890234fb442b59e22748@buzz.block.builderlab.xyz>
Co-authored-by: Claude Opus 5.5 <[email protected]>

This branch was successfully deployed

1 active deployment
codex-review — 41f93dbd Deployed Nov 12, 2026 by wpfleger96 via Run Codex Security Review #14631
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

codex-security-review-current The posted Codex security review matches its recorded range.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants