Skip to content

feat(acp): make a silently dropped event visible in the log - #5988

Open
Chessing234 wants to merge 4 commits into
block:mainfrom
Chessing234:feat/acp-report-gate-drops
Open

feat(acp): make a silently dropped event visible in the log#5988
Chessing234 wants to merge 4 commits into
block:mainfrom
Chessing234:feat/acp-report-gate-drops

Conversation

@Chessing234

Copy link
Copy Markdown
Contributor

Prompted by #5965, where an agent silently ignored every mention from one identity and diagnosing it meant reading buzz-acp's source. This doesn't change what the gate decides — only whether you can see it decide.

Two adjacent drops in the inbound loop were logged at debug!, which is off under the default buzz_acp=info filter. Between them they cover the two most confusing failures an agent can have, and in both the agent is online, the mention is correct, and nothing happens:

The author gate. Under --respond-to=owner-only every non-owner is refused; under allowlist, everyone not on the list. From the operator's side there was no record at all. The first drop for each author in each channel now logs at info! and names the mode that refused it.

A subscription miss. An event arrives, matches no rule, and vanishes. That is exactly what a forum mention looked like before the default kinds were widened — the agent joined, showed online, and never answered, with nothing saying the kind was never subscribed. First miss per (channel, kind) now logs at info! and names the kind.

Both keep every repeat at debug!, so a chatty channel can't turn one diagnostic into a flood. AuthorGateReporter holds the "have we said this already" state so that decision is a pure function with tests rather than an inline flag. The key is per channel, not global — the gate's answer can differ per channel, since a DM resolves fail-closed.

On #5965 specifically: I don't think it's a CLI-vs-Desktop dispatch bug. author_allowed branches only on the mode, and the reported symptom (owner dispatches every time, allowlisted key never) is exactly owner-only, which matches #5858 — the harness reads BUZZ_ACP_RESPOND_TO from the instance record, and that issue reports a "Respond to" edit updating the definition while the record and the running process stay at owner-only. I've left a note there rather than guess at a fix; either way, this PR is what would have made it visible in the log instead of requiring that analysis.

Verified locally on the pinned 1.95.0 toolchain:

  • cargo test -p buzz-acp --lib781 passed, 0 failed (778 before, plus the 3 new)
  • cargo clippy --workspace --all-targets -- -D warnings — clean
  • cargo fmt --all -- --check — clean

Note: I'm an outside contributor, so the workflow runs here sit at action_required until a maintainer approves them; only the DCO check reports on its own.

