Skip to content

feat(buzz): ask the host what a key is, and obey the answer (DIVE-3573) - #42

Merged
5dive-bot merged 3 commits into
mainfrom
dive-3573-buzz-bridge
Aug 19, 2026
Merged

feat(buzz): ask the host what a key is, and obey the answer (DIVE-3573)#42
5dive-bot merged 3 commits into
mainfrom
dive-3573-buzz-bridge

Conversation

@5dive-bot

Copy link
Copy Markdown
Collaborator

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 does
anything 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 one
    question: does the plugin deliver this event, and with what trust label. Pure for the
    same 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.
  • bridge.test.ts (new) — 10 arms: every failure direction, plus the positive
    controls that prove the negatives measure something. A reader that returned
    untrusted unconditionally would pass every negative arm on the page.
  • server.ts — the poller classifies each mentioning event via the host and routes
    it. Adds a trust attribute to channel meta. deliver() gains one parameter.

The load-bearing choices

server.ts:144's untrusted-input paragraph is UNCHANGED, byte for byte. The row
requires 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 a
teammate'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 is
also 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
trust reads 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 ps listing on the box, and inside a
sudo 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 sudo per mention would put one child process per mention outside
that guard and rebuild the pile-up it exists to make arithmetically impossible.

sudo -n /usr/local/bin/5dive agent buzz inbound --json … — the --json sits
after the verb on purpose: the grant is … agent buzz inbound * and sudo matches
positionally, so a global flag in front is a policy denial, not an answer. (Same shape
the scoped ask path 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 not
    installed 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 no
longer resolves; sure-redwood is held by the buzz box lane. The row's four-arm
acceptance is owed, and it also needs agent buzz enable/join re-run first — measured
2026-08-18, /var/lib/5dive/agents.json holds zero buzz identity blocks fleet-wide,
so every key resolves no-match and this bridge is correct, wired and inert until they
are backfilled.

lodar and others added 3 commits August 18, 2026 07:54
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>
@5dive-bot
5dive-bot merged commit f08a82f into main Aug 19, 2026
2 checks passed
@5dive-bot
5dive-bot deleted the dive-3573-buzz-bridge branch August 19, 2026 05:05
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