# Buzz Communication Adapter

Veritas Kanban can map a Buzz community channel to Squad Chat and move signed
root messages and replies in both directions. The integration uses Buzz's
native Nostr HTTP and WebSocket contracts. It does not spawn `buzz`,
`buzz-acp`, or `buzz-agent` for delivery.

Buzz is a communication adapter, not an `AgentProvider`. It does not create a
Veritas task, start an ACP agent, synchronize DMs or forums, or read Buzz
Desktop's internal state. An operator may separately materialize selected
public Buzz persona and team definitions as disabled Veritas profiles and
roster members. Definition import is data-only and never starts a process.

Task execution is a separate seam. A disabled-by-default `buzz-agent`
configuration uses provider `acp-stdio` and the generic ACP client. It never
turns relay delivery into task completion and never launches `buzz-acp`.
Selected task tools are exposed only through the provider-neutral
`veritas-run` bridge, with an opaque task/attempt/catalog/manifest binding;
native server credentials and the global Veritas MCP inventory are not passed
to Buzz.
See [Buzz Agent ACP](AGENT-PROVIDERS.md#buzz-agent-acp).

## Supported contract

The adapter is fixture-pinned to:

- Buzz release `0.4.24`
- Buzz commit `710ed9fff57878a1d69f809b80a6ee0416c53fc4`
- Veritas probe revision `1`
- Relay software identity `https://github.com/block/buzz`
- Required NIPs `11`, `29`, and `42`
- Optional enforced relay membership advertised as NIP `43`

The initial event projection is:

| Buzz surface                          | Veritas behavior                                                           |
| ------------------------------------- | -------------------------------------------------------------------------- |
| Kind `9` root message                 | Creates one Squad Chat message in the mapped target.                       |
| Kind `9` reply                        | Creates one threaded Squad Chat reply using Buzz root/reply tags.          |
| Veritas root                          | Signs and publishes one kind `9` event with the mapped `h` channel tag.    |
| Veritas reply                         | Publishes a direct or nested reply with the exact Buzz root/reply markers. |
| Kind `40003` edit                     | Records bounded audit metadata. Existing Squad Chat text is unchanged.     |
| Kind `9005` or NIP-09 kind `5` delete | Records bounded deletion metadata. Local content is not removed.           |
| Unknown or malformed kind             | Ignores it with a redacted delivery audit entry.                           |
| Reactions, files, canvas, forums, DMs | Not projected.                                                             |

An unknown Buzz version is `unsupported`. Veritas may still read public NIP-11
metadata, but it will not connect the worker or send messages until the pinned
compatibility contract passes.

## Identity and least privilege

Use a dedicated Buzz/Nostr identity. Add that public identity only to the
community and channels that Veritas must bridge. Veritas does not need channel
creation, moderation, desktop storage, or broad community administration.

Keep the private key in the Veritas server environment and store only its
environment-variable reference:

```dotenv
BUZZ_PRIVATE_KEY=<set outside source control>
BUZZ_AUTH_TAG=<optional NIP-OA owner attestation>
```

The signing key may be 64-character private-key hex or `nsec`. It must match
the configured 64-character public-key hex. `BUZZ_AUTH_TAG` is needed only
when an agent identity receives membership through a NIP-OA owner.

Never put an `nsec`, private-key hex, auth tag, token, authorization header, or
raw signed event in a Settings field, API response, task, log, screenshot, or
support packet.

## Configure and map a channel

In **Settings -> Notifications -> Buzz Connection**, configure:

- Relay HTTP URL, such as `https://community.example.com`
- Optional matching WebSocket URL; Veritas derives it when omitted
- Expected community host and optional non-default port
- Buzz channel UUID to map to Squad Chat
- Public-key hex
- `env:BUZZ_PRIVATE_KEY`
- Optional `env:BUZZ_AUTH_TAG`
- Explicit localhost/private-network allowances when required
- Optional `buzz`, `buzz-acp`, or `buzz-agent` executable for version
  diagnostics

HTTP and WebSocket endpoints must have the same host, port, path, and TLS
posture. Credentials, query strings, and fragments are rejected. A configured
path and non-default port are preserved because Buzz binds the community to
the request authority.

The Settings save writes the reference-only adapter first, then writes the
Squad Chat channel mapping. Changing a channel disables the old mapping before
enabling the new one. Conflicting enabled mappings for the same target are
rejected.

## API setup

`settings:write` is required to configure, map, send, reconcile, disable, or
disconnect. `settings:read` can read adapters, mappings, health, and delivery
history.

Configure the connection:

```bash
curl -X PUT http://localhost:3001/api/integrations/communication/adapters/buzz-default \
  -H 'Content-Type: application/json' \
  -H 'X-API-Key: <veritas-api-key>' \
  --data '{
    "kind": "buzz",
    "displayName": "Buzz",
    "enabled": true,
    "relayHttpUrl": "https://community.example.com",
    "expectedCommunity": "community.example.com",
    "publicKey": "<64-hex-public-key>",
    "credentialRef": "env:BUZZ_PRIVATE_KEY",
    "authTagRef": "env:BUZZ_AUTH_TAG"
  }'
```

Map one Buzz channel to Squad Chat:

```bash
CHANNEL_ID=123e4567-e89b-42d3-a456-426614174000

curl -X PUT \
  "http://localhost:3001/api/integrations/communication/adapters/buzz-default/buzz/channels/${CHANNEL_ID}" \
  -H 'Content-Type: application/json' \
  -H 'X-API-Key: <veritas-api-key>' \
  --data '{
    "target": { "kind": "squad" },
    "enabled": true,
    "actor": "operator"
  }'
```

Run the compatibility probe:

```bash
curl \
  http://localhost:3001/api/integrations/communication/adapters/buzz-default/health \
  -H 'X-API-Key: <veritas-api-key>'

vk doctor --json
```

`POST .../buzz-default/test` runs the same read-only probe. It never sends a
message.

## Import persona and team definitions

In **Settings -> Agents -> Buzz Persona and Team Definitions**, an operator can
list, preview, and explicitly import public Buzz definition heads:

- kind `30175` persona definitions keyed by `(author, kind, d tag)`;
- kind `30176` team definitions keyed the same way; and
- the deterministic NIP-33 head with the greatest `created_at`, using the
  lowest event ID to break a tie.

The importer reuses the signed, DNS-pinned Buzz `/query` transport and requires
current healthy compatibility evidence. Every candidate has bounded Nostr
shape, tags, content, JSON depth, arrays, strings, and batch size. Its
signature is reconstructed and verified before it can appear in Settings.
Invalid envelopes contribute only to a rejected count. A signature-valid
current head with rejected content appears as a non-importable coordinate and
field-level validation reason, so Veritas never silently falls back to an older
definition. Unsafe source values are not echoed into the UI or logs.

Preview classifies each field before mutation:

| Buzz definition field                                                                     | Import behavior                                                                    |
| ----------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------- |
| Persona `display_name`                                                                    | Source-owned profile display name.                                                 |
| Persona `system_prompt`                                                                   | Source-owned profile prompt when present.                                          |
| Persona `avatar_url`                                                                      | Validated public metadata only. Veritas does not fetch it.                         |
| Persona `runtime`, `model`, `provider`                                                    | Source preferences only, never runtime evidence or active provider configuration.  |
| Persona `name_pool`                                                                       | Bounded source metadata only.                                                      |
| Persona reserved response fields                                                          | Source-only. Veritas does not apply them.                                          |
| Team `name`, `description`                                                                | Source-owned roster fields for create or refresh.                                  |
| Team `persona_ids`                                                                        | Same-author persona slugs resolved to linked profiles and disabled roster members. |
| Unknown fields                                                                            | Ignored with a field-level forward-compatibility explanation.                      |
| Secrets, environment, commands, paths, managed process state, MCP, hooks, skills, engrams | Rejected.                                                                          |

The available actions are:

- `create`: use the deterministic `buzz-<slug>` profile ID or
  `buzz-team-<slug>` roster ID;
- `link`: attach provenance to an explicitly selected existing profile or
  roster without replacing local fields;
- `refresh`: replace only the saved source-owned fields after a new preview;
  and
- `skip`: record no local mutation.

Create, link, and refresh require collision-free preview. A preview returns an
optimistic local revision and exact source event ID. Import rejects a changed
local target or replaced source, so the operator must review the current diff
instead of overwriting concurrent edits. Native profile/roster fields remain
authoritative; refresh preserves local-only fields and existing routing rules.

New persona profiles, new rosters, and imported roster members are disabled.
Import never launches, enables, routes, installs, fetches, or writes back to
Buzz. Removing or replacing a Buzz definition changes its linked-source status
to `missing` or `changed`; it does not delete the materialized local object.

Definition API:

```text
GET  /api/integrations/communication/adapters/:adapterId/buzz/definitions
GET  /api/integrations/communication/adapters/:adapterId/buzz/definitions/links
POST /api/integrations/communication/adapters/:adapterId/buzz/definitions/preview
POST /api/integrations/communication/adapters/:adapterId/buzz/definitions/import
```

Reads and preview require `settings:read`. Import requires `settings:write`.
There is no continuous synchronization and no Buzz write-back endpoint.

## Trigger a Veritas workflow

A `buzz-workflow-trigger/v1` rule can bind one active channel mapping to one
Veritas workflow. The first supported event is a root kind `9`
`message.posted`. Replies, edits, deletes, reactions, adapter-originated
echoes, disabled rules, and predicate mismatches do not launch a run.

Create a rule with the mapping ID returned by the channel-mapping API:

```bash
curl -X POST \
  http://localhost:3001/api/integrations/communication/adapters/buzz-default/buzz/workflow-triggers \
  -H 'Content-Type: application/json' \
  -H 'X-API-Key: <veritas-api-key>' \
  --data '{
    "mappingId": "buzz_map_example",
    "workflowId": "triage-external-request",
    "contentIncludes": "help"
  }'
```

An exact 64-character author public key may be supplied as `authorPubkey`.
Content matching is a bounded, case-insensitive substring check. There is no
regex, expression, code, shell, or arbitrary Nostr-kind filter.

Rule creation requires `settings:write` and execute permission on the
destination workflow. The mapping alone does not grant workflow or task
mutation rights.

Veritas persists the accepted causal key
`buzz:{community}:{eventId}:{ruleId}` before dispatch through the
provider-neutral `workflow.pre-external-trigger` hook. Workflow context
retains the community, channel, event, author, mapping, rule, and sanitized
message. A replay returns the existing run. After restart, Veritas searches
the destination workflow's run context for the causal key before launching
another run.

List or disable rules and inspect bounded disposition history:

```text
GET  /api/integrations/communication/adapters/:adapterId/buzz/workflow-triggers
POST /api/integrations/communication/adapters/:adapterId/buzz/workflow-triggers/:ruleId/disable
GET  /api/integrations/communication/adapters/:adapterId/buzz/workflow-trigger-audits
```

Disabling a rule retains its prior audits and linked workflow runs.

## Send roots and replies

Send a root and associate it with a local Squad Chat message:

```bash
curl -X POST \
  http://localhost:3001/api/integrations/communication/adapters/buzz-default/send \
  -H 'Content-Type: application/json' \
  -H 'X-API-Key: <veritas-api-key>' \
  --data '{
    "target": {
      "kind": "squad",
      "squadMessageId": "msg_local_root"
    },
    "message": "Root message from Veritas",
    "actor": "VERITAS"
  }'
```

Send a reply by identifying both the new local message and the local parent:

```bash
curl -X POST \
  http://localhost:3001/api/integrations/communication/adapters/buzz-default/send \
  -H 'Content-Type: application/json' \
  -H 'X-API-Key: <veritas-api-key>' \
  --data '{
    "target": {
      "kind": "squad",
      "squadMessageId": "msg_local_reply"
    },
    "replyToSquadMessageId": "msg_local_root",
    "message": "Reply from Veritas",
    "actor": "VERITAS"
  }'
```

The parent must already have a durable Buzz event mapping. A missing parent is
blocked instead of publishing a detached root.

Each outbound event includes the mapped `h` tag, a `client=veritas-kanban`
marker, and a stable `veritas-id` delivery marker. Veritas persists the signed
event and event ID before submitting it to `/events`.

## Inbound subscription and replay

One supervised WebSocket worker runs per enabled, compatible Buzz connection.
It:

1. resolves and pins the configured relay address through the outbound network
   policy;
2. answers the NIP-42 challenge with kind `22242` and the optional NIP-OA auth
   tag;
3. subscribes only to enabled mapped channel UUIDs and kinds `9`, `40003`,
   `9005`, and `5`;
4. resumes from the persisted cursor with a five-second overlap;
5. verifies each Nostr event signature, channel, kind, timestamp, and size;
6. projects and audits the Squad Chat message; and
7. commits the total-order cursor `(created_at, event_id)` only after the
   projection and audit are durable.

Deduplication uses `(community, event_id)`, never timestamp alone. Squad Chat
uses the deterministic local ID `msg_buzz_<event-id>`, so a crash after the
chat write but before adapter-state persistence replays safely. The original
Buzz author public key, source timestamp, event kind, channel, community,
event ID, and `buzz://message` link remain attached as external metadata.

An out-of-order reply is persisted but does not advance the cursor past its
missing root. When the root arrives, queued replies are replayed in
`(created_at, event_id)` order. A root that never arrives remains bounded
pending state rather than becoming a detached Squad Chat message.

Adapter-originated event IDs are retained and ignored when echoed by the
subscription, preventing a reply loop.

## Ambiguous delivery recovery

Network failure after a write can leave delivery status unknown. Veritas does
not blindly retry:

1. the delivery remains visible as `delivery_unknown`;
2. `POST .../buzz-default/poll` queries `/query` by the signed event ID;
3. if the event exists, the original delivery becomes `success`;
4. if the relay definitively reports absence, Veritas resubmits the exact
   persisted signed event; and
5. if the query is inconclusive, the delivery remains unresolved.

Before query or resubmission, the persisted event is re-verified against its
signature, configured public identity, mapped community/channel, and event ID.
A corrupted record is failed and never retried.

## Health and audit

Health separates:

- compatibility and authorization checks;
- relay transport connection;
- active subscription state;
- mapped-channel count;
- reconnect attempts and last connection;
- last inbound event;
- cursor lag;
- last send time and status; and
- the latest redacted worker error.

Compatibility can be healthy while runtime status is `degraded`, such as when
no channel is mapped or the subscription is still connecting. `canSend`
requires an enabled adapter, current healthy compatibility evidence, and at
least one mapped channel. `canReceiveReplies` additionally requires an active
subscription.

Delivery history exposes `queued`, `success`, `delivery_unknown`, `replayed`,
`ignored`, `failed`, `blocked`, and `skipped`. It retains bounded coordinates
and redacted details, not credentials or raw authorization material.

## Network policy

Public HTTPS/WSS is the default. Plain HTTP/WS requires an explicit localhost
or private-network allowance. Localhost and RFC1918/IPv6 ULA ranges are denied
unless their matching setting is enabled. Link-local, cloud metadata, and
CGNAT ranges remain blocked. DNS is resolved and pinned, redirects are
disabled, payloads are bounded, and requests have fixed timeouts.

Enable only the narrow network class required by the relay.

## Credential-free release gate

Run the composed Buzz gate from the repository root:

```bash
pnpm test:buzz:compatibility
```

This command runs the existing credential-free fixtures for:

- pinned relay/community/identity compatibility and secret non-retention;
- signed root/reply communication, reconnect replay, dedupe, loop prevention,
  and ambiguous delivery recovery;
- `buzz-agent` through generic ACP;
- the opaque run-scoped Veritas MCP bridge;
- persona/team preview, import, refresh, provenance, and unsafe-source
  rejection;
- the typed root-message workflow trigger and causal replay; and
- the machine-readable compatibility record.

The canonical support record is still
`GET /api/config/harness-compatibility`. Its Buzz entry pins release `0.4.24`,
commit `710ed9fff57878a1d69f809b80a6ee0416c53fc4`, `buzz-agent 0.1.0`,
provider probe revision, fixture revision, and every seam fixture path. A
provider build, protocol, capability, probe, configuration, or fixture change
invalidates prior certification.

The green aggregate gate means only the following:

| Capability                                                              | Disposition                                    |
| ----------------------------------------------------------------------- | ---------------------------------------------- |
| Relay/community/identity diagnostics                                    | Supported at the pinned baseline               |
| Mapped roots/replies, replay, dedupe, and loop prevention               | Supported                                      |
| `buzz-agent` through generic ACP                                        | Supported at the pinned ACP contract           |
| Run-scoped Veritas MCP                                                  | Supported through the provider-neutral bridge  |
| Public persona/team import                                              | Supported as explicit one-way materialization  |
| One typed root message to a Veritas workflow                            | Supported                                      |
| `buzz-acp` as a Veritas provider                                        | Rejected; it is the inverse Buzz-owned harness |
| Buzz workflow definition execution and cross-system approvals           | Deferred                                       |
| NIP-AE memory sync and automatic NIP-34 task mirroring                  | Deferred or rejected                           |
| Desktop internals, DMs, forums, canvas, moderation, huddles, and mobile | Not implied by this gate                       |

An unknown or changed Buzz build remains unsupported or degraded until the
baseline, evidence digest, fixtures, and documentation are explicitly
updated.

### Optional live smoke

Live smoke is supplemental and must target the exact candidate build with a
dedicated least-privilege identity. It is not normal CI:

1. Run `vk doctor --json` and retain only redacted public build/status fields.
2. Publish one Veritas root to a dedicated mapped test channel.
3. Reply from Buzz and verify the reply appears in the correct Squad Chat
   thread, not merely as an HTTP success or ACP `end_turn`.
4. Trigger one allowlisted test workflow and verify its causal event/run link.
5. Disable the test mapping, rule, and profile.

Do not upload raw events, private messages, authorization headers, auth tags,
private keys, provider keys, or unredacted logs as evidence.

## Disable, upgrade, and rollback

Disabling a channel mapping closes and rebuilds the worker without deleting
the mapping, cursor, event coordinates, or delivery audit. Disconnecting the
adapter closes the worker and disables delivery while retaining reference-only
configuration and recovery state.

Remove environment secrets separately only when retiring the identity.
Veritas never removes relay membership, changes a Buzz community, or modifies
Buzz Desktop state.

After a Buzz upgrade, run `vk doctor --json`. A version/build change
invalidates prior compatibility evidence and must pass the pinned contract
and `pnpm test:buzz:compatibility` before workers, sends, or ACP dispatch
resume. A baseline change updates the release/commit constants, matrix
evidence digest, fixtures, and this guide together.

If a candidate fails, keep the prior baseline and report the failing facet.
Disable the affected adapter, mapping, profile, or trigger rule without
deleting redacted configuration, mappings, cursors, import provenance, trigger
audits, or workflow-run evidence.
