NIP-OA: owner mapping never materialized for direct relay members — kind:44200 turn metrics rejected with 403
- Dominant language
- Rust
- Stars
- 32.7k
- Forks
- 4.3k
- Avg merge
- 1d 13h
- Merged PRs (30d)
- 253
Description
## Summary
When an agent connects with a **valid NIP-OA auth tag** (`BUZZ_AUTH_TAG` via buzz-acp) but is **also a direct relay member**, the agent→owner mapping is never materialized — so the agent's `kind:44200` (agent-turn-metric, NIP-AM) events are rejected with HTTP 403, breaking the turn pipeline. Removing the tag restores it. Net effect: opting a direct-member agent into NIP-OA makes it worse off.
## Environment
Self-hosted relay (compose bundle, `ghcr.io/block/buzz:main`), `BUZZ_REQUIRE_RELAY_MEMBERSHIP=true`, `BUZZ_ALLOW_NIP_OA_AUTH=true`. Agent runs headless via `buzz-acp` with `BUZZ_AUTH_TAG` (empty conditions string; tag verified against the test vectors in `docs/nips/NIP-OA.md`, and buzz-acp logs `owner resolved from BUZZ_AUTH_TAG: `).
## What happens (read-only trace of `main`, 2026-08-30)
1. buzz-acp sends `x-auth-tag` on HTTP bridge requests (`crates/buzz-acp/src/relay.rs` ~L405) and embeds the tag in the WS AUTH event.
2. `check_relay_membership` (`crates/buzz-relay/src/api/mod.rs`) returns `MembershipDecision::Member` for the agent **before** inspecting the auth tag — the NIP-OA branch only runs when the caller is *not* a direct member.
3. `materialize_nip_oa_owner` is only invoked on the `ViaOwner` path (e.g. `crates/buzz-relay/src/handlers/auth.rs`), so for a direct-member agent `users.agent_owner_pubkey` is never set.
4. The agent's turn metric then fails the ownership check in `crates/buzz-relay/src/handlers/ingest.rs` (`if kind_u32 == KIND_AGENT_TURN_METRIC` → `is_agent_owner` → `AuthFailed: "restricted: agent-turn-metric \`p\` tag must be the registered owner of this agent"`), surfaced as 403 `accepted:false` via `api/bridge.rs`.
The divergence looks related to the "deliberate Phase-1 asymmetry" comment near the moderation-restriction check in `ingest.rs`.
## Expected
A cryptographically valid auth tag should materialize the agent→owner mapping regardless of how membership was satisfied — direct membership and ViaOwner delegation shouldn't diverge in owner registration.
## Suggested fix
In the auth paths, when a valid auth tag is present, call `materialize_nip_oa_owner` even when membership resolves as `Member`. Alternatively, let the `kind:44200` ownership check fall back to verifying the self-proving auth tag on the event/connection.
## Workaround (untested, for the record)
Temporarily remove the agent from the relay roster so its next connection is admitted `ViaOwner` (which materializes the first-write-wins mapping), then re-add it. Non-obvious and racy for operators.
Contributor guide
Assessment
This issue has not been assessed yet.