Proof of concept mechanical port of ircnet/ircd to Rust as part of a bit about C being insecure for network services
0

Configure Feed

Select the types of activity you want to include in your feed.

ircd.rs / docs / claude-memory / leveva-server-notices.md
11 kB

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', bit 0x0200) in mode.rs, appended last in ALL (so RPL_MYINFO/user_modes_string is now oOiwraWBxs).
  • Operator-gated to set (the command::mode::umode 's' arm): a non-oper's +s is silently ignored (no bit, no echo, no 501); -s always allowed.
  • The +s BIT propagates (slice 254 made SEND_UMODES = every umode — this memory's original "excluded from SEND_UMODES" claim is STALE), but the fan is still local: snotice::server_notice* iterates registry.records() (local clients only), so a remote-mirrored +s user is never fanned. The snomask (slice 257) is local-only and never propagates.
  • snotice::server_notice(ctx, text) (new src/snotice.rs) fans :<server> NOTICE <nick> :*** Notice -- <text> (the *** Notice -- marker is prepended by the helper) via registry.deliver_uid. Modelled on the WALLOPS-to-+w fan ([[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: C CCONNEXT, r REJ, u UNAUTH, d DEBUG, b BOTS — each a clean one-event-site producer slice like 258/259/260/261 (find the event, add a pure text seam, fan via server_notice_cat). b BOTS (the Excess Flood kill, slices 201-208) or r REJ/u UNAUTH (connection-rejection at registration — the 465 I-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]].