name: leveva-server-notices description: "leveva's +s server-notice umode + the snotice::server_notice fan (slice 184); the snomask category mask (slice 257); what's wired and the documented follow-ons" metadata: node_type: memory type: project originSessionId: dacbd64f-407d-419c-8331-59c72891578f#
P11 slice 184 (2026-06-15) re-introduced the oracle's +s (FLAGS_SERVNOTICE)
user mode and built the server-notice delivery plane, closing the recurring
"leveva has no server-notice masks yet" deferral named in command/kill.rs,
command/rehash.rs, and the SAMODE/SAPART slice plans.
Shape:
UserMode::ServerNotice('s', bit0x0200) inmode.rs, appended last inALL(soRPL_MYINFO/user_modes_stringis nowoOiwraWBxs).- Operator-gated to set (the
command::mode::umode's'arm): a non-oper's+sis silently ignored (no bit, no echo, no 501);-salways allowed. - The
+sBIT propagates (slice 254 madeSEND_UMODES= every umode — this memory's original "excluded from SEND_UMODES" claim is STALE), but the fan is still local:snotice::server_notice*iteratesregistry.records()(local clients only), so a remote-mirrored+suser is never fanned. The snomask (slice 257) is local-only and never propagates. snotice::server_notice(ctx, text)(newsrc/snotice.rs) fans:<server> NOTICE <nick> :*** Notice -- <text>(the*** Notice --marker is prepended by the helper) viaregistry.deliver_uid. Modelled on the WALLOPS-to-+wfan ([[leveva-s2s-notify-propagation]] slice 178 /command/wallops.rs).
Wired so far: local-oper KILL (local-victim branch) + REHASH (slice 184);
inbound-S2S-KILL local-victim branch (slice 185); the SA* family
(SAJOIN/SAPART/SANICK/SAMODE, slice 186); server splits (slice 219);
client connect/exit connection report (slice 226).
Slice 185 (2026-06-15): closed the inbound-S2S-KILL follow-on. s2s::kill::eject_local_victim
now fires snotice::server_notice after the eject — Received KILL message for <victim> from <killer_nick>: <reason>, the killer's nick taken from the pre-! segment of the already-computed
relay::source_mask killer_display (a UID→nick!user@host, a SID→server name). Only the
victim's home server posts it (+s is local-only), so a transit/origin node and the
further-away-victim case stay silent — the correct single-server-local semantic. Fuzzed via the
new kill_s2s_proptest::inbound_local_kill_notifies_plus_s_watchers property.
Slice 186 (2026-06-15): closed the last server-notice follow-on — the SA* family.
SAJOIN/SAPART/SANICK fire server_notice at their success/route sites (sajoin.rs/
sapart.rs/sanick.rs), SAMODE inside mode_dispatch's if !effective.is_empty() broadcast
block gated on oper_override (so a bare query / user-mode target / plain MODE posts nothing).
InspIRCd-style text <oper> used SA… , fired only when the override actually acts (≥1 valid
channel / a successful part / a successful rename / an applied+broadcast change) — and on the
issuing server for both local- and remote-target invocations (the oper acted here), but still
local-only since +s never crosses a link. Fuzzed via tests/sa_snotice_proptest.rs (one
invocation per verb, outcome forced by construction, arbitrary +s/-s watcher mix). Plan:
docs/superpowers/plans/2026-06-15-p11-slice186-sa-server-notices.md.
Slice 219 (2026-06-16): closed the SQUIT "no server notices" divergence (named in
command/squit.rs + s2s/squit.rs). s2s::squit::squit_subtree — the single teardown
chokepoint every split trigger funnels through — now fires server_notice with the pure
split_notice_text(s1, s2, users_lost) → Netsplit <s1> <s2> (<n> user[s]) (s1/s2 reuse the
netsplit-batch endpoint names: surviving side first, departed second). Because it's at the
chokepoint, an oper SQUIT, an inbound S2S SQUIT, AND an abnormal link termination
(main.rs socket-drop/ping-timeout calling squit_subtree) all notify exactly once. Guarded on
departed.is_some() so an idempotent re-teardown of an already-gone server stays silent (no
"Lost 0 users"). proptest: split_notice_text total + well-formed + pluralizes iff count≠1.
Plan: docs/superpowers/plans/2026-06-16-p11-slice219-squit-server-notice.md.
Slice 226 (2026-06-16): added the classic IRCnet/charybdis SCH_NOTICE connection report
(the last classic notice still missing). snotice::connect_notice(nick,user,host,ip,realname) /
exit_notice(nick,user,host,reason,ip) are pure format seams (charybdis client.c form minus the
{class} token). session::finalize_registration fans the connecting report after
counters.register() (ip from registry.orighost(uid) — cloak-independent; host is the visible
possibly-cloaked one); session::release's Phase::Registered arm fans the exiting report before
registry.release frees the record. Local-only (a remote client's report fires on its home
server; both seams are reached only by local clients) → no S2S plane. A local KILL victim posts the
exit notice in addition to its SCH_KILL notice (faithful — a kill is an exit). Fuzzed via
tests/connect_notice_proptest.rs; golden via golden_snotice.rs. Plan:
docs/superpowers/plans/2026-06-16-p11-slice226-connect-exit-notice.md.
Slice 257 (2026-06-18): closed the per-category snomask follow-on (charybdis
snomask). +s is now the one parameterized umode: MODE nick +s +ck subscribes to
just the connect/kill categories; a bare +s seeds SnoMask::ALL (slice-184 back-compat,
which is why every existing +s watcher test now also seeds ALL). New src/snomask.rs — the
pure apply_delta(cur, s) (sign-prefixed letter delta, ignores unknown) + render(mask) +
SnoMask::ALL/cconn()/skill()/general() (the fuzz seam). Per-record
ClientRecord.snomask: u32 (Registry::set_snomask/snomask_of).
snotice::server_notice_cat(ctx, cat, text) fans only to +s && snomask&cat records;
server_notice is the GENERAL (s) alias. Categorised producers: connect/exit→cconn (c),
KILL (command + s2s)→skill (k), rehash/SA*/squit stay GENERAL (s). 008 RPL_SNOMASK
reports the mask on a +s change; an emptied mask drops +s; a non-oper +s <mask> swallows
its param (no 501). 221/004 unchanged (mask via 008 only) → no welcome-burst churn. Letters
with producers: c/k/s; the rest (C n r u x y d f b) are valid-but-unproduced
subscriptions. command/mode.rs::umode loop is now index-aware so the s branch consumes its
next-token param. Plan: docs/superpowers/plans/2026-06-18-p11-slice257-snomask.md.
Slice 258 (2026-06-18): wired the first producer-less snomask category from 257 — n
NCHANGE (charybdis SNO_NCHANGE). snotice::nchange_notice(old,new,user,host) →
Nick change: From <old> to <new> [<user>@<host>] (pure fuzz seam); SnoMask::nchange()
accessor. Fired from the shared command::nick::apply_local_nick_change chokepoint (so both
NICK and SANICK emit it) via server_notice_cat(ctx, nchange(), …). Local-only (the fan is
local records; a remote rename fires on its home server) → no S2S plane. The existing SANICK
+s(ALL) audit-notice test now asserts the spy notice arrives first, then the audit. Fuzz:
tests/nchange_snomask_proptest.rs. Plan:
docs/superpowers/plans/2026-06-18-p11-slice258-nchange-snomask.md.
Slice 259 (2026-06-18): wired the second producer-less category — x EXTERNAL
(charybdis SNO_EXTERNAL). snotice::server_intro_notice(name,by) → Server <name> being introduced by <by> (pure fuzz seam); SnoMask::external(). Fired from the
s2s::server::handle_server success path (a server introduced behind a linked peer), by
= the direct peer (link.name, charybdis client_p->name). Local-only. Rejected
(collision/own-SID/malformed) introductions fire nothing. Fuzz:
tests/external_snomask_proptest.rs.
Slice 260 (2026-06-18): wired the third producer-less category — y SPY (charybdis
SNO_SPY). snotice::stats_spy_notice(letter,nick,user,host,server) → STATS <c> requested by <nick> (<user>@<host>) [<server>] (pure fuzz seam); SnoMask::spy(). Fired from the
command::stats::stats chokepoint before answering, gated on a selector char being present
and the requester resolving to a local registry record (the user/host source; the
unresolved branch only happens in synthetic tests). Fires for any selector (even unrecognized
f), skips a bare STATS; no self-exclusion. Local-only ([<server>] = ctx.name, STATS is
answered locally). Fuzz: tests/spy_snomask_proptest.rs.
Slice 261 (2026-06-18): wired the fourth producer-less category — f FULL (charybdis
SNO_FULL). snotice::conn_full_notice(nick,user,host,reason) → Too many connections for <nick>!<user>@<host> (<reason>) (pure fuzz seam); SnoMask::full(). Fired from the two
connection-cap denials in session.rs::finalize_registration: the local class admission
(ConnLimits::try_admit, after the nick claim → released before the fan) and the global per-host
caps (check_global_admission, before the claim → requested nick reported). charybdis varies the
verb per cap; leveva carries the exact CapDenial::reason() in parens. I-line/config-ban
(465) refusals are deliberately NOT FULL — those are the future REJ/UNAUTH categories. A clean
registration / non-cap refusal fans nothing. Local-only. Fuzz: tests/full_snomask_proptest.rs.
Producers now: c/k/s/n/x/y/f.
Documented follow-ons (NOT yet done):
- The remaining producer-less snomask categories:
CCCONNEXT,rREJ,uUNAUTH,dDEBUG,bBOTS — each a clean one-event-site producer slice like 258/259/260/261 (find the event, add a pure text seam, fan viaserver_notice_cat).bBOTS (the Excess Flood kill, slices 201-208) orrREJ/uUNAUTH (connection-rejection at registration — the465I-line/config-ban refusals slice 261 deliberately left out of FULL) are natural nexts. - The
{class}token in the connect report (the I-line class name is not plumbed to the finalize seam) — the only remaining classic server-notice gap. - A REHASH knob for the default (bare-
+s) snomask (charybdis configures it; leveva defaults to ALL) — a clean const→knob follow-on if ever wanted.
Plan: docs/superpowers/plans/2026-06-15-p11-slice184-server-notices.md.
Recording discipline: [[leveva-slice-recording]].