maxplayer-core publishes kind-3407 REJECT and the seller subscribes to it, but the vendored relay never admits that kind. The buyer's delivery-refusal path therefore cannot reach relay.maxplayer.ai.
The gap, with its denominator
crates/maxplayer-core/src/kinds.rs declares 9 kinds. buzz-relay/src/handlers/ingest.rs defines a const for 8 of them. Exactly one is absent, and it is the only one:
JOB_REJECT_KIND = 3407 — no const anywhere in ingest.rs.
required_scope_for_kind's trade arm covers 3401–3406 plus the 30340 heartbeat. Kind 3407 falls through to the _ => catch-all, which returns Ok(Scope::MessagesWrite) only when open_ingest_enabled(). On a closed-ingest deployment it is refused.
We publish it, subscribe to it, and act on it
crates/maxplayer-core/src/gateway.rs:910 builds the rejection draft; the test at :1165 asserts "rejection uses kind 3407".
crates/maxplayer-core/src/seller_node/run.rs:1286 subscribes Kind::Custom(JOB_REJECT_KIND).
run.rs:3940 dispatches it: k if k.as_u16() == JOB_REJECT_KIND => self.on_reject(&event).await.
run.rs:8217 asserts the live seller-node subscription must include that kind.
So the seller is listening for an event the relay will not accept, and has a test pinning that it listens.
Measured on the live relay
Five kinds requested on one anonymous connection against wss://relay.maxplayer.ai (read-only, nothing signed, nothing published):
3401 OFFER — 500
3403 RESULT — 500
3405 AWARD — 500
3406 ACCEPT — 500
3407 REJECT — 0
The four siblings are at the query's own limit: 500, so they are "≥500" and serve as positive controls on the same connection. REJECT is the only zero.
Bound, stated rather than glossed: n=0 is consistent with the relay refusing the kind and with nothing ever having attempted to publish one; reads alone cannot separate those. The relay-would-refuse half is read off the code above, not demonstrated by a push. Settling it empirically needs a signed 3407 publish against production, which is why it is not in this issue.
Why it matters
REJECT is the buyer's verified delivery refusal. With it unpublishable there is no on-wire way to refuse a delivery, and on_reject can never fire. Refusal otherwise resolves toward quarantine-or-paid, so this sits next to money rather than in cosmetics.
#926 is blocked by this and does not say so. It asks to "reject a delivery with no checks attestation before paying" — exactly this flow. Whoever picks it up will build the buyer half and find the event silently never lands. #682 (closed) reads like the lineage: "no buyer-authored terminal event for an award that never delivers" is the problem 3407 exists to solve, though its body never names the kind. #117 is the adjacent reject taxonomy, not this.
Shape of the fix
Add the const and extend the existing trade arm — the same one-line-per-kind pattern 3406 already follows (#329). Two things a fix must carry:
- A positive control. A test asserting 3407 maps to
MessagesWrite should be able to go red; assert the mapping, not merely that the const exists.
- A redeploy. A relay change is inert until
nixos-rebuild switch --flake .#relay runs against the box — and note that no CI job compiles crates/buzz (it is not a root-workspace member and ci.yml never names buzz), so a green PR here proves nothing about this crate. The first compile happens at deploy time.
Measured 2026-09-02 against main and the live relay. The kind enumeration was taken from both files programmatically rather than by eye.
maxplayer-corepublishes kind-3407 REJECT and the seller subscribes to it, but the vendored relay never admits that kind. The buyer's delivery-refusal path therefore cannot reachrelay.maxplayer.ai.The gap, with its denominator
crates/maxplayer-core/src/kinds.rsdeclares 9 kinds.buzz-relay/src/handlers/ingest.rsdefines a const for 8 of them. Exactly one is absent, and it is the only one:JOB_REJECT_KIND = 3407— no const anywhere iningest.rs.required_scope_for_kind's trade arm covers3401–3406plus the30340heartbeat. Kind 3407 falls through to the_ =>catch-all, which returnsOk(Scope::MessagesWrite)only whenopen_ingest_enabled(). On a closed-ingest deployment it is refused.We publish it, subscribe to it, and act on it
crates/maxplayer-core/src/gateway.rs:910builds the rejection draft; the test at:1165asserts "rejection uses kind 3407".crates/maxplayer-core/src/seller_node/run.rs:1286subscribesKind::Custom(JOB_REJECT_KIND).run.rs:3940dispatches it:k if k.as_u16() == JOB_REJECT_KIND => self.on_reject(&event).await.run.rs:8217asserts the live seller-node subscription must include that kind.So the seller is listening for an event the relay will not accept, and has a test pinning that it listens.
Measured on the live relay
Five kinds requested on one anonymous connection against
wss://relay.maxplayer.ai(read-only, nothing signed, nothing published):3401OFFER — 5003403RESULT — 5003405AWARD — 5003406ACCEPT — 5003407REJECT — 0The four siblings are at the query's own
limit: 500, so they are "≥500" and serve as positive controls on the same connection. REJECT is the only zero.Bound, stated rather than glossed:
n=0is consistent with the relay refusing the kind and with nothing ever having attempted to publish one; reads alone cannot separate those. The relay-would-refuse half is read off the code above, not demonstrated by a push. Settling it empirically needs a signed 3407 publish against production, which is why it is not in this issue.Why it matters
REJECT is the buyer's verified delivery refusal. With it unpublishable there is no on-wire way to refuse a delivery, and
on_rejectcan never fire. Refusal otherwise resolves toward quarantine-or-paid, so this sits next to money rather than in cosmetics.#926 is blocked by this and does not say so. It asks to "reject a delivery with no checks attestation before paying" — exactly this flow. Whoever picks it up will build the buyer half and find the event silently never lands. #682 (closed) reads like the lineage: "no buyer-authored terminal event for an award that never delivers" is the problem 3407 exists to solve, though its body never names the kind. #117 is the adjacent reject taxonomy, not this.
Shape of the fix
Add the const and extend the existing trade arm — the same one-line-per-kind pattern
3406already follows (#329). Two things a fix must carry:MessagesWriteshould be able to go red; assert the mapping, not merely that the const exists.nixos-rebuild switch --flake .#relayruns against the box — and note that no CI job compilescrates/buzz(it is not a root-workspace member andci.ymlnever names buzz), so a green PR here proves nothing about this crate. The first compile happens at deploy time.Measured 2026-09-02 against
mainand the live relay. The kind enumeration was taken from both files programmatically rather than by eye.