P11 slice 182 — SANICK S2S propagation#
Problem#
SANICK <nick> <newnick> (slice 151/152, the SA* oper-override family) is local-target only:
a remote/mirrored target gets a server NOTICE and nothing happens. The handler doc cites "the
KILL-local scope" as the rationale — but slice 179 (KILL S2S propagation) closed exactly
that gap, so the rationale is now stale. This slice is the natural continuation of the active S2S
notify-propagation arc (177 METADATA, 178 WALLOPS, 179 KILL, 180 metrics): make an oper's forced
rename of a remote user take effect network-wide.
Scope is SANICK only. SAJOIN/SAPART cross-link forcing (forced membership) is a separate
mechanism (NJOIN/PART propagation from the home server) and is the named follow-on.
Design — route to the home server, reuse the local rename machinery#
Extension verbs must ride ENCAP (the slice-126 lesson: a bare unknown verb SQUITs a stricter
peer). Server-authoritative, so server-prefixed like SU/METADATA:
:<our-sid> ENCAP * SANICK <target-uid> <newnick>
Broadcast to every peer (ENCAP *, apply-where-relevant). Only the target's home server acts:
its apply_encap_sanick runs the same three calls the local sanick() success path runs —
try_rename → command::nick::apply_local_nick_change → force_nick_uid. Because
apply_local_nick_change already relays :<oldmask> NICK <new> to all peers + co-members + files
WHOWAS + fires MONITOR, the actual rename then propagates network-wide as an ordinary NICK change
(every other server updates its mirror from that, not from the ENCAP). On a transit / the
oper's own server the inbound apply is a no-op (uid not local); the caller onward-relays the ENCAP
verbatim split-horizon so it reaches the home server across hops.
This keeps SANICK transparent (slice-152 principle): network-wide it is indistinguishable from the user renaming itself.
Changes#
leveva/src/s2s/sanick.rs(new):pub fn propagate(ctx, uid, new)— broadcast:<sid> ENCAP * SANICK <uid> <new>to peers (ctx.peers.broadcast(None, …)); no-op when there are no peers. The fuzz seam (outbound).pub(crate) fn apply_encap_sanick(_link, msg, ctx)— parse<uid>(params[2]) +<newnick>(params[3] or trailing); only ifis_local_uidvalidate the nick, capture the record,try_rename(InUse → silent no-op, collision race),apply_local_nick_change,force_nick_uid. Non-local / unknown / malformed → no-op.
leveva/src/s2s/forward.rs— addSome("SANICK") => super::sanick::apply_encap_sanick(...).leveva/src/s2s/mod.rs—mod sanick;.leveva/src/command/sanick.rs— the remote branch (!is_local_uid): replace the NOTICE with a network-wide collision pre-check (uid_of(new)held by a different uid →433to the oper, immediate feedback) thens2s::sanick::propagate(ctx, &uid, &new); return no numeric on success (like KILL). The format432/461/401checks stay ahead of the locality branch.
Tests (TDD — RED first; inverse invariants + fuzz)#
s2s/sanick.rs:
- home server (local uid): renames registry + force-nicks session (
ForceNick) + co-member sees client-form:<oldmask> NICK <new>+ WHOWAS filed. - inverse: non-local (mirrored) uid → no-op, mirror nick unchanged (waits for the NICK relay).
- unknown uid → no-op; malformed newnick → no-op.
- collision inverse: local uid,
newheld by another local user → no-op, both nicks survive. propagatebroadcasts the exactENCAP * SANICKline to a linked peer; no-peers → no-op.- proptest (fuzz): arbitrary valid uid/nick —
apply_encap_sanicknever panics, and apropagate → apply_encap_sanickround-trip on the home server leaves the target holdingnew(when free), else unchanged.
command/sanick.rs:
- remote target → broadcasts
ENCAP * SANICK <uid> <newnick>to the peer mailbox, no NOTICE, no numeric to the oper. - remote target whose
newnickis taken network-wide →433to the oper, nothing broadcast. - the existing local tests stay green (the local success path is untouched).
Gate#
cargo test -p leveva green (incl. new units + proptest); cargo clippy -p leveva --tests clean;
cargo build -p leveva 0 warnings. No boot-golden (forced rename is a mailbox/peer effect the
units assert; the resulting NICK line is characterized by existing nick goldens).
Follow-on#
SAJOIN/SAPART S2S propagation — route ENCAP * SAJOIN/SAPART to the home server, which runs
its existing local force_join/force_part (whose JOIN/NJOIN/PART S2S relay then propagates).