mirror of
https://github.com/vitorpamplona/amethyst.git
synced 2026-08-11 08:47:33 +00:00
A relay's `OK false` was dropped on the floor. `RelayInsertConfirmationCollector` forwarded only `OK true`, so `ChatDeliveryTracker` — which already backs the delivery ticks — could record acceptance but never refusal, and a refused message kept the pending clock indefinitely, indistinguishable from one still in flight. That is the whole of what a banned Buzz member experienced. The relay enforces a tenant ban (9040) or timeout (9042) at publish time and answers `OK false` with no event for the client to fold, so the refusal reaches the sender here or nowhere. This is why the read-side PostingGate could not cover it. The collector gains an optional onRelayRejected callback (both existing callers unchanged), ChatDelivery and RecipientDelivery gain per-relay rejections with the reason and its parsed NIP-01 prefix, and the bubble shows an error tick with the relay's own words in the delivery dialog. Three details the naive version gets wrong: - `duplicate:` is normalized to acceptance. It means the relay already holds the event, and relays disagree about which OK flag carries it, so treating it as refusal would report delivered messages as failed. - A later acceptance clears an earlier refusal from the same relay, because auth-required -> AUTH -> republish refuses first and then stores. - Refused requires every target relay to have refused with none accepting. A relay that hasn't answered is still in flight, and one refusal among several acceptances is a delivered message, so those stay out of the bubble and show in the detail rows instead. On a single-relay room — a Buzz workspace channel — the first refusal is the whole answer. Retry is offered only where a resend could plausibly work (rate-limited, error, auth-required); a ban or an unsupported kind keeps the neutral Broadcast label rather than promising a retry the relay will refuse identically. Generalizes past the ban case: every relay refusal on a chat message now reaches the sender, with rate limits, auth gating and unsupported kinds covered by the same path. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Q3KXHjmCpUDzrSRyxatbyB