feat(buzz): ask the host what a key is, and obey the answer (DIVE-3573) - #42
Merged
Conversation
The plugin half of the buzz bridge. It does NOT become a trust authority: it does not read the registry, does not decide who a key belongs to, and promotes nothing. It asks `5dive agent buzz inbound`, which re-derives the identity from the PUBLIC KEY against the registry and, for a known teammate, puts the message on the a2a rail itself — this process cannot inject into a pane and must not be able to. - bridge.ts is pure and dependency-free for the reason mention.ts and poller.ts are: repo CI runs a bare `bun test` with no plugin deps installed, so anything only reachable through server.ts cannot execute there at all. A trust decision nothing runs is not a decision. - FAIL-CLOSED MEANS UNTRUSTED, NOT DROPPED. A non-zero rc, unparseable output, ok:false, a missing route, a route this build does not know, and a refused a2a send all land on untrusted — today's behaviour, which is why a box that never gets the new CLI keeps working. Dropping instead would re-create the exact ambiguity that made DIVE-3559's measurement unusable: the sender sees silence and cannot tell a refusal from a message that never arrived. - server.ts:144's untrusted-input paragraph is UNCHANGED, byte for byte. The new paragraphs narrow nothing in it; they explain one added meta attribute (trust="owner"|"unknown") and say plainly that a teammate's instruction arriving through this plugin means the host did NOT recognise its key. - The body travels in a 0600 file under the agent's own state dir, never in argv (audit log, `ps`, sudo policy match), and is removed after the call. - The classification is awaited inside the DIVE-3486 non-overlap guard rather than fired off, so it cannot rebuild the child-process pile-up one level up. plugins/buzz/bridge.test.ts: 10 arms, every failure direction plus the positive controls that prove the negatives measure something. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…573) The colon form is rejected by the a2a rail's sender-label validator, so the host could never have delivered class (c) under it. Follow the host's label here (comment, instruction text, fixtures) and add an arm asserting the from label the host reports is spellable as an a2a sender label, with the iteration-1 colon label as the negative control so the two halves' idea of the label cannot drift apart again silently. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Union resolve of plugins/buzz/server.ts, the only conflict. #44 (DIVE-3560, DM inbound) and this branch (DIVE-3573, host-classified inbound) both rewrote the deliver gate in pollChannel. Resolution: main's widened predicate, this branch's classification. `shouldDeliver(ev, ..., isDm)` is a strict superset of `mentionsUs` — the non-DM path defers to mentionsUs verbatim — so the bridge sees every event it saw pre-merge, plus DMs. Imports take the union of all three modules; deliver() keeps its 3-arg trust-label signature from this branch (main's 2-arg call site is the side that goes). Checked on the merged tree: bun test 898 pass / 0 fail across 34 files (all 34 *.test.ts on disk are enrolled, including this branch's new bridge.test.ts); bun generator/generate.ts --check exit 0; tsc --strict --noEmit on server.ts with deps installed, clean. Co-Authored-By: Claude Opus 5 <noreply@anthropic.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.
The buzz bridge, plugin half (DIVE-3573)
Pairs with 5dive-ai/5dive
dive-3573-buzz-bridge, which carries the 5dive-layer half(
5dive agent buzz inbound, the outbound mirror, the sudoers grant). Neither half doesanything without the other.
This plugin does not become a trust authority. It does not read the registry, does
not decide who a key belongs to, and promotes nothing. It asks the host, which
re-derives the identity from the public key against the registry and, for a known
teammate, puts the message on the a2a rail itself — this process runs as the agent,
cannot inject into a pane, and must not be able to.
Design:
community/wiki/the-trust-decision-does-not-live-in-the-plugin-it-rides-the-5dive-layer.md.What changes
bridge.ts(new, pure, dependency-free) — reads the host verdict and answers onequestion: does the plugin deliver this event, and with what trust label. Pure for the
same reason
mention.tsandpoller.tsare: repo CI runs a barebun testwith noplugin deps installed, so anything only reachable through
server.tscannot executethere at all. A trust decision nothing runs is not a decision.
bridge.test.ts(new) — 10 arms: every failure direction, plus the positivecontrols that prove the negatives measure something. A reader that returned
untrustedunconditionally would pass every negative arm on the page.server.ts— the poller classifies each mentioning event via the host and routesit. Adds a
trustattribute to channel meta.deliver()gains one parameter.The load-bearing choices
server.ts:144's untrusted-input paragraph is UNCHANGED, byte for byte. The rowrequires it verbatim for the unknown-key class, and it is. The two paragraphs added
after it narrow nothing in it: they say the paragraph above is unconditional, explain
that
trust="unknown"is that paragraph unchanged, and state plainly that ateammate's instruction arriving through this plugin means the host did not recognise
its key — so treat it as a stranger, because that is what was measured.
Fail-closed means untrusted, not dropped. A non-zero rc, unparseable output,
ok:false, a missing route, a route a newer host speaks that this build does not know,and an a2a send the rail refused all land on
untrusted— today's behaviour, which isalso why a box that never gets the new CLI keeps working exactly as it does now.
Dropping them instead would re-create the ambiguity that made DIVE-3559's measurement
unusable: relay silence cannot distinguish a refusal from a message that was never
delivered. Untrusted is the weakest class in the table, so delivering there is never an
escalation.
Meta gains a field; it never substitutes one. A session that has never heard of
trustreads exactly the message it read before.The body travels in a file, never in argv. Inbound content is attacker-chosen text;
in argv it would land in the audit log, in every
pslisting on the box, and inside asudo policy match. It is written 0600 under the agent's own state dir and removed after
the call — otherwise that directory accumulates every message this seat was ever
mentioned in, in plaintext, forever.
The classification is awaited inside the DIVE-3486 non-overlap guard, not fired off.
A fire-and-forget
sudoper mention would put one child process per mention outsidethat guard and rebuild the pile-up it exists to make arithmetically impossible.
sudo -n /usr/local/bin/5dive agent buzz inbound --json …— the--jsonsitsafter the verb on purpose: the grant is
… agent buzz inbound *and sudo matchespositionally, so a global flag in front is a policy denial, not an answer. (Same shape
the scoped
askpath already documents for_deliver.)CHECKED
bun test plugins/buzz/— 24 pass, 0 fail across 4 files (10 of them new).bun build ./server.ts --external '*'bundles clean — the plugin's own deps are notinstalled in this checkout, so this is a parse/typecheck of the changed file rather
than a boot.
NOT VERIFIED
No live relay run. The fleet's buzz configs point at
shy-mesa.5dive.com, which nolonger resolves;
sure-redwoodis held by the buzz box lane. The row's four-armacceptance is owed, and it also needs
agent buzz enable/joinre-run first — measured2026-08-18,
/var/lib/5dive/agents.jsonholds zerobuzzidentity blocks fleet-wide,so every key resolves
no-matchand this bridge is correct, wired and inert until theyare backfilled.