The inbound author gate is the quietest way an agent can ignore you: the
event arrives, the agent is online, the mention is correct, and nothing
happens. The drop was logged at `debug!`, which is off under the default
`buzz_acp=info` filter — so from the operator's side there was no record at
all, and working out why meant reading the source (block#5965; the same silence
shows up in block#1743 and block#3015).

Report the first drop for each author in each channel at `info!`, naming the
mode that refused it, and keep every repeat at `debug!` so a chatty channel
cannot turn one diagnostic into a flood. The key is per channel, not global:
the gate's answer can differ per channel, since a DM resolves fail-closed.

`AuthorGateReporter` holds that state so the "have we said this already"
decision is a pure function with tests, rather than an inline flag.

Signed-off-by: Taksh <takshkothari09@gmail.com>
The drop immediately below the author gate is the other half of the same
silence: an event reaches the agent, matches no subscription rule, and
vanishes at `debug!`. That is exactly what a forum mention looked like before
the default kinds were widened — the agent joined, showed online, and never
answered, with nothing in the log to say the kind was never subscribed.

Report the first miss per (channel, kind) at `info!` and keep repeats at
`debug!`, reusing the same reporter. Keyed by kind rather than author,
because that is the thing an operator would act on: it names which kind the
subscription does not cover.

Signed-off-by: Taksh <takshkothari09@gmail.com>
@Chessing234
Chessing234 requested a review from a team as a code owner August 15, 2026 20:31

@themiguelamador themiguelamador left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Three findings:

  • AuthorGateReporter retains every (channel, pubkey) for the process lifetime. Pubkeys are attacker-controlled, so key rotation makes the new diagnostic both an unbounded memory sink and an unbounded stream of info logs.
  • match_event returning None does not mean the kind is absent from the subscription: channel scope, required mention, filter false, and fail-closed filter errors all produce the same result. The new message therefore gives operators a potentially false diagnosis.
  • The existing author_allowed documentation was accidentally attached to the new reporter, leaving the gate itself undocumented and mixing two contracts.

I fixed all three in signed commit 1e188b27b and published the proposed patch here:
https://github.com/Complear/buzz/tree/review/pr-5988-fix

The patch caps each diagnostic reporter at 256 retained subjects, uses the native u16 kind rather than allocating strings, makes the subscription guidance accurate, restores the displaced documentation, and adds bound coverage.

Verification: cargo fmt --all -- --check; focused reporter tests 4/4; full cargo test -p buzz-acp --lib 782/782; cargo clippy -p buzz-acp --all-targets -- -D warnings; clean diff checks.

Review found three problems with the diagnostic these commits added.

**The reporter was unbounded.** Its subject is a pubkey, chosen by whoever is
sending, so key rotation alone turned the diagnostic into a memory sink and an
unbounded `info!` stream — an attacker-controlled one. The earlier reasoning
only covered a single author repeating, not many authors arriving. Cap the
remembered subjects at 256; past that nothing more is remembered or reported,
with one `warn!` saying so, and drops still land at `debug!`.

**The subscription message was a false diagnosis.** `match_event` returns
`None` for a channel-scope mismatch, a missing required mention, a filter
returning false, and — fail-closed — a disabled rule, a filter timeout or a
filter error. Claiming "this kind is not covered by the agent's subscription"
would send an operator after the wrong thing, most damagingly in exactly the
fail-closed cases. Name the possibilities instead of picking one.

**`author_allowed` lost its documentation.** The reporter was inserted between
that function and its doc comment, so the DM-hardening contract ended up
describing the reporter and the gate itself was left undocumented. Restored,
with the reporter given its own.

`DropReporter` is now generic over its subject, so the subscription miss keys
on the native `u16` kind rather than rendering it to a `String`.

Signed-off-by: Taksh <takshkothari09@gmail.com>
@Chessing234

Copy link
Copy Markdown
Contributor Author

all three are real and all three are fixed in 817355dc. i couldn't cherry-pick 1e188b27bhttps://github.com/Complear/buzz 404s for me, both git ls-remote and the api, so it's private or gone. implemented independently; happy to compare against yours if you can make it reachable.

unbounded reporter. yours. the subject is a pubkey and pubkeys are chosen by whoever is sending, so key rotation alone drives both the memory growth and the info! volume. my "a chatty channel can't flood it" reasoning only covered one author repeating, not many authors arriving — which is the case that matters, and the attacker-controlled one. capped at 256 remembered subjects; past that nothing is remembered or reported, one warn! says so, and drops still land at debug!. a test walks past the cap and asserts the set stops growing.

false diagnosis. confirmed by reading match_eventNone comes from a channel-scope mismatch, a missing required mention, Ok(false), and fail-closed from a disabled rule (filter.rs:414), a filter timeout (:435) and a filter error (:445). pointing at the kind would send an operator after the wrong thing, worst in exactly those fail-closed cases. the message now names the possibilities rather than picking one.

displaced doc. confirmed — i inserted the struct between author_allowed and its doc comment, so the dm-hardening contract ended up describing the reporter. restored to the function, reporter given its own.

also took your u16 point: DropReporter is generic over its subject now, so the subscription miss keys on the native kind instead of to_string().

verification: cargo test -p buzz-acp --lib 783 passed / 0 failed (5 reporter tests, up from 3), cargo clippy -p buzz-acp --all-targets -- -D warnings clean, cargo fmt --all -- --check clean.

@themiguelamador themiguelamador left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Re-reviewed current head 817355dc9. The update correctly resolves all three prior substantive findings: reporter state and first-drop info logging are bounded, event-kind subjects remain native u16, the subscription-miss message no longer over-claims a cause, and the displaced gate documentation is restored.

One minor stale reference remains: the initialization comment at crates/buzz-acp/src/lib.rs:2081 still says to see the removed AuthorGateReporter type. It should name the new DropReporter.

Signed one-line fix: fb0c2782a
https://github.com/Complear/buzz/tree/review/pr-5988-fix-v2

Verified with cargo fmt --all -- --check, all 783 buzz-acp library tests, strict clippy across all targets, the pre-commit Rust formatter, and clean diff checks.

The initialization comment still pointed at `AuthorGateReporter`, the type
the previous commit replaced with the generic `DropReporter`. A comment
naming a type that no longer exists sends the next reader looking for it.

Signed-off-by: Taksh <takshkothari09@gmail.com>
@Chessing234

Copy link
Copy Markdown
Contributor Author

fixed in 1c5fd617 — the comment at lib.rs:2081 now names DropReporter. grep -rn AuthorGateReporter crates/ comes back empty, so that was the last reference to the removed type.

verification: cargo fmt --all -- --check clean, cargo clippy -p buzz-acp --all-targets -- -D warnings clean, cargo test -p buzz-acp --lib 783 passed / 0 failed.

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.

2 participants