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.

Progress log — P5 (Command handlers)#

See PLAN.md for the phase table and migration model, and the command-cluster-port skill for the per-cluster procedure. Each P5 sub-cluster appends one entry here.

  • 2026‑06‑02 — P5a (s_service.c — first command‑handler cluster) merged. All 6 compiled symbols (svctop, make_service, free_service, best_service [static], m_service, m_servlist, m_squery) ported to ircd-common/src/s_service.rs and s_service.o dropped outright (added to PORTED, like parse.o). Establishes the P5 handler‑porting mechanism: a Rust m_* reachable through the msgtab fn‑pointer seam. Spec/plan: docs/superpowers/plans/2026-06-02-p5a-s_service.md.

    • parse.rs needs no change: it already imports m_service/m_servlist/m_squery/svctop from ircd_sys::bindings (it imports all symbols there, even already‑ported ones); those declarations resolve to the Rust definitions at link once s_service.o is dropped. This is the general seam for every future handler — define the Rust symbol, drop the .o, no msgtab edit.
    • Variadic senders: Rust calls sendto_one/sendto_flag via a local extern "C" block (the s_numeric.rs pattern); fmt must be declared *mut c_char (matching the bindgen/other‑module decls) to avoid the clashing_extern_declarations lint, so format‑string literals are cast c"…".as_ptr() as *mut c_char. MyFree = libc free; strncpyzt/index(=strchr)/myncmp ported.
    • Config (USE_SERVICES off) shapes the port: m_servset/check_services_*/find_conf_service + the local‑service branch of m_service are not compiled and not ported; a client SERVICE reaches the #ifndef USE_SERVICES reject (ERROR :Server doesn't support services).
    • msgtab dispatch findings (caught at L2): a registered client's SERVICE maps to m_nop (msgtab index 1 = registered‑client column is m_nop), so it produces no reply — m_service is only reached from index 0 (unregistered) / 4 (server→SERVICE). Bare SQUERY is rejected by parse() with 461 (min‑params) before the handler. So L2 coverage of the ported code is: m_servlist (235), m_squery+best_service (408, empty list), and m_service reject (unregistered client → ERROR).
    • L2 harness hardening: ircd-golden boots now serialize through a process‑wide PORT_LOCK held by the Ircd guard — cargo test runs #[test]s on parallel threads and they all bind the one fixture port (16667). (Single‑test golden_registration had masked this; the 2‑test golden_service surfaced it.)
    • Verification: L1 s_service_diff zero‑diff vs cref_ (make_service svctop linkage + cptr->name self‑pointer + idempotency; free_service head/middle unlink + service=NULL); L2 golden_service 2/2 byte‑identical (registered SERVLIST/SQUERY + unregistered SERVICE reject); the Rust ircd links with s_service.o dropped (m_service/svctop resolve to ircd‑common); full cargo test green; 0 warnings.
  • 2026‑06‑02 — P5b (s_debug.c — first utility/callee TU) merged; s_zip.c dropped. All 4 compiled symbols (serveropts config‑resolved option string, send_usage, send_defines, count_memory) ported to ircd-common/src/s_debug.rs and s_debug.o dropped outright; s_zip.o also added to PORTED (ZIP_LINKS off → its only symbol is a static rcsid, nothing to port). The variadic debug() is #ifdef DEBUGMODE (off) → not compiled, not ported. This is the first utility TU (no msgtab entry): the three report fns are reached from still‑C m_stats (s_serv.c) via STATS letters.

    • Classification → L2 strategy: s_debug has no client‑reachable m_*; coverage comes from driving the caller's command. STATS dsend_defines (RPL_STATSDEFINE 248, all compile‑time constants — fully deterministic), STATS zcount_memory(.., 0) (RPL_STATSDEBUG 249 memory counts from istat/sizes). Both reachable by a plain registered local client (no oper gate on d/z). STATS r/Rsend_usage is not L2‑covered (getrusage CPU/RSS fields are wall‑clock volatile); ported faithfully (HAVE_GETRUSAGE path, hzz=1) + links, but exercised only by code review.
    • Config‑resolved data, flattened (not #ifdef‑replicated): serveropts = "aEFJKMQRTu6" (HUB off, CLONE_CHECK on, OPER_* on, IDLE_FROM_MSG on; dumped from the oracle .o and pinned by L1). send_defines bakes ~30 macro constants bindgen drops (MAXSERVERS=1 [HUB off], CLONE_MAX/PERIOD=10/2, ZL=-1, DELAY_CLOSE=15, MINLOCALNICKLEN→1, …) as resolved literals — the STATS d L2 byte‑diff is the safety net for any wrong constant. BUFFERPOOL resolved to 222320 (= DBUFSIZ 2032·MAXCONN 50·2 + QUEUELEN 19120·MAXSERVERS 1).
    • Determinism / canonicalizer: count_memory's final :TOTAL: <tot> sbrk(0)-etext: <N> line carries a volatile heap‑break token (differs between the separately‑built ref‑C and Rust binaries); added one canonicalizer mask (truncate after sbrk(0)-etext:). Everything else in STATS z is deterministic — cres_mem sends two RPL_STATSDEBUG lines but with the DNS cache empty (NO_DNS) they're all‑zero, and tot is config/counter‑derived. count_memory's if(debug) [REAL] branches (the client/channel/conf list walks) are ported faithfully but never execute on the STATS z path (debug=0).
    • libc for getrusage/rusage: sys/resource.h isn't in bindgen's wrapper.h, so send_usage uses libc::{rusage, getrusage, RUSAGE_SELF} rather than a hand‑rolled struct; sbrk/time/strerror via a local extern "C" block. Variadic sendto_one called the s_service.rs way (fmt *mut c_char, literals cast).
    • Verification: L1 s_debug_diff zero‑diff vs cref_serveropts (+ pins the resolved value); L2 golden_debug 2/2 byte‑identical (STATS d send_defines block incl. :HUB:no MS:1; STATS z count_memory block incl. masked sbrk(0)-etext: <BRK>); the Rust ircd links with s_debug.o+s_zip.o dropped (serveropts/send_*/count_memory resolve to ircd‑common); full cargo test green; 0 warnings.
  • 2026‑06‑02 — P5c (s_misc.c — the exit cascade + misc utilities) merged. All 18 ext‑header symbols + the two file‑statics (exit_server/exit_one_client) + the 4 exported globals (ircst/ircstp, motd/motd_mtime) ported to ircd-common/src/s_misc.rs; s_misc.o dropped outright (every symbol portable → added to PORTED, no _link.o). This is the largest utility TU so far (1217 LOC): exit_clientexit_serverexit_one_client is the mutually/self‑recursive client/server exit cascade (the critical network path), plus date/get_client_{name,host,ip}/get_sockhost/my_name_for_link/mark_blind_servers/tstats/read_motd/check_split/check_registered*/checklist/initstats/initruntimeconf/myrand/mysrand. Spec/plan via the command‑cluster‑port skill.

    • Config gates that shaped the port (all verified in cbuild/config.h): USE_SERVICES/UNIXPORT/USE_SYSLOG/FNAME_USERLOG/FNAME_CONNLOG/CLIENTS_CHANNEL/BETTER_CDELAY/BETTER_NDELAY off; DELAY_CLOSE=15/USE_HOSTHASH/USE_IPHASH/USE_IAUTH/CACCEPT_DEFAULT=2 on. Crucially the entire exit_client per‑client logging block (s_misc.c:465‑507) is gated on (FNAME_USERLOG||FNAME_CONNLOG||USE_SERVICES||(USE_SYSLOG&&…)) — all undefined → omitted, so sendto_flog is never called from s_misc (one fewer variadic to thread). The #ifdef USE_SERVICES check_services_butone calls throughout the cascade are likewise not compiled/not ported.
    • Keystone finding — variadic %lu‑from‑u_int is a caller‑side faithfulness hazard, caught by L2. tstats passes ~30 u_int (32‑bit) istat/stats fields straight to %lu (64‑bit) — UB that prints correctly in C only because the SysV ABI zero‑extends 32‑bit args into their 64‑bit slots. A Rust variadic call leaves the upper 32 bits undefinedSTATS t printed garbage (refused 94888712470528). Fix: cast every integer arg to exactly its conversion width (%luu_long, %lluc_ulonglong, %dc_int, %uc_uint); the decimal value is unchanged (all fields < 2³²) so the wire output is byte‑identical to a correct C run. This is now a standing rule for any ported fn that calls the C variadic senders with sub‑long integers. (The golden_s_misc STATS t diff is what surfaced it — exactly what L2 is for.)
    • format!/byte‑builder instead of libc sprintf (per user request): the buffer‑formatting fns no longer call sprintf. date (pure ASCII) uses Rust format! then copies into its static [c_char;80]. get_client_{name,host} + exit_client's comment1 use small byte‑exact push_cstr/push_cstr_prec/store helpers — not format!, because C's %.*s byte‑precision over arbitrary (possibly non‑UTF‑8) C‑string bytes can't pass through a UTF‑8 String without corruption. The senders (sendto_one/sendto_flag/sendto_serv_butone/sendto_common_channels/sendto_channel_butserv) stay C variadics (P8). (Caught en route: &str::as_ptr() is not NUL‑terminated → an early sprintf %s overran into adjacent rodata; the byte‑builder rewrite removes that footgun class.)
    • Globals owned here: ircst/ircstp (*ircstp = &ircst) as #[no_mangle] static mutircst zero‑inits via MaybeUninit::zeroed().assume_init() (const), ircstp via addr_of_mut!(ircst); motd=NULL, motd_mtime=0. months/weekdays stay module‑private &str tables. All Is*/Got*/strncpyzt macros reproduced inline; match/mycmp imported from bindings; hash/list/whowas callees (del_from_*_hash_table/remove_client_from_list/unregister_server/add_history/off_history) resolve to the Rust ports; channel/conf/bsd callees (remove_user_from_channel/del_invite/find_conf_name/close_connection/activate_delayed_listeners/is_chan_op/remove_server_from_tree/report_iauth_stats/dgets) stay C.
    • Verification: L1 s_misc_diff zero‑diff vs cref_ (date over 4 epochs; my_name_for_link dot‑walk/guard; get_sockhost @‑strip; get_client_ip ::ffff: inetntop; initstats/initruntimeconf seeded fields; mysrand/myrand determinism). L2 golden_s_misc 2/2 byte‑identical: QUITexit_client local path (ERROR :Closing Link: alice[~alice@127.0.0.1] ("gone fishing")), STATS ttstats (full RPL_STATSDEBUG block; :time connected masked as wall‑clock volatile). Added two canonicalizer masks: :time connected and a drop of the racy SPLIT_CONNECT_NOTICE (register‑time NOTICE whose post‑422 TCP chunking is nondeterministic). Full cargo test green; 0 warnings. Deferred to S2S/multi‑client L2: the exit_one_client channel‑QUIT broadcast, server SQUIT propagation, masked‑server paths (ported faithfully, covered by review).
  • 2026‑06‑02 — P5d (s_user.c WHO/WHOIS cluster) merged. The contiguous WHO/WHOIS display cluster (s_user.c:1685‑2315 — who_one/who_channel/who_find [statics], parse_who_arg [file‑global], send_whois [static], m_who/m_whois [exported, msgtab]) ported to ircd-common/src/s_user.rs. First in‑TU partial port of P5: s_user.os_user_link.o (‑DPORT_USER_P5 #ifndef‑guards the cluster out of the link set; the cref oracle keeps the full unguarded s_user.o). The rest of s_user.c (register_user/m_nick/m_kill/m_umode/m_oper/m_message/… + the shared statics buf/buf2/user_modes) stays C. s_user_link.o built via make --eval so the per‑object FNAME_USERLOG/CONNLOG/OPERLOG path defines (used by the surviving C register_user/m_oper) expand identically (the send_link.o pattern).

    • s_auth.c deferred (not a P5 cluster): it has no m_* handlers — it's the ident/iauth glue (start_auth/send_authports/read_authports/read_iauth/sendto_iauth), pure socket/pipe I/O touching adfd/highest_fd/local[]/start_iauth, and the golden harness boots ‑s (BOOT_NOIAUTH → adfd<0) so almost none of it runs ⇒ no L2 path. Belongs to P7 (I/O core) + P‑IAUTH.
    • Seam (no parse.rs edit): parse.rs already imports m_who/m_whois from ircd_sys::bindings; once s_user.os_user_link.o drops them, those decls resolve to the Rust defs. parse_who_arg is #[no_mangle] only so the L1 test can reach it (no header/external C caller). Still‑C callees (canonize/hunt_server/next_client/send_away/is_chan_op/has_voice) + Rust‑ported callees (find_client/find_channel/hash_find_client/match_/strtoken/get_client_ip/collapse/clean_channelname/cid_ok) reached via bindgen decls; sendto_one is the C variadic trampoline.
    • format! per user request: WHOX (354) is built field‑by‑field into a Vec<u8> — numeric fields (%d hopcount, %ld idle) via format!, arbitrary C‑string fields (username/host/info/…) copied raw via push_cstr (a UTF‑8 String would corrupt non‑ASCII bytes), then truncated to BUFSIZE‑1 (faithful to C's incremental snprintf/snprintf_append truncation). send_whois's channel‑list accumulation keeps the byte‑exact pointer/len logic on a private static mut BUF (the C file‑static buf stays with the C remnant). The placeholder UnknownUser anUser (unreachable on the IsPerson path) reproduced as a static mut via a const carr().
    • Config: WHOIS_SIGNON_TIME on → 317 carries the extra signon %ld (idle+signon masked by the existing canonicalizer). IsMember active def = find_channel_link(u->user->channel, c). IsChannelName uses Rust cid_ok.
    • Verification: L1 s_user_who_diff zero‑diff vs cref_parse_who_arg over 12 WHOX arg strings (o/%flags/,token len‑cap/unknown chars). L2 golden_s_user 2/2 byte‑identical: WHOIS alice (311/312/317‑masked/318) + WHO alice (352/315). The Rust ircd links with s_user_link.o substituted (m_who/m_whois/parse_who_arg resolve to ircd‑common). Full cargo test green; 0 warnings. Deferred to S2S/multi‑client L2: WHOX, channel WHO/WHOIS, wildcard WHOIS, the hunt_server remote path (ported faithfully, covered by review + the L1 differential).
  • 2026‑06‑02 — P5e (s_user.c USERHOST/ISON cluster) merged. m_userhost (s_user.c:3073) and m_ison (s_user.c:3127) ported into ircd-common/src/s_user.rs, extending the existing s_user_link.o partial port — two more #ifndef PORT_USER_P5 regions guard them out of the link set; the cref oracle keeps the full unguarded s_user.o. Pure handler cluster (both are msgtab rows, client‑reachable): no parse.rs edit (the m_userhost/m_ison decls parse.rs imports from ircd_sys::bindings resolve to the Rust defs at link). The shared C file‑statics buf/buf2 are replaced by private Rust Vec<u8> accumulators — faithful since each is used transiently within a single call (no cross‑call state); the C remnant (m_umode/register_user) keeps its own buf.

    • format! per user request: both replies are byte‑exact reconstructions of replies[RPL_USERHOST]/replies[RPL_ISON] (":%s 30x %s :") built field‑by‑field. Every appended field (nick/username/host) is an arbitrary C‑string copied raw via push_cstr — a UTF‑8 String/format! would corrupt non‑ASCII bytes — and these handlers have no integer conversions, so the only format!‑eligible tokens are the literal numerics/separators (kept as byte literals, matching the existing who_one 354 reconstruction). Documented in‑module: format! is used for ASCII/numeric, raw copy for C‑string bytes.
    • Faithfulness notes: m_userhost walks up to 5 targets across parv[] via the idx index (parv[++idx]), space‑separates entries (the if(*buf2) "not the first entry" guard → tracked by buf2 staying non‑empty), reproduces the strncat(buf, buf2, sizeof(buf)-len) cap and C's len += strlen(buf2) (the full length even when truncated), and the re‑head/flush at len > BUFSIZE-(NICKLEN+5+HOSTLEN+USERLEN)=419 (effectively dead for ≤5 short targets, ported anyway). m_ison strtoken‑splits the single trailing param, appends name + a trailing space per hit, and breaks at len+i > BUFSIZE-4 ("leave room for \r\n\0").
    • Harness fix (cross‑process boot lock). Adding a 6th ircd-golden test binary surfaced a latent flake: cargo test runs each test file as its own process and runs several in parallel, but the boot serializer was an in‑process Mutex — so two binaries (e.g. golden_debug + golden_s_user_userhost) raced to bind the one fixture port 16667 → BrokenPipe/diff failures. Replaced the Mutex with a cross‑process advisory file lock (libc::flock(LOCK_EX) on fixtures/.port.lock, held in the Ircd guard, released when its fd closes after child.kill()+wait()); separate fds contend even within one process, so it also serializes the parallel #[test] threads inside a binary. libc added as an ircd-golden dep; the lock artifact is gitignored.
    • Verification: L2 golden_s_user_userhost 2/2 byte‑identical (USERHOST alice nemo → 302 with the multi‑target idx walk + a find_person miss on nemo; ISON :alice nemo → 303 with a hit + a miss); both replies fully deterministic (no time tokens → no canonicalizer change). The Rust ircd links with m_userhost/m_ison defined T from ircd‑common. Full cargo test green and stable across repeated runs (flake fixed); 0 warnings. Deferred to multi‑client L2: the >BUFSIZE batch‑flush paths (need many recipients; ported faithfully, covered by review).
  • 2026‑06‑02 — P5f (s_user.c message‑routing cluster) merged. m_message (the shared PRIVMSG/NOTICE delivery core, s_user.c:1457) + its two #[no_mangle] wrappers m_private/m_notice ported into ircd-common/src/s_user.rs, extending the existing s_user_link.o partial port (the PORT_USER_P5 guard now spans m_message through the WHO/WHOIS cluster as one contiguous region — the redundant inner #ifndef was removed). Pure handler cluster (both wrappers are msgtab rows: PRIVMSG→m_private col 1, NOTICE→m_notice): no parse.rs edit (the decls parse.rs imports from ircd_sys::bindings resolve to the Rust defs once s_user.os_user_link.o drops them). RED confirmed via undefined m_private/m_notice (m_message is static → inlined into both, never a link symbol).

    • First multi‑client L2. The user required #/+/& channel relay coverage, which needs ≥2 simultaneous clients (sendto_channel_butone delivers to the other members, never the sender). Added to ircd-golden: a persistent Client struct (connect/register/send/read_until/drain) and boot_standalone (-p standalone). New golden_s_user_message (7 scenarios): PRIVMSG/NOTICE self‑echo, PRIVMSG no‑such‑nick (401), and alice→bob relay on #chan/+chan/&chan (PRIVMSG) + #chan (NOTICE). All byte‑identical vs reference‑C.
    • Keystone finding — a lone server is permanently split‑mode, which blocks #‑channel creation. The fixture M‑line's split thresholds are floored to SPLIT_SERVERS(10)/SPLIT_USERS(5000) (s_conf.c:1950), so check_split() never leaves split (needs 10 eob‑servers + 5000 users). During split, channel.c:2537 (IsSplit() && UseModes(name) && !(&||!)) rejects creating global # channels with ERR_UNAVAILRESOURCE — but allows + (not UseModes) and & (excluded). So # golden tests must boot -p standalone (iconf.split = -1IsSplit() false), which lets all three prefixes be created uniformly. (This is exactly why +/& passed but # failed on the first run.)
    • Harness gotcha — sentinel collision with the split notice. The SPLIT_CONNECT_NOTICE (NOTICE alice :…split-mode.) can arrive after the 422 the registration drain stops on (TCP‑chunking race) and contains the substring NOTICE alice, which falsely satisfied the NOTICE‑self read_until(" NOTICE alice "). Fixed by draining after register before sending. (PRIVMSG's sentinel PRIVMSG alice doesn't collide, so only NOTICE‑self failed at first.)
    • format! per user request — N/A here, documented. m_message is pure routing: every reply is handed to a C variadic sender (sendto_one/sendto_prefix_one/sendto_channel_butone/sendto_match_butone) as format‑string + raw C‑string args; there is no intermediate string to build, and pre‑formatting would be wrong (a sender rewrites the per‑recipient prefix itself). Senders imported from ircd_sys::bindings (bindgen declares pattern: *mut c_char → no clashing_extern_declarations); strrchr (rindex) added to the local extern block.
    • Faithfulness notes: syntax is declared outside the per‑target loop (an oper's $$/$# match leaks into later targets — verbatim C); the C for‑loop post‑expr nick = strtoken(…), penalty++ (which must run even on continue) is modeled with a labeled 'target block where each C continuebreak 'target, followed by the advance; the nick!user@host and user[%host]@server paths NUL‑terminate nick in place then restore !/@/%, exactly as C mutates the strtoken slice.
    • Verification: golden_s_user_message 7/7 byte‑identical; m_private/m_notice defined T from ircd‑common in the Rust ircd; full cargo test green (all 12 *_diff L1 + layout + all 7 golden suites); 0 warnings. Deferred to S2S L2: the server‑sourced/uid (find_uid), @server‑forwarding, and oper $$/$#‑mask broadcast paths (need a server link; ported faithfully, covered by review).
  • 2026‑06‑02 — P5g (s_user.c AWAY cluster) merged. m_away (the AWAY command handler, s_user.c:2702) + send_away (the RPL_AWAY emitter, s_user.c:3547) ported into ircd-common/src/s_user.rs, extending the existing s_user_link.o partial port (two new #ifndef PORT_USER_P5 guard regions; the cref oracle keeps the full unguarded s_user.o). Pure handler cluster (AWAY→m_away is a msgtab row) — L2 golden is the gate, RED confirmed via undefined m_away/send_away; no parse.rs edit. send_away was previously imported from ircd_sys::bindings and called by the already‑ported m_message/m_whois; removing it from the import list and defining it locally makes those two call sites resolve to Rust (it's still #[no_mangle] pub extern "C" since it's an s_user_ext.h symbol). Plan: docs/superpowers/plans/2026-06-02-p5g-s_user-away.md.

    • Config that shaped the port (verified in cbuild/config.h): USE_SERVICES offm_away's two #ifdef USE_SERVICES check_services_butone blocks not compiled/not ported. AWAY_MOREINFO defined (config.h:580) → send_away's else branch (away == NULL) is the ":%s 301 %s %s :Gone, for more info use WHOIS %s %s" form, not the #else RPL_AWAY‑"Gone" — ported the #ifdef AWAY_MOREINFO branch. Both callers guard on FLAGS_AWAY ⇒ away != NULL in practice, so the live path is the if (acptr->user->away) RPL_AWAY‑with‑text branch (the AWAY_MOREINFO else is faithfully ported but effectively dead).
    • Faithfulness note — the non‑MyConnect away‑buffer leak reproduced verbatim. For a remote (!MyConnect) sptr marking away, C MyMallocs the buffer into the local away but only assigns sptr->user->away = away (and strcpys) inside the if (MyConnect(sptr)) block — so the remote‑away allocation is leaked. The Rust port mirrors this exactly (allocate + set FLAGS_AWAY + the +a server propagation unconditionally; store + strcpy + 306 ack only under my_connect). Not "fixed" (P8 territory).
    • Types/format!: istat.is_away/is_awaymem are u_long (cast (strlen+1)/len to u_long); MyMalloc/MyRealloc (P2 Rust ports) take usize and return *mut c_char directly (no cast); MyFree→libc free; (*user).uid is [c_char;10] → passed via .as_mut_ptr(); TOPICLEN=255. format! is N/A — both fns hand every reply to a C variadic sender (sendto_one/sendto_serv_butone) as a format string + raw pointer/C‑string args (the sender formats per‑recipient); the only literals are the c"…" format strings (documented in‑module, matching the P5f note). No integer conversions through the senders → the P5c %lu‑width hazard doesn't apply.
    • Verification: L2 golden_s_user_away 3/3 byte‑identical: AWAY :gone fishing / AWAY (306 RPL_NOWAWAY then 305 RPL_UNAWAY — both m_away arms), WHOIS alice while away (send_away301 alice alice :brb inside the WHOIS block; 317 idle/signon masked by the existing canonicalizer), bob PRIVMSG alice while away (two clients → bob gets 301 bob alice :brb via send_away from the Rust m_message). m_away/send_away defined T from ircd‑common in the Rust ircd; full cargo test green (all 12 *_diff L1 + layout + all 8 golden suites); 0 warnings. No new canonicalizer masks needed. Deferred to S2S L2: the :%s MODE %s :±a server‑burst propagation (no server link under ‑s/‑p standalone; ported faithfully, covered by review).
  • 2026‑06‑02 — P5h (s_user.c UMODE cluster) merged. m_umode (s_user.c:3164), send_umode (:3358), send_umode_out (:3409) + the user_modes[] table (:38) ported into ircd-common/src/s_user.rs, extending the existing s_user_link.o partial port (one new #ifndef PORT_USER_P5 region spans the three fns; the user_modes[] static is guarded separately since only this cluster uses it). Handler cluster (UMODEm_umode is a msgtab row; MODE <nick> routes through still‑C m_mode (channel.c) → Rust m_umode once the target isn't a channel) — L2 golden is the gate. RED confirmed via undefined m_umode/send_umode/send_umode_out; no parse.rs edit. Plan: docs/superpowers/plans/2026-06-02-p5h-s_user-umode.md.

    • send_umode is called from three still‑C TUs (register_user in s_user.c, s_service.c, s_serv.c — all via send_umode(NULL, …, buf)); porting it means those C callers now resolve to the Rust def at link, so byte‑faithfulness is load‑bearing beyond just m_umode. This is why send_umode gets its own L1 differential (s_user_umode_diff): the +/- mode‑diff string builder with cptr=NULL is socket‑free, so it's L1‑testable directly (10 cases over old/current‑flags/sendmask/MyClient combos — pure/grouped/mixed add‑del/SEND_UMODES vs ALL_UMODES filtering).
    • Config (verified cbuild/config.h): USE_SERVICES off → m_umode's three check_services_butone blocks (away‑clear, oper‑±o, umode propagation) and send_umode_out's trailing block are not compiled/not ported. The active MODE_* values for s_user are MODE_NULL=0/MODE_ADD=0x40000000/MODE_DEL=0x20000000 (struct_def.h:876‑878; the 750‑758 block is commented out) — bindgen agrees. user_modes[] resolves to {(OPER,'o'),(LOCOP,'O'),(INVISIBLE,'i'),(WALLOP,'w'),(RESTRICT,'r'),(AWAY,'a')}; SEND_UMODES=61, ALL_UMODES=63.
    • format! per user request: m_umode's query reply ("+", pure ASCII) is built with a Rust String + CString (the format!‑family path), then handed to the C variadic sendto_one as the replies[RPL_UMODEIS] %s. send_umode writes its +/- diff into the caller‑owned *mut c_char buffer with the add/del grouping algorithm — that cannot be a String (the bytes must land in the C buffer, no owned allocation), so it stays a faithful byte‑builder. No integer conversions reach the senders → the P5c %lu‑width rule is N/A. Documented in‑module.
    • Faithfulness notes: the 'a' switch case falls through to default for server/internal callers (cptr==NULL || IsServer(cptr)) — reproduced by the _ => arm doing the 'a'‑specific away‑clear then continuing into the flag‑flip; a local client's +a is a no‑op (continue, the C switch break). The misleading‑indent return 1; after the USERSDONTMATCH else is inside the mismatch‑if block (unconditional once the guard trips) — reproduced. Inner loop advances the char pointer first so continue = C's switch break. ClearOper/ClearLocOp also set status=STAT_CLIENT; SetClient = IsAnOper?STAT_OPER:STAT_CLIENT — reproduced. The private UMODE_BUF static replaces the s_user.c file‑static buf (transient within send_umode_out; the C remnant keeps its own buf). istat.is_user/is_oper/is_awaymem are u_long; servp->usercnt is [c_int;3].
    • Verification: L1 s_user_umode_diff zero‑diff vs cref_send_umode (10 cases). L2 golden_s_user_umode 2/2 byte‑identical: MODE alice +i ; MODE alice (echo :alice MODE alice :+i via the ALL_UMODES local echo + 221 alice +i), MODE alice +iw-i ; MODE alice (parse nets to +w / ‑i → echo :alice MODE alice :+w + 221 alice +w, exercising the +/- grouping). m_umode/send_umode/send_umode_out defined T from ircd‑common in the Rust ircd; full cargo test green (all *_diff L1 + layout + all 9 golden suites); 0 warnings. No new canonicalizer masks. Deferred to S2S L2: the send_umode_out server‑broadcast loop (fdas/local[]:uid MODE name :modes) and the oper/restricted (det_confs_butmask, ERR_RESTRICTED) paths — need a server link / oper; ported faithfully, covered by review + the L1 differential.
  • 2026‑06‑02 — P5i (s_user.c PING/PONG cluster) merged. m_ping (s_user.c:2781) + m_pong (s_user.c:2818) ported into ircd-common/src/s_user.rs, extending the existing s_user_link.o partial port (one new #ifndef PORT_USER_P5 region spans both; the cref oracle keeps the full unguarded s_user.o). Pure handler cluster (PING→m_ping, PONG→m_pong are both msgtab rows, col 1) — L2 golden is the gate. RED confirmed via undefined m_ping/m_pong; no parse.rs edit. Plan: docs/superpowers/plans/2026-06-02-p5i-s_user-ping.md.

    • Cleanest cluster yet — pure routing, no statics. Both handlers call only external lookup/send fns (find_client/find_server/find_target/match/mycmp/sendto_one); no file‑statics, no allocators, no list linkage, so the shared C remnant (buf/buf2/user_modes) is untouched and there are no socket‑free data opsno L1 (like P5e/P5g). New bindings imports: find_target/FLAGS_PINGSENT/ERR_NOSUCHSERVER/ERR_NOORIGIN; reused the existing is_me helper (s_user.rs:905).
    • format! per user request — N/A, documented. Both fns hand every reply to a C variadic sender (sendto_one) as a format string + raw pointer/C‑string args (no intermediate string to build); the only literals are the c"…" format strings / replies[] numerics. No integer conversions reach the senders → the P5c %lu‑width rule is N/A.
    • Faithfulness notes: m_ping reassigns the local origin (find miss → cptr->name) but the else‑branch PONG still echoes the original parv[1] (verbatim C); match(destination, ME) != 0 = destination does not match the server name. m_pong clears FLAGS_PINGSENT (flags is c_long) before the target lookup; the &me no‑destination default uses addr_of_mut!(me).
    • Verification: L2 golden_s_user_ping 2/2 byte‑identical: PING :hello:irc.test PONG irc.test :hello (the find‑miss → else echo path), PING alice noserver.invalid402 ... :No such server (the find_server miss). m_ping/m_pong defined T from ircd‑common in the Rust ircd; full cargo test green (all *_diff L1 + layout + all 10 golden suites); 0 warnings. No new canonicalizer masks. No L2 path for m_pong (clears FLAGS_PINGSENT; only emits on the !IsMe forward path → needs a server/target link) — ported faithfully, covered by review.
  • 2026‑06‑02 — P5j (s_user.c OPER cluster) merged. m_oper (s_user.c:2866) ported into ircd-common/src/s_user.rs, extending the existing s_user_link.o partial port (one new #ifndef PORT_USER_P5 region around m_oper; the cref oracle keeps the full unguarded s_user.o). Handler cluster (OPER→m_oper is a msgtab row, client column) — L2 golden is the gate. RED confirmed via undefined m_oper (no other symbol; it has no file‑statics of its own under this config). No parse.rs edit. Reuses the already‑Rust send_umode_out (P5h) for the grant echo. Plan: docs/superpowers/plans/2026-06-02-p5j-s_user-oper.md.

    • Config that shaped the port (verified cbuild/config.h): CRYPT_OPER_PASSWORD off (#undef:311 → encr = password, no crypt), FNAME_OPERLOG/USE_SYSLOG/FAILED_OPERLOG (#undef:300)/UNIXPORT/USE_SERVICES off, NO_OPER_REMOTE off (#undef:555 → the #ifndef arm: if (aconf->flags & ACL_LOCOP) SetLocOp else SetOper). ⇒ the trailing logstring/syslog/logfile block is an empty block → omitted entirely; the resolved body is ~40 lines. New helpers added: set_oper/set_locop (flags |= FLAGS_OPER/LOCOP; status = STAT_OPER), mirroring the existing clear_oper/clear_locop. Via bindings: find_Oline/attach_conf/detach_conf/aConfItem/CONF_OPS/ACL_LOCOP/RPL_YOUREOPER/ERR_NOOPERHOST/ERR_PASSWDMISMATCH/ServerChannels_SCH_NOTICE; strcmp (StrEq) + sendto_flag added to the local extern block.
    • Faithfulness notes: encr = parv[2] may be NULL (OPER min‑params is 2) but is only dereferenced past a find_Oline hit, so the wrong‑name 491 path is safe; StrEq reproduced via libc strcmp (same UB as C if a matching O‑line is hit with no password — not exercised). The transient host split s = index(host,'@'); *s='\0'; …; *s='@' is reproduced verbatim even though s (after‑'@') is unused under NO_OPER_REMOTE‑off (net no‑op; mutates the C‑owned host in place and restores it). istat.is_oper is u_long; servp->usercnt is [c_int;3]usercnt[2] += 1. %d/%c args (the :%s %d %s :Too many … lines + the SCH_NOTICE operator‑letter) cast to c_int (the P5c width rule).
    • format! per user request — N/A, documented. Every reply is handed to a C variadic sender (sendto_one/sendto_flag) as a format string + raw pointer/%d/%c args (the sender formats per‑recipient); there is no intermediate string to build under this config (the only sprintf(buf,…) lives in the compiled‑out FNAME_OPERLOG block). Documented in‑module, matching the P5f/g/i notes.
    • L2 harness gained boot_conf(binary, args, conf) (refactor of boot_args, which now calls it with minimal.conf) so a test can use a dedicated config; new oper.conf = minimal.conf + a full‑operator O‑line O|*@*|secret|alice|0|10| (uppercase O → no ACL_LOCOP → SetOper; class 10 exists), isolated from every other golden test.
    • Verification: L2 golden_s_user_oper 1/1 byte‑identical over three OPER attempts on one registered client: OPER nope wrong491 ERR_NOOPERHOST (find_Oline miss), OPER alice wrong464 ERR_PASSWDMISMATCH (StrEq fails → detach path), OPER alice secret → success: :alice MODE alice :+o (send_umode_out ALL_UMODES local echo) + 381 RPL_YOUREOPER. m_oper defined T from ircd‑common in the Rust ircd; full cargo test green (67 passed, 0 failed: all *_diff L1 + layout + all 11 golden suites); 0 warnings. No new canonicalizer masks (no time tokens). Deferred to S2S L2: the send_umode_out server‑broadcast loop, the LocOp (o) path, and the SCH_NOTICE to server‑notice subscribers — need a server link / a subscribed oper; ported faithfully, covered by review.
  • 2026‑06‑02 — P5k (s_user.c QUIT/POST exit cluster) merged. m_quit (s_user.c:2479) + m_post (s_user.c:2467, the http‑proxy‑abuse alias) ported into ircd-common/src/s_user.rs, extending the existing s_user_link.o partial port (one new #ifndef PORT_USER_P5 region around s_user.c:2466‑2498; the cref oracle keeps the full unguarded s_user.o). Pure handler cluster (QUIT→m_quit, POST→m_post are both msgtab rows) — L2 golden is the gate. RED confirmed via undefined m_quit/m_post; no parse.rs edit. Both handlers terminate in the already‑Rust exit_client (P5c). Plan: docs/superpowers/plans/2026-06-02-p5k-s_user-quit.md.

    • Cleanest cluster after PING/PONG — self‑contained, one tiny static. m_quit's only state is its function‑local static char comment[TOPICLEN+1], reproduced as the private module static QUIT_COMMENT (not ABI); m_post has no state. They share no file‑statics with the C remnant (buf/buf2/user_modes untouched). m_post tail‑calls m_quit after a SCH_LOCAL server‑notice. New bindings imports: exit_client/get_client_host/ServerChannels_SCH_LOCAL; reused is_server/my_connect helpers.
    • Columns [server, client, oper, service, unreg] decide reachability: QUIT = [m_quit ×5] → a registered client (col 1) hits m_quit; POST = [m_nop,m_nop,m_nop,m_nop,m_post] → only col 4 (unregistered) hits m_post. So the two L2 scenarios use different client states (registered alice QUIT; raw unregistered connection POST). No clean socket‑free data op (both end in exit_client, which closes the connection) → no L1, like P5e/g/i.
    • format! per user request — N/A for the comment (documented). m_quit's comment is built from parv[1], an arbitrary possibly‑non‑UTF‑8 C‑string under C snprintf truncation at TOPICLEN; per the standing P5c rule that can't pass through a UTF‑8 String/format! without corruption → it's a faithful byte‑builder (snprintf_prefix_str emulates snprintf(buf,size,"<prefix>%s",s) truncation; the MyConnect path then strcats the closing " exactly as C). m_post's notice is handed straight to the C variadic sendto_flag (no intermediate string). No integer conversions reach the senders → the P5c %lu‑width rule is N/A.
    • Faithfulness notes: IsServer(sptr) → return 0; the MyConnect path is snprintf(comment, TOPICLEN, "\"%s", parv1) then strcat(comment, "\"") (= " + parv1 + "), the remote path is snprintf(comment, TOPICLEN+1, "%s", parv1) (raw, no quotes — reproduced even though it needs an S2S link to reach); parv[1] is NULL‑guarded to "" exactly as C ((parc>1 && parv[1]) ? parv[1] : "").
    • Verification: L2 golden_s_user_quit 2/2 byte‑identical: QUIT :gone fishing (registered) → ERROR :Closing Link: alice[~alice@127.0.0.1] ("gone fishing"), POST (unregistered) → the 020 connection notice + ERROR :Closing Link: [unknown@127.0.0.1] (""). m_quit/m_post defined T from ircd‑common in the Rust ircd; full cargo test green (all *_diff L1 + layout + all 12 golden suites); 0 warnings. No new canonicalizer masks (no time tokens). Deferred to S2S L2: the remote (!MyConnect) QUIT branch (needs a server link; ported faithfully, covered by review).
  • 2026‑06‑03 — P5l (s_user.c PASS cluster) merged. m_pass (s_user.c:3043) ported into ircd-common/src/s_user.rs, extending the existing s_user_link.o partial port (one new #ifndef PORT_USER_P5 region around s_user.c:3043‑3076; the cref oracle keeps the full unguarded s_user.o). Handler cluster (PASSm_pass, msgtab row [m_nop, m_reg, m_reg, m_nop, m_pass] → only the unregistered column hits m_pass, i.e. a registration‑time command on a raw connection). RED confirmed via undefined m_pass (the single symbol the new guard drops); no parse.rs edit. Plan: docs/superpowers/plans/2026-06-02-p5l-s_user-pass.md.

    • Self‑contained — buf touched only transiently. m_pass stores parv[1] into cptr->passwd (strncpyzt, 21) and, when PASS precedes USER/SERVICE with extra params, stages "<p2> <p3> <p4>" (byte caps 15/100/5) into the shared C file‑static buf[BUFSIZE] then immediately copies it out via cptr->info = mystrdup(buf)buf is never read across calls, so a private module static PASS_BUF: [c_char; BUFSIZE] is faithful and the C remnant's buf (register_user/m_user/m_userhost…) is untouched. No allocators of its own, no list linkage. MyFree→libc free; mystrdup/DefInfo are the P1/P2 Rust ports (resolve via bindings); strncpy/strncat added to the local extern block; strncpyzt reproduced as a helper (strncpy + x[N‑1]='\0').
    • format! per user request — N/A for the staged buffer (documented in‑module). The staged buffer is built from parv[2..4] — arbitrary possibly‑non‑UTF‑8 C‑strings under byte‑precision strncpyzt/strncat truncation caps; per the standing P5c rule a UTF‑8 String/format! would corrupt those bytes and can't reproduce strncpy NUL‑padding / strncat caps → it stays a faithful byte‑builder (the only literals are the " " separators). passwd is likewise a byte‑faithful strncpyzt. No integer conversions → the P5c %lu‑width rule is N/A. m_pass emits no wire output (returns 1; no sendto_*).
    • L1 is the workhorse (m_pass is socket‑free). s_user_pass_diff drives c_m_pass (resolves to Rust) and cref_m_pass on two parallel fresh clients and asserts identical passwd[0..21] bytes + info C‑string over 6 cases: passwd‑only (parc=2), full info‑staging (parc=5), passwd 21‑byte truncation, buf[0] 15‑cap, the cptr->user‑set early return (info stays DefInfo), and the strncat 100/5 caps. L2 golden_s_user_pass 1/1 byte‑identical: a raw unregistered client PASS secret ircd‑2.11 +options then NICK/USER — minimal.conf has no I‑line password so PASS is stored and ignored; the welcome banner stays byte‑identical (the "drive the path, assert no regression" gate, cf. s_debug). The full PASS form also exercises the info‑staging strcat/strncat branch live in both binaries. No new canonicalizer masks.
    • Verification: m_pass defined T from ircd‑common in the Rust ircd; L1 s_user_pass_diff 1/1 zero‑diff; full cargo test (all *_diff L1 + layout) + all 14 golden suites green; 0 warnings. Deferred to S2S L2: the server‑link parv[2..4] version/options consumption in m_server (the staged info is read there; ported faithfully on the m_pass side, covered by the L1 staging differential + review).
  • 2026‑06‑03 — P5m (s_user.c canonize utility) merged. canonize (s_user.c:283 — the O(n²) comma‑list deduplicator) ported into ircd-common/src/s_user.rs, extending the existing s_user_link.o partial port (one new #ifndef PORT_USER_P5 guard region around s_user.c:277‑319; the cref oracle keeps the full unguarded s_user.o). Utility / callee fragment (no msgtab row) — it is called from m_mode/m_part/m_topic (channel.c), m_whowas (whowas.c), and the already‑Rust WHO cluster (s_user.rs:521/797/960). RED confirmed via the three Rust call sites failing to find canonize once it was removed from the bindings import; defining it locally resolves those Rust callers + all the still‑C callers to the Rust def at link (the send_away/send_umode pattern from P5g/P5h). Plan: docs/superpowers/plans/2026-06-03-p5m-s_user-canonize.md.

    • Self‑contained — its own static, no shared state. canonize's only state is its function‑local static char cbuf[BUFSIZ] (BUFSIZ = 8192 on this glibc, verified via cpp -dM), reproduced as the module‑private CANON_BUF: [c_char; 8192] (not ABI). It shares no file‑statics with the C remnant (buf/buf2/user_modes untouched); it calls only strtoken+mycmp (P1 Rust ports) + libc strcpy.
    • Faithful raw‑pointer translation (the L1 differential is the safety net). The C body is intricate in‑place buffer surgery: the outer strtoken NUL‑splits the input; the inner loop walks cbuf with strtoken, restoring each comma it split (p2[-1] = ',') except at a dup match (break); unique tokens are strcpy'd and cp advances by (p - s) (token length incl. the split NUL). The Rust port mirrors every pointer op verbatim (p2.offset(-1), cp.offset(p.offset_from(s))). mycmp is IRC case‑insensitive → ABC,abc dedups.
    • format! per user request — N/A, documented in‑module. canonize copies arbitrary client‑supplied token bytes (channel names / nicks) verbatim via strcpy and mutates the buffers in place — there is nothing to format, and a UTF‑8 String/format! could neither reproduce strcpy of non‑ASCII bytes nor the in‑place strtoken/comma‑restore surgery. format! stays reserved for the ASCII/numeric reply cases (cf. m_umode query reply, who_one numeric fields).
    • Verification: L1 s_user_canonize_diff zero‑diff vs cref_canonize over 14 inputs (empty, single, adjacent/interior/casefold dups, channel names, trailing/leading/empty tokens, a longer list exercising the cp += (p‑s) advance). L2 golden_s_user_canonize 1/1 byte‑identical: a registered client's WHOWAS alice,alicem_whowas canonizes to aliceexactly one 406 ERR_WASNOSUCHNICK + 369 RPL_ENDOFWHOWAS (the dedup‑count assertion proves the branch fired live in both binaries). canonize defined T from ircd‑common in the Rust ircd; full cargo test green (all *_diff L1 + layout + all 15 golden suites); 0 warnings. No new canonicalizer masks (no time tokens). Deferred: the other s_user.c utilities (next_client/hunt_server/is_allowed) + the registration/nick cluster remain in s_user_link.o (C) for later P5 sub‑phases.
  • 2026‑06‑03 — P5n (s_user.c do_nick_name utility) merged. do_nick_name (s_user.c:245 — the nickname validator/canonicalizer) ported into ircd-common/src/s_user.rs, extending the existing s_user_link.o partial port (one new #ifndef PORT_USER_P5 guard region around s_user.c:245‑274; the cref oracle keeps the full unguarded s_user.o). Utility / callee fragment (no msgtab row) — it is called from still‑C m_nick/m_user/register_user; defining it #[no_mangle] resolves those C callers to the Rust def at link (the canonize/send_away pattern). RED confirmed via undefined do_nick_name once the guard dropped it. Plan: docs/superpowers/plans/2026-06-03-p5n-s_user-do_nick_name.md.

    • Cleanest leaf yet — pure string surgery, no statics, no allocators. Drops the nick (return 0) if it leads with -, leads with a digit in client context, or equals "anonymous" (case‑insensitive); else truncates in place at the first invalid char or the length cap and returns the final length. Shares no file‑statics with the C remnant (buf/buf2/user_modes untouched). Calls only libc strcasecmp + the P1 Rust char_atribs table.
    • Config that shaped the port (verified cbuild/config.h): NICKLEN=LOCALNICKLEN=15 (struct_def.h:49, config.h:726) → the length cap is 15 in both server/client contexts (the conditional kept faithfully). MINLOCALNICKLEN is #undef (config.h:732) → its #ifdef short‑nick early‑return block is not compiled and is omitted. isvalidnick(c) = char_atribs[(u_char)c] & NVALID (common_def.h:69; NVALID=0x40) → reproduced against the P1 crate::match_::char_atribs table; isdigit(*nick) reproduced as (*nick as u8).is_ascii_digit() (byte‑equivalent for the only true range 0x30‑0x39, and safe for high‑bit bytes that C's isdigit would pass UB‑ly).
    • format! per user request — N/A, documented in‑module. do_nick_name mutates the caller's nick buffer in place (*ch = '\0') — there is nothing to format and a UTF‑8 String could not reproduce the in‑place byte truncation. format! stays reserved for the ASCII/numeric reply cases.
    • Verification: L1 s_user_do_nick_name_diff zero‑diff vs cref_do_nick_name over 17 cases (valid nick server/client, leading -/digit, anonymous/ANONYMOUS casefold, embedded space/./~ truncation, [/]/`/|/} valid‑char retention, >15 length cap, empty, single char) — comparing both the returned length and the mutated buffer bytes. do_nick_name defined T from ircd‑common in the Rust ircd; full cargo test green (all *_diff L1 + layout + all golden suites; 0 failed); 0 warnings. No new golden scenario or canonicalizer mask — the existing registration banner already drives do_nick_name via the still‑C m_nick/register_user (a wrong port would diff the echoed nick). Deferred: the remaining s_user.c utilities (next_client/hunt_server/is_allowed) + the NICK/registration cluster (m_nick/m_unick/m_user/register_user/save_user/m_save/m_kill) stay in s_user_link.o (C) for later P5 sub‑phases.
  • 2026‑06‑03 — P5o (s_user.c is_allowed ACL gate) merged. is_allowed (s_user.c:3528 — the per‑client ACL gate: given an O‑line/service privilege bit return 1/0) ported into ircd-common/src/s_user.rs, extending the existing s_user_link.o partial port (one new #ifndef PORT_USER_P5 guard region around s_user.c:3523‑3564; the cref oracle keeps the full unguarded s_user.o). Utility / callee fragment (no msgtab row). RED confirmed via undefined is_allowed (the single symbol the new guard drops). Plan: docs/superpowers/plans/2026-06-03-p5o-s_user-is_allowed.md.

    • Cleanest L1‑differentiable callee — a pure function of its argument. is_allowed reads only cptr (fd/status/service->wants/the confs Link chain) and touches no globals and no hash tables, so the same constructed client is handed to both c_is_allowed (→ Rust) and cref_is_allowed (→ oracle) and equal returns are asserted — unlike next_client/hunt_server (the other remaining s_user utilities), whose lookups need a populated hash (separate‑globals problem deferred by P4 to S2S/L2). It shares no file‑statics with the C remnant (buf/buf2/user_modes untouched).
    • Seam — resolves the still‑C and the ported‑Rust callers. is_allowed is called from ported parse.rs (penalty checks at parse.rs:698/779) and still‑C m_kill/s_serv.c/s_conf.c/channel.c/res.c/hash.c/s_bsd.c. Removing it from parse.rs's ircd_sys::bindings import and use crate::s_user::is_allowed; makes the Rust caller resolve to the new local def; the C callers resolve at link once s_user.os_user_link.o drops the symbol (the canonize/send_away pattern). IsService/IsServer/MyConnect reproduced via the existing is_service_cl/is_server_cl/my_connect helpers; CONF_OPERATOR/ACL_TKLINE/ACL_KLINE/SERVICE_WANT_TKLINE/SERVICE_WANT_KLINE imported from bindings.
    • format! per user request — N/A, documented in‑module. is_allowed emits no wire output (returns 1/0); there is nothing to format.
    • L2 reachability — only oper KILL reaches it. The KILL msgtab row is [m_kill, m_nopriv, m_kill, m_nop, m_unreg], so a plain registered client (col 1) hits m_nopriv directly (parse sends 481 itself, is_allowed never runs); only an oper (col 2) reaches still‑C m_killis_allowed(sptr, ACL_KILL). The O‑line flags is_allowed walks select the branch: two oper boots with different O‑line grants (O|*@*|secret|alice|0|10|<flags>| — flags is field 7, after port+class) drive different numerics. (OPER_KILL/OPER_KILL_REMOTE defined → the K flag survives oline_flags_parse's gating.)
    • Verification: L1 s_user_is_allowed_diff zero‑diff vs cref_is_allowed over 11 cases (server, non‑MyConnect, service wants‑TKLINE/KLINE/none, no‑conf, non‑oper‑conf, CONF_OPERATOR grant‑all/grant‑some/grant‑none, oper‑second‑in‑chain). L2 golden_s_user_is_allowed 2/2 byte‑identical: grant (kill_grant.conf K flag → is_allowed=1 → oper KILL ghost proceeds → 401 ERR_NOSUCHNICK) and deny (kill_deny.conf T flag, no ACL_KILL → is_allowed=0 → m_nopriv481 Permission Denied). is_allowed defined T from ircd‑common in the Rust ircd; full cargo test green (all *_diff L1 + layout + all golden suites; 0 failed); 0 warnings. No new canonicalizer masks (no time tokens). Deferred: next_client/hunt_server (hash‑dependent → S2S/L2) + the NICK/registration handler cluster stay in s_user_link.o (C).
  • 2026‑06‑03 — P5p (s_user.c KILL handler) merged. m_kill (s_user.c:2512 — the KILL command) ported into ircd-common/src/s_user.rs, extending the existing s_user_link.o partial port (one new #ifndef PORT_USER_P5 region around s_user.c:2512‑2692; the cref oracle keeps the full unguarded s_user.o). Handler cluster (msgtab row [m_kill, m_nopriv, m_kill, m_nop, m_unreg] → reached by an oper (col 2) or a server (col 0); a plain client (col 1) hits m_nopriv). RED confirmed via undefined m_kill (the single symbol the guard drops); no parse.rs edit. Plan: docs/superpowers/plans/2026-06-03-p5p-s_user-m_kill.md.

    • Every callee already resolved → clean extraction. m_kill gates on the Rust is_allowed (P5o), resolves the victim via Rust find_uid/find_client/get_history (hash/whowas P2), and terminates in the Rust exit_client (P5c); m_nopriv is the Rust parse.rs stub (P4). Only the variadic senders (sendto_one/sendto_flag/sendto_serv_v/sendto_serv_butone/sendto_prefix_one) stay C. New bindings imports: get_history/m_nopriv/sendto_serv_v + consts ACL_KILL(6)/ACL_KILLREMOTE(4)/EXITC_KILL(75)/SV_UID(1)/ServerChannels_SCH_ERROR/SCH_KILL/FLAGS_KILLED/ERR_CANTKILLSERVER. Reuses the is_an_oper/is_oper/is_server/is_me/is_service_cl/my_connect/is_client/bad_to helpers.
    • Config (verified cbuild/config.h): UNIXPORT off → the IsUnixSocket(cptr) inpath branch not compiled → inpath = cptr->sockhost unconditionally; USE_SERVICES off → the check_services_butone(SERVICE_WANT_KILL,…) block not compiled/not ported; USE_SYSLOG/SYSLOG_KILL off → the syslog block not compiled. KILLCHASETIMELIMIT=90, TOPICLEN=255. The service‑target SCH_KILL branch and the isdigit(sid[0]) ? sid : "2.10" SID‑vs‑"2.10" fallback are ported faithfully.
    • format! per user request — N/A for the buffers (documented in‑module). m_kill's two sprintf targets (buf = "%s%s (%s)" killer+(L)?+comment; buf2 = "Local Kill by %s (%s)" / "Killed (%s)") interpolate arbitrary client‑supplied bytes (the KILL comment parv[2]/path and the index(path,' ') killer substring), so per the standing P5c/P5l rule a UTF‑8 String/format! would corrupt non‑ASCII bytes and can't reproduce the byte‑exact sprintf assembly → faithful byte‑builders (private module statics KILL_BUF/KILL_BUF2 replace the shared C buf/buf2, written only transiently within the call → the C remnant's buf/buf2 are untouched). The wire replies go straight to the C variadic senders (no intermediate string). No sub‑long integer reaches the senders → the P5c %lu‑width rule is N/A.
    • Faithfulness notes: the killer‑substring walk (while (killer > path && *killer != '!') killer--; if (killer != path) killer++) reproduced with raw‑pointer >/.offset(-1)/.add(1); path[TOPICLEN]='\0' oper‑overlong truncation; the chasing && !IsClient(cptr) echo‑back; acptr->flags |= FLAGS_KILLED set only inside the server‑propagation branch; exitc = EXITC_KILL only on the local‑oper‑kill path.
    • L2 is the gate — no clean L1 data op (m_kill ends in exit_client, which closes the connection). New multi‑client golden_s_user_kill 1/1 byte‑identical: oper alice (kill_grant.conf K flag) KILL bob :reason of a registered target → bob's victim transcript = the :alice!… KILL bob :<inpath>!alice (reason) notice (sendto_prefix_one, MyConnect path) + the ERROR :Closing Link: bob[…] (Local Kill by alice (reason)) from exit_client. Two local clients with no servers ⇒ the !MyConnect||!IsAnOper server‑burst branch is skipped (no sendto_serv_v/FLAGS_KILLED); the SCH_KILL oper notice goes to alice's socket, not bob's. The 401 (no‑such‑nick) grant + 481 deny branches are already covered by golden_s_user_is_allowed. Fully deterministic → no new canonicalizer masks. m_kill defined T from ircd‑common in the Rust ircd; full cargo test green (45 passed, 0 failed: all *_diff L1 + layout + all golden suites incl. the new kill scenario); 0 warnings. Deferred to S2S L2: the server‑sourced KILL, the sendto_serv_v SV_UID broadcast + get_history chase, and the server/service‑target ERR_CANTKILLSERVER paths (need a server link; ported faithfully, covered by review).
  • 2026‑06‑03 — P5q (s_user.c register_user — the registration core) merged. register_user (s_user.c:332‑846 — the central client‑registration routine: ident‑prefix username canonicalization, check_client/find_kill gating, STAT_CLIENT flip, user‑counter bumps, UID allocation, the welcome banner [001/002/003/004/005…/RPL_YOURID + LUSERS + MOTD], send_umode, and the leaf‑server UNICK feed) ported into ircd-common/src/s_user.rs, extending the existing s_user_link.o partial port (one new #ifndef PORT_USER_P5 region around s_user.c:332‑846; the cref oracle keeps the full unguarded s_user.o). Utility / callee (no msgtab row) — called from still‑C m_nick (1213)/m_user (2461)/m_unick (1446); defining it #[no_mangle] resolves all three C callers to the Rust def at link (the canonize/send_away/is_allowed seam). RED confirmed via the sole undefined register_user; no parse.rs edit. Plan: docs/superpowers/plans/2026-06-03-p5q-s_user-register_user.md.

    • L2 is the gate — the registration banner (the project's anchor scenario since P0‑L2) directly drives register_user through the still‑C m_nick/m_user. No clean L1 data op (side‑effect‑heavy: hash inserts, counters, banner sendto_one, exit_client; needs a live fd) → like P5e/g/i/k, RED = undefined symbol, GREEN = port, gate = golden_registration (registration_banner_matches_reference + pass_registration_banner_matches_reference) byte‑identical. A wrong port diffs the banner immediately.
    • Config‑resolved scope (verified cbuild/config.h): XLINE/RESTRICT_USERNAMES/UNIXPORT off → the X‑line conf walk, the isvalidusername check, and the IsUnixSocket host branch are not compiled/not ported. NO_PREFIX absent → the ~/^/+/-/= ident‑prefix logic is live (ported). USE_IAUTH on → the iauth early‑parse/XOPT_REQUIRED/null‑username block (incl. the static last/count → module statics IAUTH_LAST/IAUTH_COUNT) is compiled and ported faithfully (inert under ‑s since iauth_options==0). WHOISTLS_NOTICE/SPLIT_CONNECT_NOTICE defined → both NOTICEs ported (the split notice is dropped by the existing canonicalizer). USE_HOSTHASH/USE_IPHASH onadd_to_hostname/ip_hash_table ported. USE_SERVICES/CLIENTS_CHANNEL off → the services block (incl. the second send_umode+check_services_num) and the CONN client‑channel notice omitted.
    • Shared‑static hazard resolved. register_user writes the s_user.c file‑statics buf/buf2 only transiently within the call (the K‑line reason, the nick!user@host welcome string, the send_umode output read at the UNICK loop); the still‑C callers all return register_user(...) and never read them afterward, and register_user never reads a caller‑set value → private module statics REG_BUF/REG_BUF2 are faithful, the C remnant's buf/buf2 untouched (cf. P5l PASS_BUF, P5p KILL_BUF).
    • format! per user request — documented in‑module. The welcome nick!user@host string and the K‑lined: %.80s reason interpolate arbitrary client‑supplied C‑string bytes (validated nick / ident / resolved host) under byte‑precision copies; per the standing P5c/P5f rule a UTF‑8 String/format! would corrupt non‑ASCII bytes, so they stay faithful byte‑builders (push_cstr/store_cbuf). The username‑prefix assembly is byte‑precision strncpy. Every wire reply goes straight to a C variadic sender (no intermediate string). The P5c %lu‑width rule applied: every sub‑long int arg to the senders (the is_m_users/is_myclnt/is_l_myclnt u_long counters, fd, flags) is cast to its exact %‑width (%dc_int, %Xu_int). ⇒ format! is not "possible" here without losing byte‑faithfulness.
    • Faithfulness notes: the exit_msg[8] reject table + its index arithmetic (if i>-1 {i=-1}; i+=8; if i>8 {i=8}) reproduced verbatim (valid index for check_client ∈ [‑8,‑1]; a contract violation would panic in Rust vs OOB‑read in C — an acceptable divergence for an impossible input); bzero(passwd)write_bytes; aconf = sptr->confs->value.aconf via the SLink union; the leaf‑server UNICK loop walks fdas.fd[]/local[] (via local_base()); the (*buf)?buf:"+" UNICK umode arg. aClient.flags is c_long → all flag masks cast as c_long.
    • Verification: register_user defined T from ircd‑common in the Rust ircd (the still‑C m_nick/m_user/m_unick now call the Rust def); golden_registration 2/2 byte‑identical; full cargo test green (all *_diff L1 + layout + all golden suites, 0 failed); cargo build 0 warnings. No new canonicalizer masks. Deferred to S2S L2 / review: the !MyConnect UNICK‑source path, the leaf‑server UNICK feed, the iauth XOPT_REQUIRED/EARLYPARSE branches, and the K‑line/check_client rejection exits (need a server/iauth/matching‑conf link under ‑s; ported faithfully, covered by review + the banner regression). Remaining s_user.c (C): next_client/hunt_server (hash‑dependent → S2S/L2) + the m_nick/m_unick/m_user/save_user/m_save handlers stay in s_user_link.o.
  • 2026‑06‑03 — P5r (s_user.c m_user — the USER registration handler) merged. m_user (s_user.c:2337‑2469) ported into ircd-common/src/s_user.rs, extending the existing s_user_link.o partial port (one new #ifndef PORT_USER_P5 region around s_user.c:2337‑2470; the cref oracle keeps the full unguarded s_user.o). Handler cluster (msgtab row USER=[m_nop, m_reg, m_reg, m_nop, m_user] → only the unregistered column (4) reaches m_user; a registered client's USER hits the parse stub m_reg). RED confirmed via the sole undefined m_user; no parse.rs edit. Both wrappers it calls (make_user P2, register_user P5q, mystrdup/inetntop P1, get_client_host/exit_client P5c) already resolve to Rust. Plan: docs/superpowers/plans/2026-06-03-p5r-s_user-m_user.md.

    • L2 is the gate — the registration banner (the P0‑L2 anchor scenario) directly drives m_user. No clean L1 data op (side‑effect heavy: make_user, list reorder, mystrdup, then register_user/exit_client; needs a live fd) → like P5q, RED = undefined symbol, GREEN = port, gate = golden_registration byte‑identical. Its USER alice alice localhost :Alice line exercises make_user + the "no flags" umode‑parse path ('a' not a digit, not +/-what==0 → break) + the register_user tail‑call. A wrong port diffs the banner immediately.
    • Config‑resolved scope (verified cbuild/config.h): DEFAULT_INVISIBLE off (config.h:103 #undef) → no SetInvisible; XLINE off (config.h:460 #undef) → no user2/user3; REALLEN=50 (real‑name truncation live); USERLEN=10 (strncpyzt(user->username,…,USERLEN+1)); UFLAGS=FLAGS_INVISIBLE|FLAGS_WALLOP|FLAGS_RESTRICT=28; the function‑local umodes_arr[]={('i',INVISIBLE),('r',RESTRICT),('w',WALLOP)}. aConfItem.flags is c_longPFLAG_SERVERONLY/PFLAG_TLS cast as c_long; DefInfo is *const c_char → compared as *mut c_char.
    • No shared‑static hazard. m_user has no file‑statics of its own (umodes_arr function‑local; ipbuf a stack [c_char; BUFSIZE]) → the C remnant's shared buf/buf2/user_modes are untouched.
    • format! per user request — N/A, documented in‑module. There is no ASCII/numeric reply assembled in a buffer: the only string ops copy arbitrary client‑supplied C‑string bytes (username @‑truncation via strchr, real‑name info via mystrdup, username via strncpyzt) under byte‑precision truncation → direct libc copies, not format! (a UTF‑8 String would corrupt non‑ASCII bytes — the standing P5c/P5f/k rule); ipbuf is numeric ASCII from inetntop strcpy'd into user->sip; both wire emissions (the SCH_LOCAL server‑only notice via sendto_flag, the 462 reply via sendto_one) go straight to the C variadic senders.
    • Faithfulness notes: the umode +/- token loop reproduces C's switch{case '+'/'-':…continue; default:break;} then if(what==0)break as a labeled 'outer loop (the default break falls through to the what==0 check, the for‑loop break is break 'outer); the RFC‑bit branch (isdigit(*umodes) → scan rest digits → UFLAGS & atoi) ported via is_ascii_digit+atoi; the new is_unknown helper (status == STAT_UNKNOWN). (*me).serv->refcnt += 1; find_server_string((*me).serv->snum).
    • Verification: m_user defined T from ircd‑common in the Rust ircd (the still‑C m_reg/parse path now routes an unregistered USER to the Rust def); golden_registration byte‑identical; full cargo test green (40 test binaries, 0 failed: all *_diff L1 + layout + all golden suites); cargo build 0 warnings. No new canonicalizer masks. Deferred to S2S/review: the server‑only‑P‑line reject (find_bounce+exit_client), the TLS P‑line branch, the 462 re‑register reject (server‑introduced client), and the +/-/RFC‑bit umode paths beyond the banner's "no flags" case (ported faithfully, covered by review). Remaining s_user.c (C): next_client/hunt_server (hash‑dependent → S2S/L2) + m_nick/m_unick/save_user/m_save stay in s_user_link.o.
  • 2026‑06‑03 — P5s (s_user.c NICK cluster) merged. m_nick (s_user.c:855), m_unick (:1261), the file‑static save_user (:3470, the SAVE emitter), and m_save (:3506) ported into ircd-common/src/s_user.rs, extending the existing s_user_link.o partial port (two new #ifndef PORT_USER_P5 regions: m_nick+m_unick at :850‑1450, save_user+m_save at :3462‑3530; the cref oracle keeps the full unguarded s_user.o). Handler clusterm_nick/m_unick/m_save are msgtab rows; they form one connected component because all three call save_user. RED confirmed via undefined m_nick/m_unick/m_save (save_user is C static → inlined, never a link symbol). No parse.rs edit. This leaves only next_client/hunt_server in s_user_link.o (hash‑dependent → S2S/L2 per P4). Plan: docs/superpowers/plans/2026-06-03-p5s-s_user-nick.md.

    • goto control flow refactored into helper fns (faithful). m_nick has two interior labels reached by goto: badparamcountkills (the bad‑server‑NICK complaint, returns 0; reached on the server‑parc≠2 fall‑through, the sptr==cptr collision, and IsServer(sptr) in nickkilldone) and nickkilldone (the validated‑nick tail; reached from ~6 sites). Ported as two private fns — bad_param_count_kills(cptr,sptr,parc,parv) and nick_kill_done(cptr,sptr,nick,parc,parv) (the latter re‑calls the former for its IsServer(sptr) arm) — so every C goto X becomes return X(...). nick's mutated buffer (possibly rewritten to the UID via strncpyzt) is threaded through as a *mut c_char; user/host stay local to m_nick (only the nick‑collision/do_nick_name‑fail blocks use them, never nick_kill_done). The lp channel‑walk (sets the return 15 bigger‑penalty signal) lives entirely in nick_kill_done.
    • Config that shaped the port (verified cbuild/config.h): USE_SERVICES off → every check_services_butone block (m_nick nick‑change, save_user) omitted; CLIENTS_CHANNEL off → the SCH_CLIENT %s … NICK %s blocks in m_nick/save_user omitted; DISABLE_NICK0_REGISTRATION/DISABLE_NICKCHANGE_WHEN_BANNED absent → the #else (goto nickkilldone) / no‑op (no ERR_CANNOTSENDTOCHAN reject) arms taken. New consts: SIDLEN=4, DELAYCHASETIMELIMIT=1800, MODE_QUIET=0x2000, FLUSH_BUFFER=-2; new helpers my_person/is_conf_serveronly/is_quiet; strncmp/strncasecmp added to the local extern block; sendto_common_channels added as a variadic extern. New bindings imports: add_client_to_list/add_history/bootopt/check_uid/del_from_client_hash_table/find_history/make_client/BOOT_PROT/ERR_ERRONEOUSNICKNAME/ERR_NICKCOLLISION/ERR_NICKNAMEINUSE/ERR_UNAVAILRESOURCE/RPL_SAVENICK/ServerChannels_SCH_SAVE.
    • format! per user request — documented in‑module. The nick‑collision path strings (m_nick "(%s@%s[%s](%s) <- %s@%s[%s])" ×2, m_unick "(%s@%s)%s <- (%s@%s)%s") interpolate arbitrary client C‑string bytes (username/host/server names) under C sprintf → per the standing P5c/P5f/p rule a UTF‑8 String/format! would corrupt non‑ASCII bytes, so they're faithful byte‑builders (push_cstr into the private static NICK_PATH_BUF, which replaces m_nick/m_unick's char path[BUFSIZE] and badparamcountkills's char buf[BUFSIZE] — written only transiently within the call, so the C remnant's shared buf/buf2 are untouched). Every wire reply goes straight to a C variadic sender. No sub‑long int reaches a sender → the P5c %lu‑width rule is N/A.
    • Faithfulness notes: m_unick rebuilds a server‑introduced client exactly like m_user (make_clientadd_client_to_listmake_userservp/refcnt/servermystrdup(realname) capped at REALLEN→strncpyzt username/host→reorder_client_in_list→name/uid/sip set + hash inserts→m_umode(NULL,acptr,3,pv) with the stack pv[4]register_user); the (acptr->user)?…:"???" NULL‑guards on the both‑die nick‑collision report reproduced; bad_to(acptr->name) for the ERR_NICKCOLLISION target; cptr ? cptr->name : ME and the cptr ? '!' : ' ' separator in save_user's SAVE propagation; (*sptr).flags |= FLAGS_KILLED as c_long. The leading‑-/digit/anonymous rejects come from the already‑Rust do_nick_name (P5n); the SID‑nick‑prefix burn (strncasecmp(me.serv->sid, nick, SIDLEN)) and BadNick server‑KILL ported faithfully (S2S).
    • Verification: L2 golden_s_user_nick 3/3 byte‑identical: local nick change (registered alice NICK alice2sendto_common_channels echoes :alice!~alice@127.0.0.1 NICK :alice2 back to the sender — the nickkilldone "changing" branch: add_history, del/add client hash, strcpy(name)), erroneous nick (NICK -baddo_nick_name=0 → 432 ERR_ERRONEOUSNICKNAME), nick in use (two clients; bob NICK alice → find_client hit, !IsServer433 ERR_NICKNAMEINUSE). m_nick/m_unick/m_save defined T from ircd‑common in the Rust ircd; full cargo test green (all *_diff L1 + layout + all golden suites incl. the 3 new nick scenarios, 0 failed); cargo build 0 warnings. No new canonicalizer masks (no time tokens). Deferred to S2S L2: m_unick (UNICK arrives only from a server link), m_save + save_user's live SAVE emit (server‑sourced), and m_nick's server paths (SID‑nick burn, BadNick KILL, both‑SAVE collision) — ported faithfully, covered by review.
  • 2026‑06‑03 — P5t (channel.c leaf‑predicate foundation cluster) merged. is_chan_op (channel.c:577), has_voice (:593), find_channel (:716), clean_channelname (:2062) ported to ircd-common/src/channel.rs. First channel.c sub‑phase — establishes the channel_link.o partial‑port mechanism (‑DPORT_CHANNEL_P5 #ifndef‑guards the four C defs out of the link set; the cref oracle keeps the full unguarded channel.o). These are the only truly‑leaf channel.c exports (no file‑static, no shared buf/modebuf/parabuf/uparabuf use). Plan: docs/superpowers/plans/2026-06-03-p5t-channel-leaf-predicates.md.

    • channel_link.o wiring gotcha (cost one RED cycle): a partial port must NOT add channel.o to PORTEDlink_objs() filters PORTED entries before the channel.ochannel_link.o .map, so the substitution never fires and the whole TU goes undefined. The seam is: leave channel.o out of PORTED, add the channel.ochannel_link.o arm to link_objs(), and a plain sh -c compile step (channel.o's recipe carries no per‑object ‑D, unlike send/s_user, so no make --eval needed). After fixing: RED showed exactly the four leaf symbols undefined; GREEN resolves them T from ircd‑common.
    • Classification & seam: is_chan_op/has_voice are callee predicates, find_channel/clean_channelname callee utilities (none a msgtab row). All four are already imported by still‑C channel.c callers and the already‑Rust s_user.rs (clean_channelname/find_channel) + s_misc.rs (is_chan_op); defining them #[no_mangle] resolves every caller (C and Rust) to the Rust def at link (the canonize/send_away pattern). No parse.rs edit. find_user_link/hash_find_channel are the P2 Rust ports; my_connect/is_person/is_restricted inline helpers mirror s_user.rs. can_send deferred to the mode‑manipulation cluster (it calls the file‑static match_modeid). JAPANESE offclean_channelname's comma‑flag block isn't compiled (flag always 0); jp_*/get_channelmask absent.
    • format! per user request — N/A, documented in‑module. None of the four builds an ASCII/numeric reply string: clean_channelname mutates the caller's buffer in place, find_channel returns a pointer, is_chan_op/has_voice return ints. format! stays reserved for the reply‑building clusters.
    • Verification: L1 channel_leaf_diff zero‑diff vs cref_ (4 tests: is_chan_op over op/uniqop/voiced/non‑member/restricted‑on‑#‑vs‑&/remote + null‑chan; has_voice; find_channel empty‑name→arg + hash‑miss→NULL after inithashtables; clean_channelname over 10 strings comparing return + mutated bytes). L2 golden_channel 1/1 byte‑identical (registered alice JOIN #chan under ‑p standalone → JOIN echo + 353 with @alice [is_chan_op marks the creator chanop] + 366). Full cargo test green (0 failed); cargo build 0 warnings. No new canonicalizer masks. Deferred: has_voice has no JOIN/NAMES path under ‑s (L1‑only); the rest of channel.c (mode/join/display clusters, 5 components over the shared mode buffers) stays C in channel_link.o.
  • 2026‑06‑03 — P5 S2S golden coverage (the deferred server‑path harness) merged. Every P5 cluster above deferred its IsServer(cptr)/server‑reachable paths to "an S2S scenario" because the standalone (‑s) client harness can never present a linked peer. This adds that peer. New ircd-golden primitives (committed first, then a 12‑agent ultracode workflow authored one test per cluster, each probing both binaries before writing): boot_s2s + a Peer fake‑server that completes the IRCnet 2.11 server handshake (PASS linkpass 0211030000 IRC|aHmM / SERVER peer.test 1 001B … → drains the burst through EOB) and then speaks the SID/UID‑prefixed server protocol; the s2s.conf fixture (M/A/P/Y/I + C/N/H lines authorizing an inbound link from 127.0.0.1, name peer.test, pass linkpass). 13 golden_s2s_* scenarios, all byte‑identical ref‑C == Rust after canonicalize() (full golden suite 51/51, 0 warnings).

    • Foundation findings (verified empirically, both binaries): the handshake reply + burst is byte‑identical; local UIDs are deterministic & sequential (first local user = 000AAAAAA; curr_cid starts at 0, not wall‑clock) so no UID masking is needed; SID format is 4 chars, first a digit (sid_valid, s_id.c) — peer SID 001B, server SID 000A; ‑p standalone refuses all links ("Running in standalone mode") so S2S boots use plain ‑t ‑s (server starts in split‑mode until the peer links, which canonicalize() already handles via the split‑mode NOTICE drop). No new canonicalizer masks were required by any scenario.
    • golden_s2s_link (P5s m_unick + P5d m_whois): peer links, a local client rides the burst as :000A UNICK alice 000AAAAAA …, then the peer introduces remote bob (001BAAAAA) via UNICK and the local client WHOIS bob311 … peer.test / 312 / 318. The foundation proof.
    • golden_s2s_message (P5f): bidirectional PRIVMSG through the ported m_message/m_private core. remote→local: :001BAAAAA PRIVMSG alice :… → alice gets :bob!~bob@remote.host PRIVMSG alice :… (sendto_prefix_one full‑mask prefix). local→remote: PRIVMSG bob routes out the peer link. Correction to the scenario brief: under the negotiated caps the on‑wire form is name‑prefixed (:alice PRIVMSG bob :…), not UID‑prefixed/UID‑targeted — the test asserts the actual faithful bytes.
    • golden_s2s_away (P5g send_away): peer sends :001BAAAAA AWAY :gone fishing; alice PRIVMSGes + WHOISes bob, hitting send_away from the m_message and m_whois remote‑target branches. Faithful subtlety pinned: m_away stores user->away only under MyConnect(sptr), so a remote user's away text is never stored here → send_away takes its else branch and emits the generic 301 alice bob :Gone, for more info use WHOIS bob bob (NOT the away text). Peer gets no echo of its own AWAY.
    • golden_s2s_umode (P5h send_umode_out/send_umode): alice (in the burst) sends MODE alice +i → client echo :alice MODE alice :+i (ALL_UMODES local branch) and link propagation :000AAAAAA MODE alice :+i (SEND_UMODES, source=uid/target=name). Order‑sensitive: alice must register before Peer::link().
    • golden_s2s_quit (P5k m_quit + exit_one_client): local QUIT :leaving → peer receives :000AAAAAA QUIT :"leaving". Pins two faithful details: local QUIT reason is wrapped in literal double‑quotes by m_quit (the sentinel exit_one_client uses to classify a genuine QUIT), and the propagation source is the UID, not the nick.
    • golden_s2s_kill (P5p m_kill IsServer(cptr) branch): peer sends :001B KILL 000AAAAAA :peer.test (spam) → victim sees :peer.test KILL alice :peer.test!peer.test (spam) then ERROR :Closing Link: alice[~alice@127.0.0.1] peer.test (Killed (peer.test (spam))) (the Killed (<killer>) branch, not Local Kill by); socket close confirmed on both binaries.
    • golden_s2s_squit (P5c exit_clientexit_server): introduce remote bob, confirm live WHOIS, drop the peer TCP socket (netsplit) → after settle, WHOIS bob401 … :No such nick/channel / 318 on both binaries (the recursive reap of dependents behind the dead link).
    • golden_s2s_nick (P5s m_nick server branch): peer introduces bob then sends :001BAAAAA NICK :newbob; WHOIS newbob311 … peer.test, WHOIS bob401 (the sendto_serv_butone(":%s NICK :%s") propagation + del/add client‑hash retarget).
    • golden_s2s_save (P5s m_save/save_user): peer sends :001B SAVE 000AAAAAA :savepath → alice gets :peer.test 043 alice 000AAAAAA :nickname collision, forcing nick change to your unique ID. then :alice!~alice@127.0.0.1 NICK :000AAAAAA (renamed to her UID). Deterministic — UID stable, SAVE path a fixed literal.
    • golden_s2s_userhost (P5e): remote bobUSERHOST bob302 alice :bob=+~bob@remote.host (+ not away, no * not oper); ISON alice bob303 alice :alice bob (trailing space per the C loop).
    • golden_s2s_who (P5d who_one classic‑352): WHO bob:irc.test 352 alice * ~bob remote.host peer.test bob H :1 001B Bob Remote (the server/hop/sid trailer that only differs for a remote target) then 315 … bob :End of WHO list. (mask keyed on the literal bob, not *).
    • golden_s2s_register_feed (P5q): peer links and drains the burst before any local client exists (burst has no UNICK), then a fresh local carol registers → peer receives :000A UNICK carol 000AAAAAA ~carol 127.0.0.1 127.0.0.1 + :carol (the register_user leaf‑server feed loop, unreachable by the standalone harness).
    • golden_s2s_service (P5a m_service/m_squery): peer introduces a remote service via :001B SERVICE chanserv@peer.test * 0 :Channel Service (the IsServer(cptr) introduce branch; USE_SERVICES off so the client SERVICE path is rejected — only the common tail runs), then local SQUERY chanserv@peer.test :hello there routes out the link as :alice SQUERY chanserv@peer.test :hello there (remote service → not MyConnect → final routing branch).
    • Verification: full cargo test -p ircd-golden = 51 passed / 0 failed (the 13 S2S scenarios + all prior golden + L1/layout suites); cargo build 0 warnings. The S2S harness lives entirely in ircd-golden/src/lib.rs (Peer, boot_s2s) + ircd-golden/fixtures/s2s.conf — no change to any ported ircd-common source (these tests characterize the already‑merged ports, they don't re‑port). Still S2S‑deferred (no test yet): multi‑hop server‑burst diffing through a TS‑aware peer, channel NJOIN/MODE burst (channel.c still mostly C), and m_ping/m_pong server‑col paths (P5i) — left for the channel mode‑cluster + a future burst harness.
  • 2026‑06‑03 — P5u (channel.c invite cluster) merged. m_invite (channel.c:3260), the file‑statics add_invite (:2176) + list_length (:85), check_channelmask (:2100), and the exported del_invite (:2233) ported to ircd-common/src/channel.rs, extending the existing channel_link.o partial port (five new #ifndef PORT_CHANNEL_P5 regions; the cref oracle keeps the full unguarded channel.o). Second channel.c sub‑phase. Plan: docs/superpowers/plans/2026-06-03-p5u-channel-invite.md.

    • Cluster = a closed connected component over static‑function call edges. Coupling in channel.c is only via static functions called across the C/Rust boundary (static variables like buf/modebuf used transiently can be duplicated — established P5 practice). Verified edges: list_length→only add_invite; add_invite→only m_invite; del_invite non‑static (no linkage issue); check_channelmask10 C callers (m_join/m_part/m_kick/m_topic/m_njoin/set_mode/send_channel_modes/send_channel_members + m_invite; the JAPANESE site is #if 0). So the cluster is {m_invite, add_invite, list_length, del_invite, check_channelmask} — every static is called only by cluster members except check_channelmask, handled by the extern‑prototype switch below.
    • Keystone mechanism — static function with remaining C callers → extern‑prototype switch. check_channelmask is a file‑static whose definition we drop but whose callers mostly stay C. A static symbol has internal linkage → the C remnant's call can't resolve to the Rust #[no_mangle] def. Fix: guard the forward prototype (channel.c:55) #ifndef PORT_CHANNEL_P5 static int check_channelmask(...) #else extern int check_channelmask(...) #endif — so under ‑DPORT_CHANNEL_P5 the C remnant references it as an external symbol resolved to the Rust def at link. Linkage‑only change, runtime‑faithful. (This is the general recipe for every future channel.c cluster that drops a shared static — e.g. get_channel, find_chasing.) del_invite (already exported) now also resolves the still‑C free_channel/m_kick/m_njoin callers + the already‑Rust s_misc.rs exit‑cascade caller to the Rust def (the canonize/send_away seam). No parse.rs edit (m_invite resolves from the bindings import once the C def drops).
    • Config (verified cbuild/config.h): JAPANESE offm_invite's #ifdef JAPANESE jp_valid block not compiled/not ported; get_channelmask(x) is the macro rindex(x, ':') → in check_channelmask it is just libc strrchr, no static dragged in. USE_SERVICES off (no check_services_* in this cluster). MAXCHANNELSPERUSER=21 (config.h:505) — bindgen drops the macro → hardcoded as a resolved literal in add_invite.
    • Faithfulness notes: del_invite/add_invite reproduce the original's crossed istat bookkeeping verbatim — add_invite channel‑side does is_useri++, client‑side is_banmem += len/is_invite++; del_invite channel‑side is_invite‑‑, client‑side is_banmem ‑= strlen(who)+1/is_useri‑‑ (note the original's += len vs ‑= len+1 off‑by‑one is kept). The who string ("<nick>!<user>@<host>") is built into a [u8;91] (NICKLEN+USERLEN+HOSTLEN+3) then MyMalloc+strcpy'd, byte‑exact. m_invite mirrors the two chptr ? chptr->chname : parv2 ternaries and the MyConnect(acptr) && chptr && sptr->user && is_chan_op add_invite guard.
    • format! per user request — N/A, documented in‑module. add_invite's who interpolates arbitrary client‑supplied C‑string bytes → byte‑builder, not format! (a UTF‑8 String would corrupt non‑ASCII bytes); every reply goes straight to a C variadic sender (sendto_one/sendto_prefix_one); no sub‑long int reaches a sender → the P5c %lu‑width rule is N/A.
    • L1 vs L2 split — statics aren't L1‑differentiable. add_invite/list_length/check_channelmask are C file‑statics → the cref_ prefix‑rename keeps them local, so there is no cref_* oracle symbol to diff against (the first P5u attempt hit undefined symbol: cref_check_channelmask at link). Per the established practice they are covered by L2 instead. del_invite (exported) is the lone L1 target: channel_invite_diff builds parallel chptr->invites (Link) + cptr->user->invited (invLink, with libc‑malloc'd who) chains, removes the middle element on the Rust port (c_del_invite) and the oracle (cref_del_invite), and asserts identical chain relink (marker sequence) + identical is_invite/is_useri/is_banmem deltas (the two tests serialize on a Mutex since they mutate the process‑global istat/cref_istat).
    • Verification: L1 channel_invite_diff 2/2 zero‑diff vs cref_del_invite. L2 golden_channel_invite 4/4 byte‑identical: success (alice chanop of #chan INVITE bob #chan341 RPL_INVITING to alice + :alice!~alice@127.0.0.1 INVITE bob :#chan to bob — the only path that fires the private add_invite/list_length live), 401 (INVITE ghost → find_person miss), 442 (alice not a member of bob's #other), 476 (check_channelmask reject on #chan:nomatch.zzz + the MyClient ERR_BADCHANMASK send). m_invite/del_invite/check_channelmask defined T from ircd‑common in the Rust ircd; full cargo test green; cargo build 0 warnings. No new canonicalizer masks (no time tokens). Deferred to S2S L2: the !chptr remote‑INVITE forward (sendto_prefix_one to a remote acptr), the &‑channel + !MyClient(acptr) reject, and check_channelmask's server/chan:mask propagation branches (need a server link; ported faithfully, covered by review). Remaining channel.c (C): the mode (m_mode/set_mode/match_modeid/…), join (m_join/m_njoin/can_join/get_channel/add_user_to_channel), part/kick/topic, and display (m_names/m_list/names_channel/find_chasing) clusters stay in channel_link.o.
  • 2026‑06‑03 — P5v (channel.c can_send + match_modeid cluster) merged. can_send (channel.c:615 — the channel send‑permission predicate, +m/+n/+b/+e) and match_modeid (channel.c:314 — the ban/exception mlist matcher) ported to ircd-common/src/channel.rs, extending the existing channel_link.o partial port (three new #ifndef PORT_CHANNEL_P5 regions: the match_modeid forward proto switch at :75, the match_modeid body, the can_send body; the cref oracle keeps the full unguarded channel.o). Third channel.c sub‑phase. Completes the P5t deferral ("can_send deferred to the mode cluster — it calls the static match_modeid"). Plan: docs/superpowers/plans/2026-06-03-p5v-channel-can_send.md.

    • Cluster = a closed connected component over static call edges. can_send (exported, channel_ext.h) calls only IsMember/find_user_link/match_modeid; match_modeid (static) calls only match/match_ipmask/macros — no other channel.c static. match_modeid's other callers (can_join, reop_channel) stay C → handled by the P5u extern‑prototype switch (the forward staticextern at channel.c:75 under the guard, so the C remnant resolves to the Rust #[no_mangle] def at link; linkage‑only, faithful). can_send is already imported by the Rust m_message (P5f, s_user.rs:1155) + the WHO path (s_user.rs:3439) from ircd_sys::bindings → those bindgen decls resolve to the Rust def once the C def drops (the is_chan_op/has_voice P5t seam — no s_user.rs edit needed, unlike the plan's tentative Step 6 which would have diverged from established practice). No parse.rs edit (neither is a msgtab row).
    • Classification → L1/L2 split. can_send is exported → cref_can_send exists → L1‑differentiable (the workhorse). match_modeid is a C file‑static → the cref_ prefix rename keeps it local (no cref_match_modeid oracle symbol, cf. P5u check_channelmask) → not L1‑differentiable in isolation; its empty‑mlist NULL‑return is exercised via can_send's ban path in L1, and the populated +b match is L2/review‑covered.
    • Keystone finding — a latent P5u is_member faithfulness bug, fixed here. The is_member helper (added in P5u for m_invite) cited the commented‑out struct_def.h:773 (find_user_link(c->members, u)). The active IsMember (struct_def.h:775, lines 771‑773 are inside a /* */ block) is u && u->user && find_channel_link(u->user->channel, c) — it walks the client's channel list, not the channel's member list. The L1 differential surfaced it immediately (the "+n member" case: Rust found the member via chptr->members, the oracle didn't via the empty user->channel0 vs 256). Fixed is_member to find_channel_link (matching the active macro); the P5u golden_channel/golden_channel_invite + channel_invite_diff/channel_leaf_diff all still pass (the bug wasn't triggered by the invite scenarios, which link members both ways). The L1 fixture now links a real member into both chptr->members (→ lp) and who->user->channel (→ IsMember).
    • Config (verified cbuild/config.h): JAPANESE offcan_send has no JP branch. MODE_MODERATED=32/MODE_NOPRIVMSGS=256/MODE_BAN=1024/CHFL_BAN=8/CHFL_EXCEPTION=16 (bindgen consts); (*chptr).mode.mode is u_int; aConfItem.flags is c_long (cast CFLAG_NORESOLVEMATCH as c_long). New helpers is_an_oper/is_conf_no_res_match mirror s_user.rs; match_ipmask/find_channel_link/aConfItem/Link + the mode/flag consts added to the bindings import; strchr added to the local extern block.
    • Faithfulness notes: match_modeid reproduces the for‑loop post‑continue advance (tmp = tmp->next; continue; at each continue site, the natural advance at the bottom for the flags‑mismatch fall‑through), the isdigit(nick[0]) UID‑ban fallback, the n!u→host/sockhost/sip/CIDR cascade, and the oper conf‑chain scroll (acf = acf->next when IsAnOper); arrays (username/uid/host/sip/sockhost) passed via addr_of_mut!, name/alist->nick are pointers. can_send computes member + lp unconditionally, returns 0 for !MyConnect, and the +be ban path only when the sender is a non‑op/non‑voice member.
    • format! per user request — N/A, documented in‑module. Both return ints; no string is assembled. format! stays reserved for the reply‑building clusters.
    • Verification: L1 channel_can_send_diff zero‑diff vs cref_can_send over 8 cases (+m non‑voiced/voiced/chanop member + non‑member, +n member/non‑member, plain‑member empty‑mlist ban path, remote !MyConnect bypass). L2 golden_channel_can_send 2/2 byte‑identical: +m moderated (alice chanop sets +m, bob joins as a plain member → bob PRIVMSG → 404 ERR_CANNOTSENDTOCHAN via the Rust m_message→Rust can_send MODE_MODERATED branch) and +n no‑external (bob non‑member → MODE_NOPRIVMSGS branch); read‑barriers (366/MODE echoes) serialize the cross‑connection ordering so +m/+n is in effect before bob sends. can_send/match_modeid defined T from ircd‑common in the Rust ircd; full cargo test green (0 failed); cargo build 0 warnings. No new canonicalizer masks (no time tokens). Deferred to S2S L2 / review: match_modeid's populated‑mlist +b/+e match (incl. the UID‑ban and CIDR match_ipmask paths) and the remote‑client can_send bypass under a server link (ported faithfully). Remaining channel.c (C): the mode (m_mode/set_mode/add_modeid/del_modeid/send_mode_list/change_chan_flag/make_bei/free_bei/channel_modes/send_channel_*), join (m_join/m_njoin/can_join/get_channel/add_user_to_channel/reop_channel/free_channel), part/kick/topic, and display (m_names/m_list/names_channel/find_chasing/check_string) clusters stay in channel_link.o.
  • 2026‑06‑03 — P5w (channel.c channel_modes leaf) merged. channel_modes (channel.c:831 — the "simple" channel mode‑string serializer: writes the +‑flags into the caller's mbuf and the +l/+k parameters into pbuf) ported to ircd-common/src/channel.rs, extending the existing channel_link.o partial port (one new #ifndef PORT_CHANNEL_P5 region around the body; the cref oracle keeps the full unguarded channel.o). Fourth channel.c sub‑phase. Plan: docs/superpowers/plans/2026-06-03-p5w-channel-channel_modes.md.

    • Classification: exported true‑leaf callee (no msgtab row). channel_modes touches no file‑static — it writes into the caller‑provided mbuf/pbuf — and calls only the IsMember/IsServer macros + libc sprintf/strcat. So a plain #ifndef guard drops the exported def and the still‑C callers resolve to the Rust def at link: the m_mode query path (channel.c:1112, MODE #chan with parc < 3channel_modes then replies[RPL_CHANNELMODEIS]), set_mode (mode‑change echo), and send_channel_modes (the S2S burst — the symbol that referenced channel_modes at link, confirming RED). The #ifdef USE_SERVICES m_join call (channel.c:1987) is not compiled. No parse.rs / build.rs edit.
    • Config findings (verified): the body (831‑866) has no #ifdef inside it — the full bit cascade compiles verbatim. nm cbuild/channel.o shows T channel_modes (exported). JAPANESE/USE_SERVICES off — neither affects this fn. SMode { mode: u_int, limit: c_int, key: [c_char;24] }; mode consts present in bindings (MODE_SECRET=16/MODE_PRIVATE=8/MODE_MODERATED=32/MODE_TOPICLIMIT=64/MODE_INVITEONLY=128/MODE_NOPRIVMSGS=256/MODE_ANONYMOUS=4096/MODE_QUIET=8192/MODE_REOP=65536) — added the six missing ones + strcat/variadic sprintf externs to channel.rs.
    • Faithfulness notes: the + prefix then each bit in exact C order (s else p; m; t; i; n; a; q; r), then l (sprintf(pbuf,"%d ",limit) if IsMember||IsServer), then k (strcat(pbuf, key) if IsMember||IsServer), then the terminator. The C *mbuf++ = '\0' post‑advance is dead (mbuf never re‑read) → written in place to avoid a spurious unused_assignments warning (behaviorally identical). IsMember = the active find_channel_link(cptr->user->channel, chptr) (the P5v‑corrected helper); IsServer = is_server. format! per user request — N/A, documented in‑module: the +l/+k params must match C sprintf("%d ")/strcat byte‑for‑byte into the caller's raw char* buffer; format! would change framing. Kept as the libc calls.
    • Verification: L1 channel_modes_diff zero‑diff vs cref_channel_modes over 17 cases (empty +, every single bit, secret‑wins‑over‑private precedence, +ntl/+nk/+tlk params for a server cptr, the same +tlk for a non‑member [params suppressed → empty pbuf], and the all‑bits+l+k full house) — both the mbuf and pbuf byte buffers asserted identical. L2 golden_channel_modes 1/1 byte‑identical: alice (chanop → member) sets +tnl 5 then +k secret, then the MODE #chan query → 324 RPL_CHANNELMODEIS carrying the mode string + the +l 5 (sprintf) and +k secret (strcat) params. channel_modes defined T from ircd‑common in the Rust ircd; cargo build 0 warnings; no new canonicalizer masks (no time tokens). Remaining channel.c (C): the rest of the mode cluster (m_mode/set_mode/add_modeid/del_modeid/send_mode_list/change_chan_flag/make_bei/free_bei/send_channel_*), join, part/kick/topic, and display clusters stay in channel_link.o.
  • 2026‑06‑03 — P5x (channel.c membership cluster) merged. add_user_to_channel (channel.c:424), remove_user_from_channel (:493, exported), and change_chan_flag (:564) ported to ircd-common/src/channel.rs, extending the existing channel_link.o partial port (‑DPORT_CHANNEL_P5). Fifth channel.c sub‑phase — the members/clist/user->channel list manipulators + the istat channel counters. Plan: docs/superpowers/plans/2026-06-03-p5x-channel-membership.md.

    • Cluster shape & the linkage moves. The three touch no shared buf/modebuf/parabuf/uparabuf static (unlike the mode/display clusters) — their only remaining‑C edge is the static free_channel, which remove_user_from_channel calls when the last user leaves. Two statics (add_user_to_channel, change_chan_flag) have no forward decl in C (defined before their callers), so guarding their bodies out would leave the C remnant (setup_server_channels/m_join/m_njoin; set_mode/reop_channel) calling an undeclared symbol → added extern forward decls under the guard. free_channel is kept C but switched staticextern under #ifdef PORT_CHANNEL_P5 (forward decl :71 + definition storage class) — a new variant of the P5u mechanism (P5u/P5v switched a C static so the Rust def wins; here the body stays C and only its linkage opens so the Rust caller can reach it). remove_user_from_channel is exported and already called from the Rust s_misc.rs exit cascade (P5c, extern decl s_misc.rs:65) → resolves to the Rust def once the C def drops. No parse.rs / build.rs edit.
    • Config findings (verified): nm cbuild/channel.ot add_user_to_channel, t change_chan_flag, T remove_user_from_channel, t free_channel, t free_bei (confirms the static/export split). USE_SERVICES off → the check_services_butone calls inside add/remove (channel.c:460‑470, :540‑550) are #ifdef'd out (skipped). Active MODE_ADD = 0x40000000 (struct_def.h:877; the :756‑757 pair is commented out), MODE_FLAGS = 0x3ffff, LDELAYCHASETIMELIMIT = 5400, CHFL_UNIQOP/CHANOP/VOICE = 0x1/0x2/0x4. RED build confirmed exactly the three undefined symbols (free_channel resolved as C).
    • Faithfulness notes: add_user_to_channelchptr->users++ == 0 is the unconditional post‑increment (was_zero capture then += 1); the locked‑channel history reset bzero(&chptr->mode, sizeof(Mode))ptr::write_bytes(addr_of_mut!((*chptr).mode) as *mut u8, 0, size_of::<Mode>()) gated on *chname != '!'; the per‑uplink clist link found/created on who->from then flags++. remove_user_from_channel — a leaving CHFL_CHANOP arms chptr->reop = timeofday + LDELAYCHASETIMELIMIT + myrand()%300 (the L1 fixture uses non‑chanop members so this non‑deterministic myrand() path is not exercised — it would diverge Rust's live myrand from the oracle's cref_myrand); if (tmp2 && !--tmp2->flags) predecrement drops the clist link at 0; --chptr->users <= 0 → the is_chan/is_chanmem/is_hchan/is_hchanmem swap + free_channel. change_chan_flag — mirrors the flag into both the member link and the client link, ~lp->flags & MODE_FLAGS = (!lpflags) & MODE_FLAGS (unary‑not binds tighter, same as C). format! per user request — N/A, documented in‑module: the cluster builds no reply string (pure list/counter mutation).
    • Verification. L1 channel_membership_diff zero‑diff vs cref_remove_user_from_channel: a 2‑member channel with both members linked into chptr->members + each who->user->channel + a per‑uplink clist entry; removing one member asserts identical surviving members/clist/user->channel chains, chptr->users, joined--, and the is_userc/is_chanusers deltas — deliberately keeping users >= 1 so the last‑user free_channel teardown (needs the channel hash + global) is not L1‑reached (L2/review‑covered). Inverse/round‑trip invariant (add_remove_readd_roundtrip_is_clean, the skill's required inverse test): drives the now‑Rust add_user_to_channel directly (a C static → no cref_ oracle) to add two members, remove one and assert it is actually gone from members/clist/user->channel with the counters decremented, then re‑add and assert no duplicate node + the is_userc/is_chanusers counters return exactly to the two‑member values (a botched removal passes the positive‑only differential but leaks/double‑counts here). change_chan_flag is a C static → no cref_ oracle → covered by L2. L2 (all byte‑identical ref‑C == Rust, 0 new canonicalizer masks): golden_channel_part — (1) PART with a second member (bob) staying on #chan to observe alice's relayed PART, then a follow‑up NAMES + WHO confirming alice is gone from both membership lists (remove_user_from_channel); (2) MODE +o → NAMES showing @bob (change_chan_flag set the flag, is_chan_op reads it). golden_channel (JOIN) still covers add_user_to_channel. S2S golden_s2s_membership — a remote NJOIN (m_njoin C → Rust add_user_to_channel; the server‑side get_channel(&me, CREATE) creates the federated #chan a split lone link won't let a local client create) then a local observer JOINs and sees @bob, then a remote PART (m_part C → Rust remove_user_from_channel) the observer sees + a NAMES no longer listing bob. cargo build 0 warnings; full cargo test green (41 test binaries incl. the new channel_membership_diff L1 + golden_channel_part/golden_s2s_membership L2). Split‑mode finding: in boot_s2s a single configured uplink keeps the server permanently split (IsSplit()), so a local client cannot create a federated # channel (m_join:2591 ERR_UNAVAILRESOURCE) — but can JOIN an existing one (the split guard is inside if(!chptr)); the NJOIN server path creates it. Remaining channel.c (C): the mode cluster (m_mode/set_mode/add_modeid/del_modeid/send_mode_list/make_bei/free_bei/send_channel_*), join/access (can_join/get_channel/free_channel/reop_channel), part/kick/topic, and display (names_channel/find_chasing/check_string/m_names/m_list) clusters stay in channel_link.o.
  • 2026‑06‑03 — P5y (channel.c m_topic handler) merged. m_topic (channel.c:3208‑3300 — the TOPIC command: per‑channel query [331/332/333] or set [propagate + 482 reject]) ported to ircd-common/src/channel.rs, extending the existing channel_link.o partial port (one #ifndef PORT_CHANNEL_P5 region around the body; the cref oracle keeps the full unguarded channel.o). Sixth channel.c sub‑phase. Plan: docs/superpowers/plans/2026-06-03-p5y-channel-m_topic.md.

    • The cleanest channel.c handler — no linkage juggling. m_topic is a msgtab row (TOPIC = [m_nop, m_topic, m_topic, m_nop, m_unreg] → registered client col 1, server col 0/2) and has no file‑static of its own (it uses find_channel, not get_channel, so the get_channel linkage problem the rest of channel.c carries doesn't apply). Every callee is already Rust — canonize (P5m), strtoken (P1), find_channel (P5t), check_channelmask (P5u), is_chan_op (P5t) — or a C variadic sender. So a plain #ifndef guard drops the C def and the m_topic decl parse.rs already imports from ircd_sys::bindings resolves to the Rust def at link. No extern‑prototype switch, no parse.rs/build.rs edit. RED confirmed via the sole undefined m_topic.
    • Config (verified cbuild/config.h): TOPIC_WHO_TIME defined (config.h:522) → the topic_t/topic_nuh blocks (the 333 RPL_TOPIC_WHO_TIME reply on query, the topic_nuh = "%s!%s@%s" build + topic_t = timeofday on set) are compiled and ported. USE_SERVICES off → the check_services_butone(SERVICE_WANT_TOPIC, …) block not compiled/not ported. JAPANESE off (no effect). Numerics: RPL_NOTOPIC=331, RPL_TOPIC=332, RPL_TOPIC_WHO_TIME=333, ERR_NOSUCHCHANNEL=403, ERR_NOTONCHANNEL=442, ERR_NOCHANMODES=477, ERR_CHANOPRIVSNEEDED=482. Fields (bindgen): topic[256], topic_nuh[92], topic_t (time_t). New helpers: is_channel_name (cid_ok, CHIDLEN=5), use_modes, is_anonymous, strncpyzt; new bindings imports canonize/strtoken/cid_ok/RPL_NOTOPIC/RPL_TOPIC/RPL_TOPIC_WHO_TIME/ERR_NOCHANMODES/ERR_NOSUCHCHANNEL; new externs sendto_match_servs/sendto_channel_butserv/strncpy.
    • format! per user request — N/A, documented in‑module. topic_nuh ("%s!%s@%s" of the setter's arbitrary client name/username/host C‑strings) is a byte‑builder (build_nuh, mirroring the add_invite who builder) — not sprintf/format! (a UTF‑8 String would corrupt non‑ASCII bytes; the standing P5c/P5f rule). topic is a byte‑precision strncpyzt. Every wire reply (the TOPIC echo, 332/333, the rejects) goes straight to a C variadic sender. The 333 topic_t (time_t) passed to %lu is cast to u_long (the P5c width rule).
    • Faithfulness notes: the strtoken(&p, parv[1], ",") per‑target loop with parv[1]=NULL advance; the find_channel miss does return penalty (not continue); IsChannelName reject → 442, !UseModes → 477, !IsMember → 442, check_channelmask → continue; query (topic==NULL) → empty topic → 331, else 332 + (if topic_t > 0) 333 with IsAnonymous(chptr) ? "anonymous!anonymous@anonymous." : topic_nuh; set (parc > 2) only if !(mode & MODE_TOPICLIMIT) || is_chan_op → strncpyzt topic, build topic_nuh, topic_t = timeofday, propagate (sendto_match_servs UID‑sourced + sendto_channel_butserv name‑sourced), penalty += 2; else → 482.
    • L2 is the gate (no clean L1 data op — m_topic sends wire output via a live channel). L2 golden_channel_topic 2/2 byte‑identical (-p standalone so #chan can be created): lifecycle (query empty → 331; set → echo; query → 332 + 333; change → re‑query 332 reflects the new value — the round‑trip/inverse invariant a botched set/strncpyzt would break) + rejects (442 non‑member, 403 no‑such‑channel, 482 non‑op on +t). S2S golden_s2s_topic 1/1 byte‑identical (the user‑requested server path): a remote chanop bob (NJOIN'd onto a federated #chan) sets the topic → m_topic (cptr = peer server, sptr = bob) relays :bob TOPIC #chan :… to local member alice via sendto_channel_butserv and her query returns 333 carrying bob's topic_nuh (the remote‑user fields only an S2S link produces); then local alice sets the topic → sendto_match_servs propagates :000AAAAAA TOPIC #chan :… (UID‑sourced) to the peer. New canonicalizer mask: 333 RPL_TOPIC_WHO_TIME's trailing topic_t (= timeofday) is wall‑clock volatile between the separately‑booted ref‑C/Rust → masked to <T>. m_topic defined T from ircd‑common in the Rust ircd; cargo build 0 warnings. Remaining channel.c (C): the mode cluster (m_mode/set_mode/add_modeid/del_modeid/send_mode_list/make_bei/free_bei/send_channel_*), join/access (m_join/m_njoin/can_join/get_channel/free_channel/reop_channel), part/kick (m_part/m_kick/find_chasing), and display (m_names/m_list/names_channel/check_string) clusters stay in channel_link.o.
  • 2026‑06‑03 — P5z (channel.c m_list handler) merged. m_list (channel.c:3402‑3542 — the LIST command: bare lists all visible channels, or per‑name/!‑mask forms) ported to ircd-common/src/channel.rs, extending the existing channel_link.o partial port (one #ifndef PORT_CHANNEL_P5 region around the body; the cref oracle keeps the full unguarded channel.o). Seventh channel.c sub‑phase. Plan: docs/superpowers/plans/2026-06-03-p5z-channel-m_list.md.

    • A second true‑leaf handler (like P5y). m_list is a msgtab row (LIST = [m_list, m_list, m_list, m_list, m_unreg] → registered client + server) with no file‑static of its own. Every callee is already Rust — canonize (P5m), strtoken (P1), find_channel (P5t), hash_find_channels (P2), get_sendq (P2/class) — or a C extern (hunt_server still C; the variadic sendto_one). So a plain #ifndef guard drops the C def and the m_list decl parse.rs already imports resolves to the Rust def at link. No extern‑prototype switch, no parse.rs/build.rs edit. RED confirmed via the sole undefined m_list.
    • The sendto_one return type. m_list is the first ported caller that needs sendto_one's return value (rlen += sendto_one(…) — the remote‑LIST reply‑length throttle vs CHREPLLEN). The ircd‑common module‑local extern decls had declared it -> (); since clashing_extern_declarations is crate‑wide, all five decls (channel/s_user/s_misc/s_service/s_debug) were unified to the true ABI -> c_int (the sibling callers ignore the result — a c_int isn't #[must_use], so no warning). The cross‑crate bindings decl already returned c_int.
    • Config (verified cbuild/config.h): LIST_ALIS_NOTE defined (config.h:516) → both #ifdef LIST_ALIS_NOTE NOTICE blocks compile and are ported (the opening notice when MyConnect(sptr) in the bare branch, and the trailing notice when MyConnect && listedchannels > 24); the note string is reproduced as a byte‑exact c"…" const (incl. the embedded quotes around /squery alis help). CHREPLLEN=8192 (config.h:363). Numerics: RPL_LIST=322 (:%s 322 %s %s %d :%s), RPL_LISTEND=323, ERR_TOOMANYMATCHES=416. New helpers: secret_channel/hidden_channel/pub_channel/show_channel/dbuf_length; new bindings imports channel(as channel_list)/get_sendq/hunt_server/RPL_LIST/RPL_LISTEND/ERR_TOOMANYMATCHES.
    • format! per user request — N/A, documented in‑module. m_list builds no reply string of its own — every line goes straight to the C variadic sendto_one (the RPL_LIST chname/topic are arbitrary client bytes). The maxsendq = (int)((float)get_sendq(sptr,0) * (float)0.9) truncating double→float→int chain is mirrored as (get_sendq(sptr,0) as f32 * 0.9f32) as c_int; the DBufLength > maxsendq test mirrors C's unsigned comparison (maxsendq as u_int).
    • Faithfulness notes: parc > 2 && hunt_server(…, 2, …) != 0return 10 (the forwarding short‑circuit; HUNTED_ISME=0 falls through, PASS=1/NOSUCH=‑1 forward). Bare arg (BadPtr(parv[1])): !sptr->user → RPL_LISTEND + return 2; else opening notice, then loop 1 over sptr->user->channel listing only Secret||Hidden channels, then loop 2 over the global channel list skipping !users || Secret || Hidden — each with the DBufLength > maxsendq → ERR_TOOMANYMATCHES throttle (loop 1's goto end_of_list modelled as break 'lists, loop 2's break falls through), then the trailing notice. Named arg (else): canonize then per‑token strtoken(&p, parv[1], ",") with parv[1]=NULL advance; find_channel && ShowChannel && sptr->user → RPL_LIST; *name=='!'hash_find_channels(name+1, …) mask loop with the scr = SecretChannel && !IsMember users‑-1/topic‑"" masking; both with the !MyConnect && rlen > CHREPLLEN break. Tail: !MyConnect && rlen > CHREPLLEN → ERR_TOOMANYMATCHES; always RPL_LISTEND; return 2.
    • L2 + L2‑S2S are the gates (no clean L1 data op — m_list only reads channel state and sends wire output). L2 golden_channel_list 1/1 byte‑identical (-p standalone): bare LIST shows two public channels (322) + the ALIS notice + 323, and a +s secret channel a non‑member (alice) is not on does not appear (the inverse); named LIST #pub → just that channel; LIST #nonexistent → only 323 (the find_channel miss). S2S golden_s2s_list 1/1 byte‑identical: the hunt_server forwarding path the standalone harness can't reach — a local client issues LIST #chan peer.test (parc > 2, parv[2] = the linked peer) and the peer receives the forwarded LIST command byte‑for‑byte under ref‑C and Rust, confirming the Rust parc > 2 branch invokes hunt_server and short‑circuits (return 10) exactly as C. m_list defined T from ircd‑common in the Rust ircd; cargo build 0 warnings; the six channel goldens (channel/topic/part/modes/can_send/invite) stay green after the sendto_one sig unification; no new canonicalizer masks. Remaining channel.c (C): the mode cluster (m_mode/set_mode/add_modeid/del_modeid/send_mode_list/make_bei/free_bei/send_channel_*), join/access (m_join/m_njoin/can_join/get_channel/free_channel/reop_channel/collect_channel_garbage/setup_server_channels), part/kick (m_part/m_kick/find_chasing/check_string), and display (m_names/names_channel) clusters stay in channel_link.o.
  • 2026‑06‑03 — P5aa (channel.c m_part handler) merged. m_part (channel.c:2952‑3038 — the PART command: leave one or more channels) ported to ircd-common/src/channel.rs, extending the existing channel_link.o partial port (one #ifndef PORT_CHANNEL_P5 region around the body). Eighth channel.c sub‑phase. Plan: docs/superpowers/plans/2026-06-03-p5aa-m_part.md.

    • First handler in channel.c that calls the file‑static get_channel. m_part is a msgtab row (PART = [m_part, m_part, m_part, m_nop, m_unreg] → unreg/client/server cols, min 1 param). Unlike the P5y/P5z true leaves (which use find_channel), it resolves each named channel via get_channel(sptr, name, 0) — a static in C that stays C (the channel‑lifecycle cluster isn't ported yet). So the P5u/P5x extern‑switch was applied to get_channel: the forward decl (channel.c:69) and the definition storage class (channel.c:2174) both flip staticextern under #ifdef PORT_CHANNEL_P5 (linkage‑only; the body stays C and resolves within channel.c, but the symbol is now reachable from the Rust m_part). Every other callee is already Rust — canonize (P5m), strtoken (P1), check_channelmask (P5u), remove_user_from_channel (P5x) — or a C variadic sender (sendto_serv_butone added to the extern block; sendto_match_servs/sendto_channel_butserv already there). get_channelmask(name) is the JAPANESE‑off macro rindex(name,':') = strrchr. RED confirmed via the sole undefined m_part (get_channel resolved as C).
    • Config (verified): JAPANESE off → the && jp_valid(NULL, chptr, NULL) clause in the broadcast‑batching guard is not compiled, so the condition is just (!get_channelmask(name) && *chptr->chname != '!'); the #ifdef JAPANESE extern get_channelmask/jp_valid decls are skipped and the #else macro form applies. USE_NEWMSG off (no effect on m_part). Numerics: ERR_NOSUCHCHANNEL=403, ERR_NOTONCHANNEL=442. BUFSIZE=512, TOPICLEN=255 (struct_def.h, bindgen drops the macros → module consts). New helper my_person (MyConnect && IsPerson, struct_def.h:791).
    • The shared static buf[BUFSIZE] → a module‑private static mut BUF. channel.c's buf is shared across the TU, but m_part uses it as pure scratch: *buf = '\0' at entry, strcat‑accumulates the comma‑joined non‑local channel names, and flushes (sendto_serv_butone + *buf = '\0') within the same call — no state crosses calls or is read by another function. So a private static mut BUF: [c_char; BUFSIZE] in the Rust module is faithful (not ABI → no #[no_mangle]; accessed via addr_of_mut!). The size = BUFSIZE - strlen(parv[0]) - 10 C size_t arithmetic is mirrored with wrapping_sub and the strlen(buf)+strlen(name)+1 > size test compares size as usize (the exact C int→size_t promotion).
    • format! per user request — N/A, documented in‑module. m_part builds no reply string of its own; the comma‑joined buf is assembled with libc strcat byte‑for‑byte (arbitrary client channel‑name bytes — a String would corrupt non‑ASCII), and every reply/relay goes straight to a C variadic sender. PartFmt (":%s PART %s :%s") is inlined as a c"…" literal at each call site.
    • Faithfulness notes: comment = BadPtr(parv[2]) ? "" : parv[2] reads parv[2] directly (the parser NULL‑pads parv past parc, so parc is unused — _parc, matching C); strlen(comment) > TOPICLENcomment[TOPICLEN] = '\0'. Per‑token loop (strtoken(&p, parv[1], ","), parv[1]=NULL advance): get_channel miss → MyPerson(sptr)‑gated 403 + continue; check_channelmask != 0 → continue; !IsMember → 442 + continue. Then the broadcast split: a non‑local (*name != '&') channel whose name has no : mask and isn't !‑prefixed accumulates into buf (flush‑on‑overflow); otherwise sendto_match_servs relays it immediately with the per‑channel name; always sendto_channel_butserv echoes :nick PART #chan :comment to members and remove_user_from_channel unlinks the parter. Tail: a non‑empty buf gets one final sendto_serv_butone. Returns 4.
    • L2 + L2‑S2S are the gates (no clean L1 — m_part is a socket‑driven handler with no extractable data‑op core, consistent with m_topic/m_list). L2 golden_channel_part (now driving the Rust m_part) stays byte‑identical on the local success path (PART observed by a second member + the NAMES/WHO inverse confirming the parter is gone — remove_user_from_channel), extended with part_errors_match_reference: m_part's own 403 (PART a nonexistent channel) and 442 (PART a channel you're not on) numerics, previously unexercised. S2S golden_s2s_membership already covers the IsServer(cptr)/MyPerson remote‑PART branch — a remote user behind the peer PARTs (:001BAAAAA PART #chan), routed through the now‑Rust m_part's sptr->user->uid‑sourced propagation + the member relay a local observer sees, then NAMES no longer lists the remote user. All byte‑identical ref‑C == Rust; cargo build 0 warnings; no new canonicalizer masks. Remaining channel.c (C): the mode cluster (m_mode/set_mode/add_modeid/del_modeid/send_mode_list/make_bei/free_bei/send_channel_*), join/access (m_join/m_njoin/can_join/get_channel/free_channel/reop_channel/setup_server_channels), m_kick/find_chasing/check_string, and display (m_names/names_channel) clusters stay in channel_link.o.
  • 2026‑06‑03 — P5bb (channel.c m_kick handler + file‑static find_chasing) merged. m_kick (channel.c:3068‑3221 — the KICK command) and the file‑static find_chasing (channel.c:139 — history‑aware nick resolver) ported to ircd-common/src/channel.rs, extending the existing channel_link.o partial port (-DPORT_CHANNEL_P5). Ninth channel.c sub‑phase. Plan: docs/superpowers/plans/2026-06-03-p5bb-channel-m_kick.md.

    • find_chasing is the cluster's shared static — the P5u/P5x extern‑switch case. m_kick is its caller, but find_chasing is also called by the still‑C set_mode/m_mode (channel.c:~1301), so the C body is guarded out under #ifndef PORT_CHANNEL_P5 and the #else branch adds an extern aClient *find_chasing(…) forward decl (it had none — it was defined before both use sites) so the C remnant resolves to the Rust #[no_mangle] def at link (linkage‑only, faithful). check_string — the other display‑cluster static — is not touched: m_kick doesn't call it (only make_bei/m_mode do). RED confirmed via the two undefined symbols find_chasing+m_kick (every other callee already Rust or a C extern).
    • Callees, all already resolved: get_channel (C static, extern‑switched by P5aa; called flag 0 = !CREATE), check_channelmask (P5u), is_chan_op (P5t), remove_user_from_channel (P5x), find_uid (P2), find_client (P4), get_history (P2 whowas), mystrdup (P1), MyFree→ libc free, strlen/strcat libc; the C variadic senders sendto_flag/sendto_match_servs_v added to the local extern block (sendto_one/sendto_channel_butserv already there). Inline macro helpers all present except a std::cmp::max for the C MAX. Numerics 403/477(ERR_NOCHANMODES)/442/482 + 401 (find_chasing) + 441 (ERR_USERNOTINCHANNEL, added to the import). Consts MAXPENALTY=10, UIDLEN=9, KILLCHASETIMELIMIT=90 (config.h:676 macro, bindgen drops it) as module consts.
    • Faithfulness notes. nbuf[BUFSIZE+1] is a local auto array in C (not a file‑static) → a Rust stack [c_char; BUFSIZE+1]; no shared buf/modebuf touched. The C size_t arithmetic is mirrored exactly: maxlen = BUFSIZE - MAX(strlen(sender),strlen(name)) - strlen(comment) - 10 via wrapping_sub … as c_int, and clen = maxlen - strlen(name) - 1 via (maxlen as usize).wrapping_sub(…) (the C int→size_t promotion), with the flush test comparing … >= clen as usize. The !(IsServer(cptr) && (who=find_uid(...))) && !(who=find_chasing(...)) short‑circuit assignment is preserved branch‑for‑branch (find_uid only attempted for a server cptr, find_chasing only when that misses). sender = SID (server) / UID (person) / name; the relay echoes sptr->name/who->name to members but the comma‑batched sendto_match_servs_v(SV_UID,…) carries UIDs (who->user->uid). Returns penalty.
    • format! per user request — N/A, documented in‑module. find_chasing returns a pointer (its one 401 goes to the C sender); m_kick assembles the comma‑joined victim‑id list nbuf with libc strcat byte‑for‑byte (arbitrary client nick/UID bytes — a String would corrupt non‑ASCII) and routes every wire line through the C variadic senders.
    • L2 + L2‑S2S are the gates (no L1 — find_chasing is file‑static so no cref_ oracle symbol, cf. P5u/P5x statics; m_kick is a socket‑driven handler with no extractable data‑op core, cf. m_topic/m_list/m_part). New golden_channel_kick (L2): chanop alice KICKs member bob → bob sees :alice KICK #chan bob :… + the NAMES inverse (bob gone — remove_user_from_channel); plus m_kick's own error numerics 441 (find_chasing resolves a non‑member → !IsMember), 403, 442, 482 (461 min‑params is the parser's, not asserted). New golden_s2s_kick (L2‑S2S): (a) UID propagation — a local chanop KICKs a remote member (peer‑NJOINed) → the peer receives :000AAAAAA KICK #chan 001BAAAAA :… (sendto_match_servs_v(SV_UID), kicker‑UID source + victim‑UID target); (b) server‑sourced KICK — the peer server KICKs a local user by UID (:001B KICK #chan 000AAAAAA :…) → the IsServer(cptr)+find_uid victim resolution + IsServer(sptr) SID‑sender/SCH_NOTICE branches relay :peer.test KICK #chan alice :… and remove her (NAMES member‑list inverse). All byte‑identical ref‑C == Rust; cargo build 0 warnings; no new canonicalizer masks. Also fixed a pre‑existing timing flake in golden_s2s_membership (s2s_deop_and_clean_readd): the re‑NJOIN :bob JOIN relay raced the 300 ms drain window — now consumed deterministically via read_until(" JOIN ") (mirroring the MODE‑relay barrier already in that test). Remaining channel.c (C): the mode cluster (m_mode/set_mode/add_modeid/del_modeid/send_mode_list/make_bei/free_bei/send_channel_*), join/access (m_join/m_njoin/can_join/get_channel/free_channel/reop_channel/setup_server_channels), check_string, and display (m_names/names_channel) stay in channel_link.o.
  • 2026‑06‑03 — P5cc (channel.c display cluster — m_names handler + file‑static names_channel) merged. m_names (channel.c:3730 — the NAMES command) and the file‑static names_channel (channel.c:3577 — the per‑channel 353 RPL_NAMREPLY/366 RPL_ENDOFNAMES emitter) ported to ircd-common/src/channel.rs, extending the existing channel_link.o partial port (-DPORT_CHANNEL_P5). Tenth channel.c sub‑phase. Plan: docs/superpowers/plans/2026-06-03-p5cc-channel-m_names.md.

    • names_channel is the cluster's shared static — the P5u/P5x extern‑switch case. m_names is one caller, but names_channel is also called by the still‑C m_join (channel.c:2673, the JOIN echo). So its static forward decl (channel.c:105) is switched to extern under #ifndef PORT_CHANNEL_P5, and its body is guarded out, so the C m_join remnant resolves to the Rust #[no_mangle] def at link (linkage‑only, faithful). m_njoin does not call it (verified — the only other channel.c caller besides m_names is m_join). m_names is a plain #ifndef guard (no extern switch — its decl is imported by parse.rs from ircd_sys::bindings, resolving to Rust once channel_link.o drops the C def). RED confirmed via the two undefined symbols names_channel+m_names.
    • Callees, all already resolved: find_channel_link (P2), find_channel/clean_channelname (P5t), strtoken (P1), hunt_server (still C — bindings decl), the variadic sendto_one (P8 C trampoline). Inline macro helpers all present except a new is_invisible (x->user->flags & FLAGS_INVISIBLE, mirroring s_user.rs:144); globals client/channel/me + numerics RPL_NAMREPLY=353/RPL_ENDOFNAMES=366 + FLAGS_INVISIBLE added to the import. BUFSIZE/MAXCHANNELSPERUSER/MAXPENALTY already module consts.
    • Shared buf — the keystone faithfulness point. In C, m_names and names_channel both use the file‑static char buf[BUFSIZE] (channel.c:115). They are not reentrant: names_channel fills buf and flushes it (sendto_one) within each call, and m_names only writes buf itself (the third strcpy(pbuf,"* * :") remaining‑users section) after all its names_channel calls — so reusing the existing module BUF (introduced for P5aa m_part) is faithful, exactly as C shares one buf. Documented residual: the still‑C m_join keeps its own C buf; in its !‑safe‑channel autocreate branch it does sprintf(buf,…); name=buf; and re‑reads name after the names_channel call (which, in ref‑C, clobbers buf). With names_channel now writing the Rust BUF, C m_join's name survives intact → the Rust path is in fact more correct in that one buggy branch. This affects only !‑autocreate NJOIN bytes (no golden scenario drives ! channels; all tests use #/&/+ where name is the parv token, never buf) and disappears when m_join itself ports and buf becomes Rust‑owned. Noted, not tested.
    • Faithfulness notes. names_channel's cptr param is unused in C (only sptr/to/chptr/sendeon are read) → _cptr. Pointer arithmetic mirrored raw: *pbuf = ch; pbuf = pbuf.add(1) for the @/+/=/* prefix bytes; the 353 member list assembled with copy_nonoverlapping (arbitrary client nick bytes — a String would corrupt non‑ASCII); the maxlen = BUFSIZE - 1 - strlen(ME) - 5 - strlen(to) - 1 - pxlen - 2 budget + the (pbuf - buf) + nlen >= maxlen flush‑and‑restart‑after‑prefix exactly as C. The server‑member skip (strchr(name,'.')) precedes the IsInvisible skip, matching the C order. m_names: the parc>2 hunt_server(":%s NAMES %s %s",2,…) forward short‑circuit → MAXPENALTY; the named‑arg branch's sent throttle return (sent<2 ? 2 : (sent*MAXCHANNELSPERUSER)/MAXPENALTY); the all‑channels branch's three sections (secret‑on‑user, public via channel/nextch, remaining users via client/next). The dead sent=1/sent=0 writes in the third section (never read before return MAXPENALTY) are omitted — observationally identical (local, unread).
    • format! per user request — N/A, documented in‑module. Neither builds an ASCII reply string in Rust; the 353 member list is assembled into BUF byte‑for‑byte (copy_nonoverlapping + @/+/space prefixes), and every wire line goes to the C variadic sendto_one.
    • L2 + L2‑S2S are the gates (no L1 — read‑only + wire, no extractable socket‑free data‑op core, cf. m_topic/m_list/m_part). New golden_channel_names (L2): NAMES #pub from a member → = #pub :@alice bob 353 + 366 (the = public marker, @ op prefix); a +s secret channel's member view → the @ #secret 353 marker; the inverse — a non‑member's NAMES #secret yields only 366 (showusers=0, members hidden); bare NAMES → all‑visible‑channels + the * * : remaining‑users third section ending 366 … *. JOIN‑echo regression covered by the existing golden_channel (its 353/366 now come from the Rust names_channel via the still‑C m_join). New golden_s2s_names (L2‑S2S): the parc>2 hunt_server forwarding short‑circuit (NAMES #chan peer.test → the peer receives the byte‑identical forwarded NAMES line). All byte‑identical ref‑C == Rust; cargo build 0 warnings; no new canonicalizer masks. Test note: the L2 test uses 2 concurrent clients (alice op of #pub, bob member + #secret owner) — a 3‑client variant tripped the per‑host clone limit (BrokenPipe), so the inverse is driven by alice (non‑member of #secret) rather than a third nick. Remaining channel.c (C): the mode cluster (m_mode/set_mode/add_modeid/del_modeid/send_mode_list/make_bei/free_bei/send_channel_*), join/access (m_join/m_njoin/can_join/get_channel/free_channel/reop_channel/setup_server_channels), and check_string stay in channel_link.o.
  • 2026‑06‑03 — P5dd (channel.c JOIN cluster — m_join handler + file‑static can_join) merged. m_join (channel.c:2395‑2710 — the JOIN command) and can_join (channel.c:2036‑2119 — the JOIN access‑control predicate) ported to ircd-common/src/channel.rs, extending the existing channel_link.o partial port (‑DPORT_CHANNEL_P5). Eleventh channel.c sub‑phase. Plan: docs/superpowers/plans/2026-06-03-p5dd-channel-m_join.md.

    • can_join is a private Rust unsafe fn, not an export. It is file‑static in C and its only caller (m_join) ports here too → no C caller remains, so both its forward decl (channel.c:53) and body are plainly #ifndef PORT_CHANNEL_P5‑guarded out (no extern‑prototype switch needed, unlike the P5u/P5x shared statics). With no cref_can_join oracle symbol it has no L1 — every return branch is L2‑observed through m_join's reject numerics. m_join is a plain #ifndef guard (msgtab JOIN=[m_nop,m_join,m_join,m_nop,m_unreg] → client (col 1) + server (col 2, the IsServer(cptr) JOIN‑0 head); its decl parse.rs imports from ircd_sys::bindings resolves to the Rust def once channel_link.o drops the C def). RED confirmed via the sole undefined m_join (can_join had no remaining C reference).
    • Callees, all already resolved: Rust — strtoken (P1), check_channelmask (P5u), clean_channelname/find_channel/is_chan_op (P5t), hash_find_channels (P2), check_chid/get_chid (P3 s_id), del_invite (P5u), add_user_to_channel/remove_user_from_channel (P5x), names_channel (P5cc), exit_client (P5c), match_modeid (P5v), mycmp (P1 match_); C extern — get_channel (channel.c file‑static, extern‑switched by P5aa; called both 0=!CREATE and CREATE), the variadic senders (sendto_serv_v + sendto_channel_butone added to the local extern block; sendto_one/sendto_channel_butserv/sendto_match_servs/sendto_match_servs_v/sendto_flag already there). New helper is_split (iconf.split > 0, struct_def.h:805). New consts/imports: bootopt/iconf/CREATE/BOOT_PROT/CHFL_INVITE/CHFL_REOPLIST/EXITC_VIRUS/CHIDLEN(local)/the JOIN ERR_* numerics. CLIENTS_CHANNEL off (no &CLIENTS branch), USE_IAUTH on (&AUTH in can_join's oper‑channel set), JAPANESE off (no jp_valid), TOPIC_WHO_TIME defined (the topic‑on‑join 333 block ported).
    • jbuf vs the shared buf — the faithfulness point. m_join uses two distinct buffers: its function‑static char jbuf[BUFSIZE] (the validated, comma‑joined channel‑list accumulator) → a new module‑private static mut JBUF; and the file‑static buf[BUFSIZE] (the !‑channel‑id sprintf(buf,"!%.*s%s",CHIDLEN,get_chid(),name+2) scratch) → the existing module BUF. Both are pure in‑call scratch (never read across calls), so private Rust statics are faithful. Porting m_join also resolves the P5cc‑documented residual: the !‑autocreate name=buf aliasing is now wholly within the Rust module's BUF (no cross‑language buf clobber).
    • format! per user request — N/A, documented in‑module. Neither builds an ASCII reply string of its own — every wire line goes to a C variadic sender; the one sprintf(buf,"!%.*s%s",…) !‑id builder is a byte build into BUF (arbitrary client bytes — a String would misframe), faithful to C (cf. P5y topic_nuh).
    • Faithfulness notes. IsServer(cptr) head (the only JOIN over a link): parv[1]=="0" → walk sptr->user->channel, PART each (the PartFmt :%s PART %s :%s literal + the now‑Rust remove_user_from_channel), then sendto_match_servs(NULL,cptr,":%s JOIN 0 :%s",uid,parv[0]); a non‑zero server JOIN is silently ignored. First pass: per‑token check_channelmask/"0"‑literal/clean_channelname/the !‑channel block (!!/!# new‑id build + hash_find_channels/check_chid duplicate rejects; short !chan resolves via find_channel→hash_find_channels with the &&‑short‑circuit preserved)/IsChannelName gate/jbuf‑overflow break. Second pass: parallel key strtoken on parv[2]; JOIN 0 part‑all; get_channel(!CREATE); IsMember skip; MyConnect && joined>=MAXCHANNELSPERUSER → 405; the 10‑byte virus‑string strncmpexit_client; the split‑mode create gate → 437; get_channel(CREATE); can_joinreplies[i]; the chanop‑on‑create flag decision; add_user_to_channel; the JOIN echo; del_invite; topic 332/333; names_channel; anonymous NOTICEs; the NJOIN propagation (get_channelmask(name)||*chname=='!'sendto_match_servs_v(SV_UID) else *chname!='&'sendto_serv_v(SV_UID), both me.serv->sid‑sourced with the @@/@/"" flag prefix). Returns 2.
    • L2 + L2‑S2S are the gates (no L1 — m_join only manipulates membership via the already‑L1'd add_user_to_channel/remove_user_from_channel and emits wire; can_join is a file‑static, cf. m_names/m_list/m_topic). New golden_channel_join (L2, boot_standalone, 5 scenarios incl. inverses): +k wrong‑key 475 then the keyed JOIN succeeds (the key round‑trip); +i 473 then alice's INVITE overrides can_join's invite gate; +l 1 full 471; a duplicate JOIN is a silent no‑op (the IsMember skip — assert no second echo); JOIN 0 PARTs every channel then the NAMES inverse (she is gone, the channel destructed). The existing golden_channel (basic JOIN + JOIN→PART→re‑JOIN round‑trip) now drives the Rust m_join as a regression. New golden_s2s_join (L2‑S2S, boot_s2s, 2 scenarios): (a) a local client's JOIN of a federated #chan (peer‑created via NJOIN) propagates :000A NJOIN #chan :000AAAAAA to the peer — the sendto_serv_v SV_UID tail formatting the LOCAL SID + the joiner's UID, fields the client‑facing echo never carries; (b) the IsServer(cptr) head — a peer‑sourced :001BAAAAA JOIN 0 relays bob's :bob!~bob@remote.host PART #chan to a local co‑member. All byte‑identical ref‑C == Rust; the 10 channel goldens + 20 s2s goldens + the channel L1 diffs stay green; cargo build 0 warnings; no new canonicalizer masks. Remaining channel.c (C): the mode cluster (m_mode/set_mode/add_modeid/del_modeid/send_mode_list/make_bei/free_bei/send_channel_*), m_njoin, reop_channel/collect_channel_garbage/setup_server_channels, free_channel, and check_string stay in channel_link.o.
  • 2026‑06‑03 — P5ee (channel.c m_njoin handler — server NJOIN burst) merged. m_njoin (channel.c:2724‑2974 — the server‑to‑server channel‑membership burst command, carrying [@@+]uid,… member lists) ported to ircd-common/src/channel.rs, extending the existing channel_link.o partial port (‑DPORT_CHANNEL_P5). Twelfth channel.c sub‑phase. Plan: docs/superpowers/plans/2026-06-03-p5ee-channel-m_njoin.md.

    • Server‑only → L2‑S2S is the only gate. msgtab NJOIN=[m_njoin, m_nop, m_nop, m_nop, m_unreg]col 0 only (reachable from a linked server; a registered client hits m_nop). No standalone client path → no L2, and (a socket‑driven wire handler) no extractable L1 data op. A plain #ifndef PORT_CHANNEL_P5 guard drops the C def; the m_njoin decl parse.rs imports from ircd_sys::bindings resolves to the Rust def at link (m_njoin is not anyone's static callee → no extern‑prototype switch). Split from the P5dd m_join cluster: m_njoin shares no static with m_join (only the global scratch buffers) and is server‑only. RED confirmed via the sole undefined m_njoin.
    • Callees, all already resolved: Rust — check_channelmask (P5u), find_person/find_uid (P2/P4), add_user_to_channel (P5x), get_client_name (P5c s_misc); C extern — get_channel (channel.c file‑static, extern‑switched by P5aa; called with CREATE), the variadic senders sendto_one/sendto_flag/sendto_channel_butserv/sendto_match_servs_v (all already in the block). New helper is_bursting (!(flags & FLAGS_EOB), struct_def.h:222; flags is c_long). New imports: get_client_name/DELAYCHASETIMELIMIT/FLAGS_EOB/MAXMODEPARAMS/NICKLEN/ServerChannels_SCH_CHAN/ServerChannels_SCH_DEBUG; module const MODEBUFLEN=200. JAPANESE off → the IsServer(sptr) || clause in the JOIN‑relay :‑prefix test is dropped (just IsBursting(sptr)).
    • Shared modebuf/parabuf file‑statics → module‑private MODEBUF/PARABUF. These are shared with the still‑C set_mode, but m_njoin uses them as pure in‑call scratch (zeroed at the first mode via the cnt==0 reset, flushed via sendto_channel_butserv before return — no state crosses calls or is read by another fn), so private Rust statics are faithful (cf. P5aa's BUF). uidbuf[BUFSIZE] and mbuf[3] are C autos → Rust stack arrays.
    • format! per user request — N/A, documented in‑module. m_njoin builds no ASCII reply of its own; the uidbuf NJOIN‑relay buffer (the re‑emitted UID member list) and the modebuf/parabuf synthetic‑MODE scratch are assembled byte‑for‑byte (arbitrary client UID/nick bytes — a String would misframe non‑ASCII); every wire line goes to a C variadic sender.
    • Faithfulness notes. The mode‑prefix parse (@/@@/@@+/@+/+mbuf o/ov/v + chop CHFL bits, name advanced past the prefix, target left at the prefix for the verbatim uidbuf copy) is branch‑for‑branch. The MODE‑burst accumulator (the cnt switch): case 0 falls through to case 1 (strcat modebuf/parabuf, the mbuf[1] double‑name for a +ov user), case 2 flushes :%s MODE %s +%s%c %s %s (the mbuf[0] %c promoted as c_int), cnt == MAXMODEPARAMS flushes :%s MODE %s +%s %s and resets — modelled as if cnt==0||cnt==1 {…} else if cnt==2 {…} (the C fall‑through). The size_t/ptrdiff math (maxlen = BUFSIZE - 17 - strlen(parv0) - strlen(parv1) - NICKLEN; the u.offset_from(u_base) >= maxlen flush test) mirrored as signed isize. Empty‑channel lock (parv[2]=="."history = timeofday + (L)DELAYCHASETIMELIMIT, istat.is_hchan* bumps, return 0) ported (no observable wire → review‑covered). JOIN relay :%s JOIN %s%s with the :‑prefix‑unless‑bursting netjoin‑discriminator trick. Already‑on‑channel error class (IsBursting → SCH_ERROR notice + ERROR line to cptr; else SCH_CHAN "Fake:" notice). Source SID = sptr->serv->sid (the NJOIN origin server). Returns 0.
    • L2‑S2S green (the only gate). New golden_s2s_njoin: the @+ (op+voice) MODE‑burst path the existing golden_s2s_membership (plain add/remove) doesn't cover — a peer NJOINs carol @+001BBBBBB onto a #chan a LOCAL member (alice) is already on → alice observes the relayed :carol… JOIN :#chan + the synthetic :peer.test MODE #chan +ov carol carol burst (exercising the mbuf[1] double‑param path). golden_s2s_membership (remote NJOIN add → add_user_to_channel, remote PART remove, the deop/clean‑readd inverse) now drives the Rust m_njoin as a regression; the other NJOIN‑driving goldens (golden_s2s_join/golden_s2s_kick/golden_s2s_topic) stay byte‑identical ref‑C == Rust. cargo build 0 warnings; no new canonicalizer masks. Remaining channel.c (C): the mode cluster (m_mode/set_mode/add_modeid/del_modeid/send_mode_list/make_bei/free_bei/send_channel_*), reop_channel/collect_channel_garbage/setup_server_channels, free_channel, and check_string stay in channel_link.o.
  • 2026‑06‑03 — P5ff (channel.c mode‑id list layer) merged. check_string (channel.c:176), make_bei (:212), free_bei (:193), add_modeid (:263), del_modeid (:331) — the ban/exception/invite (+b/+e/+I/+R) mode‑id list‑item allocators + the chptr->mlist add/del — ported to ircd-common/src/channel.rs, extending the existing channel_link.o partial port (‑DPORT_CHANNEL_P5). Thirteenth channel.c sub‑phase; the connected component over the file‑static asterix[2]="*" var ({check_string, make_bei, free_bei} reference it; add_modeid/del_modeid call make_bei/free_bei). Plan: docs/superpowers/plans/2026-06-03-p5ff-channel-modeid-list.md.

    • All five are file‑static in C → the P5u/P5x extern‑prototype switch on each. The still‑C set_mode calls check_string (:1295/:1400), make_bei (:1786), add_modeid (:1934), del_modeid (:1937); set_mode + the still‑C free_channel (:2368) call free_bei. So each static forward decl flips to extern under #ifdef PORT_CHANNEL_P5 and the body is guarded out, resolving the C remnant to the Rust #[no_mangle] def at link (linkage‑only, faithful). add_modeid/del_modeid/free_bei/make_bei had forward decls (channel.c:97/98/112/113); check_string had none (defined before its callers) → a new extern char *check_string(char *) added under the guard (cf. P5x add_user_to_channel). RED confirmed via exactly the five undefined symbols (asterix has no external C reference → no link error, as predicted).
    • asterix is module‑private (static mut ASTERIX: [c_char;2]). C never references asterix outside the now‑ported check_string/make_bei/free_bei (verified: channel.c:44/181/190/195/199/203/220/222/231/233/242/244 are all inside the cluster), so the Rust pointer identity is self‑consistent: make_bei stores the Rust asterix ptr, free_bei compares against it (!= asterix_ptr() ⇒ a MyMalloc'd component to free), and the still‑C set_mode/free_channel only ever pass the resulting aListItem (never compare a component to their own asterix). No cross‑language pointer‑identity mismatch. The C asterix def is #ifndef‑guarded out.
    • Callees, all already resolved: Rust — collapse (P1 match_; the add_modeid MyClient nick/user/host collapse), mycmp (P1; BanExact), make_link/free_link (P2 list), istat (P2; is_bans/is_banmem are u_longwrapping_add/wrapping_sub, cf. P5u add_invite — a debug -= underflow would abort the nounwind extern fn); libc — MyMalloc(P2)/free/strncpy(strncpyzt_ helper)/strlen/isspace (added to the extern block; check_string passes the char sign‑extended as c_int as C does). New imports: aListItem/collapse/ERR_BANLISTFULL/HOSTLEN/MAXBANLENGTH/MAXBANS/RPL_BANLIST/RPL_EXCEPTLIST/RPL_INVITELIST/RPL_REOPLIST/USERLEN. Inline ban_len/ban_exact macros + asterix_ptr/strncpyzt_ helpers. The add_modeid (len > MAXBANLENGTH) || (++cnt >= MAXBANS) short‑circuit modelled as len > … || { cnt += 1; cnt >= … } (the ++cnt block runs only when the left is false — faithful to C's ||). bzero(mode, sizeof(Link))ptr::write_bytes(mode,0,1).
    • No L1 — all five are file‑static → the cref_ archive keeps them local (no cref_add_modeid oracle, cf. P5v match_modeid / P5dd can_join). The data‑op faithfulness is gated at L2: ref‑C set_mode calls C add_modeid/make_bei; the Rust build's (still‑C) set_mode calls the Rust ones — byte‑identical wire output through set_mode is the gate.
    • format! per user request — N/A, documented in‑module. make_bei copies arbitrary client‑supplied ban‑mask bytes (nick/user/host, capped NICKLEN/USERLEN/HOSTLEN) via strncpyzt_ byte copies (a UTF‑8 String would corrupt non‑ASCII — the standing P5c/P5f rule); every reject reply (ERR_BANLISTFULL, RPL_BANLIST/etc.) goes straight to the C variadic sendto_one; no sub‑long int reaches a sender.
    • L2 green + L2‑S2S green (the gates). New golden_channel_ban (L2): local chanop alice +b foo!bar@baz/+e *!*@trusted.example/+I friend!*@* add (→ make_bei 3‑component split + asterix‑fill + add_modeid head‑prepend), the MODE #chan b/e/I query (367/348/346 reading the Rust‑built mlist + 368/349/347 terminators), duplicate +b (BanExact dedup → re‑367, no second node), then ‑b (→ del_modeid + free_bei) and the inverse re‑query (the ban is gone — mask occurrence count 2→1). New golden_s2s_ban (L2‑S2S): a peer server sets :001B MODE #chan +b …set_mode calls the Rust add_modeid with MyClient(cptr) false (the pure non‑MyClient list‑add arm that skips every sendto_one, reachable only over a link), alice's MODE #chan b query confirms the ban, then the peer server ‑bdel_modeid + free_bei and the re‑query shows it gone. All byte‑identical ref‑C == Rust; full ircd-golden suite + the channel L1 diff suite (channel_leaf/invite/can_send/membership/modes_diff) green; cargo build --workspace 0 warnings; no new canonicalizer masks. Remaining channel.c (C): the mode core (m_mode/set_mode/send_mode_list/send_channel_modes/send_channel_members, sharing the modebuf/parabuf/uparabuf statics) + reop_channel/collect_channel_garbage/setup_server_channels/free_channel stay in channel_link.o.
  • 2026‑06‑03 — P5gg (channel.c mode core — m_mode/set_mode/send_mode_list/send_channel_modes/send_channel_members) merged. The MODE command handler (channel.c:1136), the ~860‑line mode parser/applier set_mode (:1195), the +beIR list emitter send_mode_list (:931), and the two server‑burst emitters send_channel_modes (:1022) / send_channel_members (:1066) ported to ircd-common/src/channel.rs, extending the existing channel_link.o partial port (‑DPORT_CHANNEL_P5). Fourteenth (final large) channel.c sub‑phase — the connected component over the shared file‑static scratch buffers modebuf/parabuf/uparabuf. Plan: docs/superpowers/plans/2026-06-03-p5gg-channel-mode-core.md.

    • All five take a plain #ifndef PORT_CHANNEL_P5 guard — no extern‑prototype switch. set_mode/send_mode_list are file‑static in C, and after this port have no remaining C caller (their only callers — m_mode and send_channel_modes — port here; m_njoin, the only other modebuf/parabuf user, is already Rust from P5ee) → they become private Rust unsafe fns (no cref_ oracle → L2/L2‑S2S tested). send_channel_modes/send_channel_members are exported (channel_ext.h) and still called by the C send_server_burst (s_serv.c:1302‑1311) → #[no_mangle] resolves s_serv's calls to the Rust defs at link. m_mode's decl parse.rs imports from ircd_sys::bindings resolves once the C def drops. RED confirmed via exactly three undefined symbols (m_mode/send_channel_modes/send_channel_members); the two statics vanished with their sole callers. After the port the C modebuf/parabuf/uparabuf file‑statics have zero C users → reuse the existing module‑private MODEBUF/PARABUF (P5ee in‑call scratch) + new UPARABUF.
    • Callees, all already resolved: Rust — canonize (P5m), strtoken (P1), clean_channelname/find_channel/is_chan_op (P5t), check_channelmask (P5u), channel_modes (P5w), change_chan_flag (P5x), find_chasing (P5bb), check_string/make_bei/free_bei/add_modeid/del_modeid (P5ff), del_invite (P5u), find_uid (P4), m_umode (P5h — the non‑channel MODE target route); C extern — the variadic senders (sendto_one/sendto_match_servs_v/sendto_channel_butserv/sendto_channel_butone/sendto_flag, all already in the block), myrand (P2). New imports: ircstp (the is_rreop/is_fake counters), m_umode, SLink__bindgen_ty_1 (the chops union init), ERR_NEEDMOREPARAMS/ERR_RESTRICTED/ERR_UNKNOWNMODE, RPL_CHANNELMODEIS/RPL_UNIQOPIS/the four RPL_ENDOF{BAN,EXCEPT,INVITE,REOP}LIST. New c_int consts MODE_DEL/MODE_KEY/MODE_LIMIT/MODE_EXCEPTION/MODE_INVITE/MODE_REOPLIST/KEYLEN/MODE_WPARAS (the imported MODE_BAN/… are bindgen u32 → cast at use; MODE_UNIQOP/MODE_CHANOP/MODE_VOICE == the CHFL_* locals). USE_SERVICES off (the check_services_butone block skipped); V29PlusOnly off (the opcnt >= MAXMODEPARAMS+1 inner guards compile); JAPANESE off (no jp_valid).
    • chops/flags[] + the tri‑state whatt. set_mode's static Link chops[MAXMODEPARAMS+3] (in‑call scratch) → a module‑private static mut CHOPS: [Link; 6] (const‑init via the SLink__bindgen_ty_1{cptr: null} union); static int flags[] (the simple‑mode bit↔char table) → a const SIMPLE_FLAGS: &[(c_int, u8)] walked in the exact order. whatt (C u_int, used as both MODE_ADD/MODE_DEL and the 1/-1/0 +/- sign tracker across the emission loops) is kept c_int — every value (0x40000000/0x20000000/0/1/-1) compares identically. mode->mode is u_int → the working copy new_ is c_int (matching C's int new), cast on read/write (all bits < 0x40000, no sign issue). The C switch breaks (break the switch → continue to curr++) became a labeled 'sw block with break 'sw; the 'O''o'/'v' fall‑through is a proceed‑bool computed in an 'ob: labeled block; the opcnt reconstruction's if (tmplen == -1) break; (breaks the for loop) is break 'recon, while the per‑item MODE_KEY skips are a nested 'emit_key: block.
    • format! per user request — N/A, documented in‑module. The whole cluster builds protocol‑byte strings into the modebuf/parabuf/uparabuf scratch (*mbuf++, strcat, the +l sprintf("%-15d")) byte‑for‑byte as C (arbitrary client nick/ban‑mask bytes — a UTF‑8 String would misframe), then hands them to the C variadic senders; there is no owned ASCII reply to format!.
    • Faithfulness notes. set_mode tail: the sender cascade s = IsServer(sptr) ? serv->sid : sptr->user ? user->uid : sptr->name; sendto_match_servs_v(chptr, cptr, SV_UID, …mbuf, upbuf) (the UID‑form params in upbuf, name‑form in pbuf); the fake‑vs‑real branch (IsServer(cptr) && !IsServer(sptr) && !ischopsendto_flag(SCH_CHAN, "Fake:…") + ircstp->is_fake++, else sendto_channel_butserv(…pbuf)); return ischop ? count : -count. The k key sanitizer (,., the >0x7f/>0xa0 high‑byte mask, KEYLEN truncation) ported on *mut u8 exactly. reop scheduling (timeofday + LDELAYCHASETIMELIMIT + myrand()%300 for chanop self‑deop / server +R; +r from server sets chptr->reop) writes channel state (not wire — myrand non‑determinism is invisible to L2). parseNUH splits nick!user@host in place via strchr/strrchr, make_bei, restores the separators.
    • Bug caught by the burst S2S test (the inverse paid off). aListItem.nick/user/host are *mut c_char pointers, not arrays — the first port took addr_of!((*al).nick) (address of the pointer field) and %s printed the pointer's bytes as garbage (:000A MODE #chan +b ``…). The golden_s2s_mode relink‑burst diff caught it immediately; fixed to pass (*al).nick directly at all three sites (send_mode_list, the beIR list query, the reconstruction nuh). The simpler L2/outbound tests passed without this fix (they never serialize a stored mlist entry) — only the burst path re‑emits the list, so the S2S coverage was load‑bearing.
    • L2 + L2‑S2S are the gates (no fresh L1 — set_mode's state mutation is already L1‑covered via the P5x membership + P5ff ban diffs through it). New golden_channel_mode_set (L2): a chanop voices a second member (+v bob) — the relay reaches every member via sendto_channel_butserv, observed by bob; NAMES shows +bob; the inverse -v bob → NAMES shows plain bob (change_chan_flag MODE_DEL clears CHFL_VOICE). The 2‑client harness (the per‑host clone limit is 2, the P5cc finding) makes bob both target and observer. New golden_s2s_mode (L2‑S2S, 2 scenarios): outbound — a local chanop (the peer ops alice on a remote‑NJOIN'd #chan, since a split local client can't create one) sets +m then +l 5, and the peer receives the UID‑sourced :000AAAAAA MODE #chan +m / +l 5 (set_mode's sendto_match_servs_v(SV_UID) tail, upbuf form); link burst — a populated #chan, then the peer is dropped and relinks (the only way to burst a # channel: it can't exist before the single harness peer links), so the second burst replays :000A NJOIN #chan :000AAAAAA (send_channel_members) + :000A MODE #chan +tnl 10 / +b *!*@evil.host (send_channel_modeschannel_modes + send_mode_list). Regression: golden_channel_modes/ban/part (L2) + golden_s2s_ban/membership (inbound server MODE through the Rust set_mode) stay byte‑identical ref‑C == Rust; full ircd-golden + ircd-testkit (L1) suites green; cargo build 0 warnings; no new canonicalizer masks. Remaining channel.c (C): only the lifecycle groupreop_channel/collect_channel_garbage/setup_server_channels/free_channel/get_channel — stays in channel_link.o. The channel.c mode + handler clusters are now fully Rust.
  • 2026‑06‑03 — P5hh (channel.c lifecycle group — get_channel/free_channel/reop_channel/setup_server_channels/collect_channel_garbage + the channel global) merged. The channel struct's birth/death cluster ported to ircd-common/src/channel.rs. 15th and FINAL channel.c sub‑phase — every symbol channel.c defines now lives in ircd-common, so channel.o is dropped outright (added to PORTED in ircd-sys/build.rs; the P5t…P5gg channel_link.o second‑compile + its -DPORT_CHANNEL_P5 link substitution removed entirely; the cref oracle keeps the unguarded channel.o). Plan: docs/superpowers/plans/2026-06-03-p5hh-channel-lifecycle.md.

    • Symbols. get_channel (channel.c:2226, file‑static, extern‑switched P5aa) + free_channel (:2378, file‑static, extern‑switched P5x) → #[no_mangle] pub unsafe extern "C" (their extern‑block decls in channel.rs removed; the still‑Rust callers m_part/m_join/m_njoin/remove_user_from_channel/setup_server_channels resolve to them). reop_channel (:3911, purely file‑static, collect_channel_garbage's only caller) → private Rust unsafe fn (no oracle). setup_server_channels (:812) + collect_channel_garbage (:4026) are channel_ext.h exports called from ircd.c:738 (boot) / ircd.c:1188 (io_loop timer) → #[no_mangle]. The channel global list head (:52) → #[no_mangle] pub static mut channel (replaces the channel as channel_list bindgen import; the two read sites in m_list/the GC updated; the still‑C s_debug STATS resolves its extern decl to it).
    • Config findings (locked config). USE_IAUTH on → setup_server_channels creates the 14th channel &AUTH; CLIENTS_CHANNEL undef → no &CLIENTS; JAPANESE off → get_channel's jp_chname/FLAGS_JP block dead; DEBUGMODE off → collect's del/SCH_NOTICE + reop's SCH_NOTICE blocks dead; LOG_SERVER_CHANNELS off → the still‑C setup_svchans (send.c) is just the find_channel relink loop. MyFree(chptr) → plain free (the macro's NULL‑out is dead on a by‑value param). The function‑local statics max_nb/split in collect_channel_garbage → module‑private static mut MAX_NB/SPLIT (persist across calls, faithful). reop_channel's !CHFL_REOPLIST arg = the C logical‑not of a nonzero value = 0 (ported literally). istat decrements use wrapping_sub (cf. P5u/P5ff).
    • Classification: utility/callee TU (none of the six is a msgtab handler). get_channel/free_channel/reop_channel are file‑static → the --prefix-symbols=cref_ rename keeps them local (no oracle); setup_server_channels/collect_channel_garbage/channel/add_to_channel_hash_table are exported → cref_* oracles exist.
    • L1 (ircd-testkit/tests/channel_lifecycle_diff.rs, the two exported entries vs cref_; the three statics transitively covered). Both sides drive separate process globals (channel vs cref_channel, separate channel hash tables init'd per‑test via inithashtables/cref_inithashtables — NULL until boot otherwise, the L1 segfault finding — separate istat). (1) setup_server_channels create‑cluster: 14 server channels, identical chname/topic/mode.mode/users==1‑lone‑chanop walk (get_channel CREATE + global‑list prepend + hash insert + add_user_to_channel + topic strcpy + mode masks + setup_svchans relink). (2) Inverse: the oracle'd remove_user_from_channel(mp, &ERRORS)users→0, history==0 → free_channel unlinks &ERRORS from both global lists and the hash (find now misses), the other 13 survive. (3) collect_channel_garbage: hand‑built &dead (empty+expired) vs &live → identical return now+CHECKFREQ, &dead freed (gone from list+hash), &live kept. Finding: free_channel re‑checks the global timeofday >= chptr->history (not collect's now param) → the test pins timeofday/cref_timeofday to now so the expired‑free path fires; the myrand reop arm needs a live opless +r channel (non‑deterministic) → not entered, review‑covered.
    • No new L2‑S2S. None of the six has an IsServer(cptr) dispatch branch or formats remote‑user fields (get_channel's only client test is MyClient(cptr) chname truncation). get_channel/free_channel are already L2/S2S‑covered transitively by every JOIN/PART golden (golden_channel*, golden_s2s_membership — remote NJOIN/PART drive get_channel(CREATE)/free_channel); collect_channel_garbage/reop_channel are timer‑driven (300 s, unreachable in a short golden). Regression: full ircd-golden (83 tests) + ircd-testkit L1 suites byte‑identical ref‑C == Rust; cargo build 0 warnings; no new canonicalizer masks. channel.c is now FULLY ported to Rust — only the channel lifecycle remained after P5gg, and this closes it.
  • 2026‑06‑03 — P5ii (s_serv.c informational query foundation cluster — m_version/m_time/m_admin) merged. The three leaf informational query handlers (s_serv.c:109/2534/2547) ported to the new ircd-common/src/s_serv.rs. First s_serv.c sub‑phases_serv.c is the last (and largest) P5 TU (~3884 lines, ~14 file‑statics, ~40 handlers), ported in clusters; this one establishes the s_serv_link.o partial‑port mechanism. Plan: docs/superpowers/plans/2026-06-03-p5ii-s_serv-informational.md.

    • Mechanism — establish s_serv_link.o (mirrors s_user_link.o). A second compile of s_serv.c with the three ported handlers #ifdef'd out (-DPORT_SERV_P5), substituted into the link set via the new "s_serv.o" => "s_serv_link.o" map entry in build.rs link_objs(); the cref oracle keeps the unguarded s_serv.o in CREF_OBJS. The recipe carries the per‑object define -DIRCDMOTD_PATH="\"$(IRCDMOTD_PATH)\"" (Makefile s_serv.o recipe line 346 — needed by the still‑C m_motd), driven through make --eval so the recursive path var expands identically. All three handlers take a plain #ifndef PORT_SERV_P5 guard — no extern‑prototype switch: none is a file‑static and none is anyone's static callee. RED→GREEN confirmed: binary links clean (no undefined, no multiple‑definition) and nm target/debug/ircd | grep ' T \(m_version\|m_time\|m_admin\)$' shows all three defined in the binary (came from Rust).
    • Callees, all already resolved. C externs — hunt_server (still C, s_user.c; bindings decl), find_admin (still C, s_conf.c; bindings decl), sendto_one (variadic P8 trampoline; local extern "C" block, fmt *mut c_char). Rust — replies[] (P3, via reply(i)), date (P5c s_misc.rs, date(0)), serveropts (P5b s_debug.rs; the bindgen [c_char; 0] incomplete‑array extern → addr_of!(serveropts) as *mut c_char resolves to the real 12‑byte Rust static at link). Globals/consts — me (me.name self‑pointer; (*me.serv).sid.as_ptr() for the VERSION SID), IRC_VERSION (bindgen &[u8;7]), HUNTED_ISME (0u32). Inlined macros: ME=me.name, BadTo/BadPtr (struct_def.h:787), IsRegistered=status >= STAT_SERVER || status == STAT_ME (struct_def.h:132).
    • Classification: handler cluster, no L1. All three are in msgtab (common/parse.c) at col 1 (m_version/m_time) or all‑columns (m_admin), min‑params 0 → client‑reachable → L2. Each has a hunt_server forwarding branch (server‑reachable, exactly like P5z m_list) → L2‑S2S. m_admin additionally has the IsRegistered(cptr) head‑gate (unregistered clients skip the forward — a local branch, L2‑covered). No L1 — like m_list/m_topic/m_names, these are pure wire handlers with no socket‑free data op (no allocator/list‑mutation/data‑returning lookup), so there is nothing for an L1 differential to drive.
    • format! per user request — N/A, documented in‑module. None of the three builds an ASCII/numeric reply string of its own — every wire line goes straight to the C variadic sendto_one(replies[…], …); date() is the P5c Rust port returning its own static buffer. There is nothing to format.
    • L2 + L2‑S2S are the gates. golden_s_serv_info (L2): a lone registered client drives VERSION→351 RPL_VERSION, TIME→391 RPL_TIME (trailing localtime wall‑clock volatile → masked by the existing canonicalize() 391 rule), ADMIN→256‑259 RPL_ADMINME…RPL_ADMINEMAIL via minimal.conf's A line (find_admin() non‑null → the admin path, not ERR_NOADMININFO) — reference‑C == Rust byte‑identical. golden_s2s_s_serv_info (L2‑S2S): a local client targets VERSION/TIME/ADMIN peer.testhunt_server matches the linked peer and forwards each command out the link; the peer receives byte‑identical forwarded lines under ref‑C and Rust (the forwarding short‑circuit the standalone harness can't reach). cargo build 0 warnings; full ircd-golden + ircd-testkit suites green; no new canonicalizer masks.
  • 2026‑06‑03 — P5jj (s_serv.c network‑info query cluster — m_info/m_links + the file‑static check_link) merged. The two remaining leaf network‑info query handlers (s_serv.c:1400/1438) + their shared per‑link load‑health gate check_link (s_serv.c:3419) ported into ircd-common/src/s_serv.rs, extending the existing s_serv_link.o partial port (‑DPORT_SERV_P5; three new guard regions). Second s_serv.c sub‑phase. Plan: docs/superpowers/plans/2026-06-03-p5jj-s_serv-info-links.md.

    • First s_serv.c extern‑prototype switch. check_link is file‑static in C and still called by the C remnant m_stats (×2) / m_motd → the P5u/P5x mechanism: its forward decl at s_serv.c:36 flips staticextern under #ifdef PORT_SERV_P5 (body guarded out) so the C remnant resolves to the Rust #[no_mangle] def at link (linkage‑only, faithful). m_info/m_links take plain #ifndef guards (neither is a static / anyone's static callee — their decls parse.rs imports from ircd_sys::bindings resolve to Rust). RED→GREEN: binary links clean and nm target/debug/ircd | grep -E ' T (m_info|m_links|check_link)$' shows all three defined from Rust (check_link is now T, was a local t).
    • Callees, all already resolved. C externs — hunt_server, sendto_one (variadic P8 trampoline, local block fmt *mut c_char). version.c externs (still C) — infotext ([*mut c_char; 0] incomplete‑array → addr_of! + NULL‑walk), creation, generation. Rust — replies[] (P3 reply()), myctime (P1 support), collapse/match_ (P1), svrtop (P2), ircstp (P5c). Globals/consts — me (me.name/me.firsttime/(*me.serv)…), bootopt+BOOT_PROT, timeofday, Status_STAT_CLIENT, CHREPLLEN=8192 (config.h:363, baked as a const c_int — not a bindgen const, cf. channel.rs). Inlined macros: ME/BadTo/BadPtr, MyConnect=fd>=0, IsMasked=x && x->serv && x->serv->maskedby != x, DBufLength=(d)->length. Invariant kept: acptr->serv->up->name is (*(*(*acptr).serv).up).name (deref the *mut aClient, never .read() an aClient by value).
    • Classification: handler cluster, no L1. INFO {m_nop,m_info,m_info,m_nop,m_unreg} + LINKS {m_links×4,m_unreg} (msgtab col 1, min‑params 0 → client‑reachable → L2); each has a hunt_server forward (INFO always; LINKS when parc>2) → L2‑S2S. No L1 — like P5ii/m_list/m_names, pure wire handlers with no socket‑free data op. check_link does mutate ircstp counters, but it is file‑static → the ‑‑prefix‑symbols=cref_ rename keeps it local (no cref_check_link oracle), and its non‑zero branches need a remote client over a loaded link (SendQ>65536 / link‑age<60s / lastload timing) which a local client never reaches (MyConnect → return 0) and which is timing‑non‑deterministic over S2S → the return‑0 path is L2‑covered (every INFO/LINKS), the load branches are review‑covered (no deterministic harness path). So no L1 file.
    • Two new canonicalizer masks (m_info's volatile 371 trailers). INFO emits, after the deterministic infotext[] AUTHORS block, two 371 RPL_INFO lines that differ between the separately‑built / separately‑booted ref‑C and Rust runs: :Birth Date: <creation>, compile # <generation> (version.c build timestamp + per‑build counter) and :On‑line since <ctime(me.firsttime)> (boot wall‑clock). Added a 371 rule to canonicalize() masking each volatile tail (keeps the label). infotext[] itself is byte‑identical across builds → not masked.
    • format! per user request — N/A, documented in‑module. Every wire line (INFO text/birth/online, the per‑server 364 RPL_LINKS, the TRYAGAIN/ENDOF replies) goes straight to the C variadic sendto_one; myctime() returns its own static buffer. m_links's only integer arg is acptr->hopcount (%d, already c_int — the P5c width rule is satisfied). Nothing to format.
    • L2 + L2‑S2S are the gates. golden_s_serv_info_links (L2): a lone registered client drives INFO→a run of 371 (masked birth/online) ending in 374 RPL_ENDOFINFO, then bare LINKS364/365 RPL_ENDOFLINKS — ref‑C == Rust byte‑identical; check_link returns 0 (local MyConnect) so no 263 RPL_TRYAGAIN. golden_s2s_s_serv_info_links (L2‑S2S): the hunt_server forwards (INFO peer.test, LINKS peer.test *) reach the linked peer byte‑identical, and the bare‑LINKS tree walk before vs after the peer links (the positive/inverse pair — the lone server sees only :irc.test 364 alice irc.test irc.test :0 000A L2 Test Server, then after the link adds :irc.test 364 alice peer.test irc.test :1 001B Peer Test Server), the only way to exercise m_links's remote‑server up->name/sid/info field formatting. cargo build 0 warnings; full ircd-golden + ircd-testkit suites green.
  • 2026‑06‑04 — P5kk (s_serv.c informational stub cluster — m_users/send_users/m_summon/m_help) merged. The three leaf informational stub handlers (s_serv.c:1500/2154/2247) + the file‑static helper send_users (s_serv.c:2202) ported into ircd-common/src/s_serv.rs, extending the existing s_serv_link.o partial port (‑DPORT_SERV_P5). Third s_serv.c sub‑phase. Plan: docs/superpowers/plans/2026-06-04-p5kk-s_serv-users-summon-help.md.

    • Config‑resolved bodies. USERS_RFC1459 off (config.h:85 #undef) → m_users takes the send_users arm: forward via hunt_server(":%s USERS :%s", 1) only when parc>1, then send_users emits 265 RPL_LOCALUSERS (is_myclnt/is_m_myclnt ×2) + 266 RPL_GLOBALUSERS (is_user[0]+is_user[1]/is_m_users ×2). ENABLE_SUMMON off (config.h:77 #undef) → m_summon, after the parc<2/empty 411 ERR_NORECIPIENT guard, rewrites parv[1..=4] (user/host=ME‑or‑parv[2]/chname=*/NULL) and forwards via hunt_server(":%s SUMMON %s %s %s", 2); the ISME path emits 445 ERR_SUMMONDISABLED. m_help walks msgtab[] (the incomplete‑array extern, addr_of! + cmd.is_null() terminator) emitting :%s NOTICE %s :%s per command.
    • send_users extern‑prototype switch (P5u/P5x). send_users is static in C (s_serv.c:52 forward decl) and was GCC‑inlined into both callers as .isra.0 (no global symbol in the oracle s_serv.o → no cref_send_users). But it has a remaining C caller — the still‑C m_lusers (s_serv.c:2386, if (all) send_users(...)) — so guarding out its definition left s_serv_link.o with an undefined local send_users (confirmed at RED: undefined m_users/m_summon/m_help/send_users). Fix: the forward decl flips staticextern under #ifdef PORT_SERV_P5 and the Rust def is #[no_mangle] pub extern "C", so m_lusers resolves to the Rust send_users at link. nm target/debug/ircd | grep -E ' T (m_users|m_summon|m_help|send_users)$' shows all four defined from Rust.
    • Callees, all already resolved. C externs — hunt_server, sendto_one (variadic P8 trampoline, local block fmt *mut c_char). Rust — replies[] (P3 reply()), msgtab[] (parse.rs, static mut resolves at link). Globals — istat (istat_t: is_myclnt/is_m_myclnt/is_user[2]/is_m_users, u_long counters passed verbatim to the variadic sender), me. Inlined macros — ME/BadTo/BadPtr. The parv rewrite in m_summon is faithful raw‑pointer stores (*parv.add(n) = …) into the parser's fixed MAXPARA+1 array.
    • Classification: handler cluster, no L1. USERS {m_nop,m_users,m_users,m_users,m_unreg} / SUMMON {m_nop,m_summon,m_summon,m_nop,m_unreg} / HELP {m_nop,m_help,m_help,m_nop,m_unreg} (msgtab col 1, min‑params 0 → client‑reachable → L2). m_users/m_summon each have a hunt_server forward → L2‑S2S; m_help has no forward/IsServer path → no S2S. No L1 — like P5ii/P5jj/m_list, pure wire handlers with no socket‑free data op; send_users has no cref_ oracle (inlined) so it too is L2‑covered, not L1.
    • format! per user request — N/A. Every wire line (the 265/266 counts, 411/445, the HELP NOTICEs) goes straight to the C variadic sendto_one; nothing is assembled into a Rust string.
    • L2 + L2‑S2S are the gates. golden_s_serv_stubs (L2): a lone registered client drives USERS265+266, SUMMON nobody445, HELP→the NOTICE list anchored on the table's final :ETRACE entry — ref‑C == Rust byte‑identical. golden_s2s_s_serv_stubs (L2‑S2S): USERS peer.test / SUMMON nobody peer.test forward via hunt_server and the linked peer receives byte‑identical forwarded commands (m_help, having no forward, is excluded with a note). cargo build 0 warnings; both touched golden binaries green.
  • 2026‑06‑04 — P5ll (s_serv.c LUSERS handler — m_lusers) merged. The LUSERS network‑stats handler (s_serv.c:2272) ported into ircd-common/src/s_serv.rs, extending the existing s_serv_link.o partial port (‑DPORT_SERV_P5). Fourth s_serv.c sub‑phase. This closes the P5kk send_users loop: m_lusers's all‑path tail if (all) send_users(...) already resolves to the Rust send_users (P5kk), and now both live in Rust. Plan: docs/superpowers/plans/2026-06-04-p5ll-s_serv-m_lusers.md.

    • Guard + RED→GREEN. A single plain #ifndef PORT_SERV_P5 guard around the C def (s_serv.c:2272‑2392); m_lusers is not a file‑static and not anyone's static callee. RED: cargo build -p ircd-rs → exactly one undefined symbol m_lusers. GREEN: nm target/debug/ircd | grep ' T m_lusers$' → defined from Rust; cargo build 0 warnings.
    • Config‑resolved body (USERS_RFC1459 off). Three counting paths feed RPL_LUSER{CLIENT 251, OP 252, UNKNOWN 253, CHANNELS 254, ME 255}: (1) bare LUSERS or LUSERS * → the all path reads istat (is_serv/is_user[0..1]/is_unknown/is_oper/is_service/is_myclnt/is_myserv/is_myservice) then tails into send_users (265/266); (2) LUSERS <server> → a remote find_clientIsServerserv->usercnt[0..2]; (3) LUSERS <mask> → walks svrtop summing each asptr->usercnt[] (the IsMe arm grabs is_myclnt/…/is_unknown), then walks svctop counting matching services. parc>2 forwards via hunt_server(":%s LUSERS %s :%s", 2) first.
    • Callees, all already resolved. C externs — hunt_server, sendto_one (variadic P8 trampoline, local block fmt *mut c_char). Rust — collapse/match_ (P1), find_client (P4 parse.rs), send_users (P5kk). Globals — istat/svrtop/svctop (the aServer.usercnt/nexts/bcptr + aService.servp/nexts chains, raw‑pointer walked, never an aServer/aService read by value), replies (P3 reply()), me. New inline helpers is_server=status==STAT_SERVER, is_me=status==STAT_ME (struct_def.h:136/138).
    • Faithfulness notes. The int locals (s_count/c_count/i_count/u_count/o_count/v_count/m_clients/m_servers/m_services) stay c_int, assigned from u_long istat fields with as c_int (C's implicit u_longint truncation). istat.is_chan is passed straight to the %d RPL_LUSERCHANNELS variadic as u_long — byte‑identical on x86‑64 SysV (the low 32 bits of the arg register match a small positive int), exactly as the already‑Rust send_users does. Quirk ported literally: RPL_LUSERUNKNOWN (253) is emitted with parv0, not BadTo(parv0) (s_serv.c:2380) — every other LUSERS reply uses BadTo.
    • Classification: handler cluster, no L1. LUSERS {m_lusers, m_lusers, m_lusers, m_lusers, m_unreg} (msgtab col 1 client‑reachable + col 2 server‑reachable, min‑params 0 → L2); the hunt_server forward (parc>2) and the remote‑server field formatting (svrtop/find_client usercnt[] + server names) → L2‑S2S. No L1 — pure wire handler with no socket‑free data op (the lookups are read‑only counting terminating in sendto_one), cf. P5ii/P5jj/P5kk/m_list.
    • format! per user request — N/A. Every wire line (the five LUSER numerics + the send_users 265/266) goes straight to the C variadic sendto_one; nothing is assembled into a Rust string.
    • L2 + L2‑S2S are the gates. golden_s_serv_lusers (L2): a lone registered client drives a bare LUSERS → 251/254/255 then the send_users 265/266 (the all path), read through the always‑last 266 anchor — ref‑C == Rust byte‑identical. golden_s2s_s_serv_lusers (L2‑S2S): two server‑reachable facets after a peer links — (1) LUSERS *.test exercises the svrtop mask walk (the *.test matches both irc.test + peer.test, so the 251 server count reflects the link and the IsMe 253 arm fires), diffed from the client side through the 255 tail; (2) LUSERS * peer.test (parc>2) forwards via hunt_server and the peer receives the byte‑identical forwarded command. cargo build 0 warnings; both touched golden binaries green.
  • 2026‑06‑04 — P5mm (s_serv.c map‑display cluster — m_map/dump_map/dump_map_sid) merged. The MAP handler (s_serv.c:3748) + its two recursive file‑static tree renderers dump_map (plain names, s_serv.c:3666) and dump_map_sid (names + usercount + SID + version, s_serv.c:3599) ported into ircd-common/src/s_serv.rs, extending the existing s_serv_link.o partial port (‑DPORT_SERV_P5). Fifth s_serv.c sub‑phase.

    • Guard + RED→GREEN. A single plain #ifndef PORT_SERV_P5 guard around the contiguous C region (s_serv.c:3599‑3781, all three fns): dump_map/dump_map_sid are file‑static with no remaining C caller (only m_map calls them, and it ports here too) → private Rust unsafe fns (no cref_ oracle, like m_list/can_join); m_map's decl resolves via the parse.rs msgtab imports. nm target/debug/ircd | grep ' T m_map$' → defined from Rust; cargo build 0 warnings.
    • Module‑private BUF (the shared buf[BUFSIZE]). dump_map/dump_map_sid accumulate the output line in the s_serv.c TU‑wide static char buf[BUFSIZE] via raw pointer arithmetic — pbuf indexes the current nesting offset (pbuf + 4 per level), each leaf flushes the whole buf with 015 RPL_MAP, and the tree‑char cleanup walks backwards (pbuf[-2]/pbuf[-3]). m_map fills+flushes buf entirely in‑call (no cross‑call aliasing with the still‑C users of buf — m_stats/report_*/send_server_burst), so a module‑private static mut BUF: [c_char; BUFSIZE] + a buf_base() helper is faithful (cf. P5aa/P5cc). Pointer ops use .add()/.sub()/.offset_from(); the snprintf size arg keeps C's intsize_t conversion via size as usize (negative→huge, faithful UB).
    • Callees, all already resolved. C externs — sendto_one (variadic P8 trampoline, local block fmt *mut c_char), plus snprintf/strcat/strlen (libc, added to the local extern block, faithful to the C). Rust — match_ (P1), replies[] (P3 reply()). Globals — me (addr_of_mut!(me) for &me, the tree root), timeofday. New inline helpers is_bursting=!(flags & FLAGS_EOB) (struct_def.h:222) and map_name=IsMasked(root) ? maskedby->name : root->name; reused is_masked/my_connect/bad_to/me_name (P5ii/P5jj).
    • Faithfulness notes. me is SetEOB'd at boot (ircd.c:732) → IsBursting(&me) false → dump_map_sid(&me) always takes the non‑bursting "%s %d %s %s" branch (no wall‑clock "bursting %ds" trailer). dump_map's prevserver carries the last unmasked server across masked‑subtree boundaries via a *mut *mut aClient out‑param + a recursion‑local prev (addr_of_mut!(prev)), byte‑for‑byte the C aClient **. The %ds literal (snprintf) is %d followed by a literal s.
    • Classification: handler cluster, no L1. MAP {m_map, m_map, m_map, m_nop, m_unreg} (msgtab col 1 client‑reachable + col 2 server‑reachable, min‑params 0 → L2). m_map has no hunt_server forward (it always dumps from &me), but dump_map/dump_map_sid format remote‑server fields (names, SID, version, usercount) only reachable with a linked peer → L2‑S2S. No L1 — pure wire handlers with no socket‑free data op, terminating in sendto_one (cf. P5ii/P5jj/m_list/m_names).
    • format! per user request — N/A. Every wire line goes straight to the C variadic sendto_one; dump_map_sid builds its per‑server token into buf via libc snprintf (faithful, not a Rust string), dump_map via libc strcat. Nothing assembled with format!.
    • L2 + L2‑S2S are the gates. golden_s_serv_map (L2): a lone registered client drives MAP018 RPL_MAPSTART + a single 015 (irc.test) + 017 RPL_MAPEND, then MAP s018 + a single 015 with the local server's usercount/SID/version — ref‑C == Rust byte‑identical. golden_s2s_s_serv_map (L2‑S2S): the multi‑server tree walk a lone server can't produce — bare MAP before vs after the peer links (the positive/inverse pair: the lone server sees only :irc.test 015 alice :irc.test, then after the link adds :irc.test 015 alice : \- peer.test), then MAP safter the link adds: `- peer.test 0 001B 0211030000(the only way to exercisedump_map_sid's remote serv->sid/verstr/usercntformatting; the peer is sent anEOBfirst soIsBursting(peer)is false).cargo build` 0 warnings; both touched golden binaries + the s_serv golden regression suite green.
  • 2026‑06‑04 — P5nn (s_serv.c TRACE cluster — m_trace/m_etrace/trace_one/count_servers_users) merged. The TRACE handler (s_serv.c:2745) + the extended‑TRACE handler m_etrace (s_serv.c:2862) + the per‑client renderer trace_one (s_serv.c:2656) + the count helper count_servers_users (s_serv.c:1653) ported into ircd-common/src/s_serv.rs, extending the existing s_serv_link.o partial port (‑DPORT_SERV_P5). Sixth s_serv.c sub‑phase. Plan: docs/superpowers/plans/2026-06-04-p5nn-s_serv-trace.md.

    • Guard + RED→GREEN. One contiguous #ifndef PORT_SERV_P5 guard around trace_one/m_trace/m_etrace (s_serv.c:2664‑2923, comment blocks included); m_sidtrace (the next fn, #ifdef ENABLE_SIDTRACE) left untouched. trace_one is file‑static with no remaining C caller (only m_trace×2 call it, both port here) → a private Rust unsafe fn (no cref_ oracle, cf. m_list/can_join/dump_map). m_trace/m_etrace's decls resolve via the parse.rs msgtab imports. RED confirmed exactly {m_trace, m_etrace, count_servers_users} undefined; nm target/debug/ircd | grep ' T (m_trace|m_etrace|count_servers_users)$' → all three defined from Rust; cargo build 0 warnings.
    • count_servers_users extern‑prototype switch. The static void count_servers_users(...) forward decl (s_serv.c:47) gives the function internal linkage despite its non‑static definition, so GCC inlined it into both call sites → no emitted symbol (absent from nm). It is still called by the C report_myservers (s_serv.c:1632, stays C) and by the now‑Rust trace_one, so it gets the P5u/P5x extern‑prototype switch: the forward decl flips staticextern under the guard, its body (1653‑1681) is guarded out, and the Rust #[no_mangle] pub extern "C" fn count_servers_users resolves the C remnant's call at link. HUB off → only the #else istat branch is ported: *servers = is_serv‑1, *users = is_user[0]+is_user[1]‑is_myclnt (the cptr arg unused); the u_long fields are wrapping_*‑combined then as c_int (faithful to C's u_longint, avoids a debug overflow panic in the nounwind extern fn).
    • Callees, all already resolved. C externs (bindgen) — get_client_name/get_client_class/get_client_ip/find_matching_client/find_sid/find_person/match_; the variadic sendto_one (local block, fmt *mut c_char). Rust — is_allowed (P5o), m_nopriv (P4), trace_one/count_servers_users (this port). Globals — local[]/highest_fd (incomplete‑array via the local_base() send.rs helper), me, timeofday, istat, replies (P3 reply()), IRC_VERSION. New inline macro helpers is_client/is_person/is_unknown/is_an_oper/send_wallops/my_client; reused is_server/is_me/my_connect/bad_to/me_name/dbuf_length.
    • Faithfulness notes. trace_one's status switch ported as a match (*acptr).status as c_int (status is c_short) over the nine STAT_* arms incl. the STAT_ME no‑op and the default RPL_TRACENEWTYPE; the STAT_SERVER case keeps both serv->user/!serv->user branches verbatim (the *serv->by ? by : "*" first‑char test, the masked‑vs‑real server fields). m_trace's passthru emits the volatile 200 RPL_TRACELINK (link‑uptime timeofday‑from->firsttime, two sendQ DBufLengths) ported faithfully but only diffed peer‑side in S2S (the deterministic signal is the forwarded :%s TRACE :%s). m_etrace is gated to my opers with ACL_TRACE; XLINE off → the user2/user3 columns are the "-","-" literals; the serv->user->{username,host}/acptr->user->{username,host} are [c_char;N] arrays passed via .as_ptr(). Never reads an aClient/aServer by value (raw‑pointer invariant). ENABLE_SIDTRACE offm_sidtrace not compiled, not ported.
    • Classification: handler cluster, no L1. TRACE {m_trace, m_trace, m_trace, m_trace, m_unreg} (any registered client, col 1 → L2); ETRACE {m_nop, m_nopriv, m_etrace, m_nop, m_unreg} (only STAT_OPER, col 2, reaches m_etrace; a plain client hits m_nopriv/481). m_trace's passthru + trace_one's STAT_SERVER case format remote‑server fieldsL2‑S2S; m_etrace walks only local persons (no S2S path of its own). No L1 — pure wire handlers terminating in sendto_one; count_servers_users is a data op but has no cref_ oracle (internal linkage in the oracle archive too) → L2‑S2S‑covered via the 206 servers/users it feeds (cf. P5jj check_link).
    • format! per user request — N/A. Every wire line goes straight to the C variadic sendto_one(replies[…], …); no ASCII/numeric reply string is assembled in Rust. get_client_name/get_client_ip return their own C buffers.
    • L2 + L2‑S2S are the gates. golden_s_serv_trace (L2): bare TRACE from a lone plain client → 205 RPL_TRACEUSER (self) + 262 RPL_TRACEEND; OPER alice secret+ETRACE via the new trace_oper.conf (O‑line t/ACL_TRACE flag) → 381 + 709 RPL_ETRACEFULL (self) + 759 RPL_ETRACEEND — byte‑identical ref‑C == Rust. (Symbolic RPL_ETRACEFULL=708 but the dumped replies[708] carries the wire numeric 709; both builds share the P3 table → bytes match.) golden_s2s_s_serv_trace (L2‑S2S): bare TRACE after Peer::link()206 RPL_TRACESERVER for the peer (the trace_one STAT_SERVER + count_servers_users path with the remote serv->version/by/user fields), diffed client‑side; TRACE peer.test → the IsServer passthru forwards the command, diffed peer‑side. cargo build 0 warnings; the two touched golden binaries green; no new canonicalizer masks (the 206/205/262/709/759 numerics carry no volatile token).
  • 2026‑06‑04 — P5oo (s_serv.c STATS / reporting cluster — m_stats + the five report_* reporters + report_array) merged. The STATS handler (s_serv.c:1882) + its file‑static reporters report_myservers (1619), report_configured_links (1756), report_ping (1833), report_fd (1854), report_listeners (3794) + the report_array[18][3] table (1713) ported into ircd-common/src/s_serv.rs, extending the existing s_serv_link.o partial port (‑DPORT_SERV_P5). Seventh s_serv.c sub‑phase — completes the cluster‑map "Stats / reporting" component (its siblings trace_one/check_link/count_servers_users already ported P5jj/P5nn). Plan: docs/superpowers/plans/2026-06-04-p5oo-s_serv-m_stats.md.

    • Guard + RED→GREEN. Three #ifndef PORT_SERV_P5 guards: the report_listeners forward decl (s_serv.c:48); the contiguous report_arrayreport_configured_linksreport_pingreport_fdm_stats span (with report_myservers guarded separately, since the already‑guarded count_servers_users sits between it and report_array); and the report_listeners body (3794). RED left exactly m_stats undefined in s_serv_link.o (the five reporters are static → no external reference; report_array is data) → nm target/debug/ircd | grep ' T m_stats$' defined from Rust after GREEN; cargo build 0 warnings.
    • All five reporters are private Rust unsafe fns. Each is file‑static in C with m_stats as its only caller (verified per‑symbol with grep) → since m_stats ports here too, no C caller remains → no extern‑prototype switch, no cref_ oracle (cf. P5mm dump_map, P5dd can_join). report_listeners additionally had a static forward decl → both decl + body guarded out.
    • report_array reproduced resolved to TKLINE‑on. A module‑private static REPORT_ARRAY: [[c_int; 3]; 18] — the 14 base rows + the two #ifdef TKLINE {CONF_T*KILL, RPL_STATSKLINE, 'k'/'K'} rows (present, not the #else {0,0,0} placeholders) + the {0,0,0} sentinel + the implicit 18th zero row. The C for (p = &report_array[0][0]; *p; p += 3) walk is ported as "iterate rows until row[0]==0"; the if (!*p) continue no‑match path preserved. XLINE offreport_x_lines (#ifdef XLINE) not compiled, not ported (STATS X is a no‑op).
    • Callees, all already resolved. C externs (bindgen imports) — report_iauth_conf (s_auth.c, USE_IAUTH on), tstats (s_bsd.c), get_conf_class/iline_flags_to_string/oline_flags_to_string/pline_flags_to_string (s_conf.c), find_client/get_client_name/hunt_server/is_allowed/mycmp/match_/inetntop; the variadic sendto_one (local block, fmt *mut c_char) + libc sprintf/strcpy/strchr/memcpy/isupper (new local extern decls; sprintf fmt *const c_char to avoid the clashing_extern_declarations with list.rs/channel.rs). Rust — send_defines/send_usage/count_memory (s_debug P5b), report_classes (class.c P2), check_link/count_servers_users (P5jj/P5nn). Globals — conf/kconf/tkconf/ListenerLL/iconf/fdas/ipv6string/msgtab/me/istat/timeofday/highest_fd/local[] (via local_base()). New inline helpers is_illegal/is_listener_inactive/is_conf_delayed/is_split; reused is_server/is_me/is_registered/is_masked/is_person/is_unknown/is_an_oper/send_wallops/my_connect/dbuf_length/bad_to/bad_ptr/me_name/reply/buf_base.
    • Faithfulness notes. The full selector switch ported as a match stat as u8 over the locked config (DISABLE_STATSTKLINE/TXT_NOSTATSK off → no oper gate on STATS k/K; DEBUGMODE off → STATS x no‑op and STATS Z/z both → count_memory(...,0); the Zz fallthrough collapsed into one arm). The IsServer(cptr) rate‑limit head (i/I/c/C/k/K/m/M/t/T/z/Zcheck_linkRPL_TRYAGAIN) + both hunt_server parc==3 (:%s STATS %s %s) / parc>=3 (:%s STATS %s %s %s) forms preserved. report_configured_links's five reply forms (K/V/H/Q passwd‑shown; TKLINE hold‑timeofday; CLIENT iline_flags; OPERATOR oline_flags; else) ported with the exact C arg types (char cc_int, portc_int, tmp->hold‑timeofdayc_long, iline/oline_flags_to_string((*tmp).flags: c_long)). The STATS m loop walks msgtab by raw pointer until cmd is null, summing the five handlers[n].count and emitting 212 RPL_STATSCOMMANDS (counters byte‑identical — the P4 dispatch invariant). report_ping/report_fd use the module BUF/ipv6string as in‑call scratch (faithful, cf. dump_map P5mm); report_myservers's %X serv->version is c_int, report_fd's idle ternary is c_long (the C time_t/-1 common type). Never reads an aClient/aConfItem by value (raw‑pointer invariant).
    • Classification: handler cluster, no L1. STATS {m_nop, m_stats, m_stats, m_nop, m_unreg} (registered client col 1 + server col 2, min‑params 1). The IsServer(cptr) head + hunt_server forward + report_myservers's remote‑server fields are server‑reachable → L2‑S2S. No L1 — every symbol is a pure wire reporter terminating in sendto_one (no socket‑free data op), and report_array is config‑gated data with no cref_ oracle (file‑static in the oracle too) → L2/L2‑S2S are the gates (cf. P5ii–P5nn / m_list / m_names).
    • format! per user request — N/A. Every wire line goes straight to the C variadic sendto_one(replies[…], …) (or the inline ":%s …" literals for report_myservers/report_fd/report_listeners); report_ping builds its name[host] token into BUF via libc sprintf/strcpy. Nothing assembled with format!.
    • L2 + L2‑S2S are the gates. golden_s_serv_stats (L2): a lone registered client under oper.conf drives STATS i215 RPL_STATSILINE and STATS o243 RPL_STATSOLINE (the report_configured_links walk over the I/O lines + iline/oline_flags), STATS y218 RPL_STATSYLINE (report_classes), STATS m212 RPL_STATSCOMMANDS (the msgtab loop) — each closing 219 RPL_ENDOFSTATS, byte‑identical ref‑C == Rust. The volatile selectors (l/L link info, P listeners, f/F fds, ? report_myservers, u uptime) carry per‑run sendQ/timeofday‑firsttime tokens (or are canonicalizer‑masked) → review/mask‑covered, not asserted here. golden_s2s_s_serv_stats (L2‑S2S): two server‑reachable facets after Peer::link() — (1) report_myservers via STATS ?249 RPL_STATSDEBUG with the peer name / 1S 0C (count_servers_users) / 0kB sent 0kB recv 0kB sq / BURST / %X version, diffed in full bar the masked (timeconnected) real‑clock token; (2) the hunt_server forward STATS m peer.test (parc==3) → the peer receives the byte‑identical forwarded command. cargo build 0 warnings; both touched golden binaries + the s_serv golden regression suite green.
  • 2026‑06‑04 — P5pp (s_serv.c m_motd handler) merged. The MOTD command handler (s_serv.c:2975) ported into ircd-common/src/s_serv.rs, extending the existing s_serv_link.o partial port (‑DPORT_SERV_P5). Eighth s_serv.c sub‑phase. Plan: docs/superpowers/plans/2026-06-04-p5pp-s_serv-m_motd.md.

    • Guard + RED→GREEN. One #ifndef PORT_SERV_P5 guard around the m_motd body (s_serv.c:2975‑3005). A leaf handler with no file‑static of its own and not anyone's static callee → a plain guard (no extern‑prototype switch); its decl resolves via the parse.rs msgtab imports. RED left exactly m_motd undefined (rust-lld: error: undefined symbol: m_motd); after GREEN nm target/debug/ircd | grep ' T m_motd$' → defined from Rust; cargo build 0 warnings.
    • Callees, all already resolved. Rust — check_link (P5jj), is_unknown/me_name/bad_to/reply (existing s_serv.rs helpers). C externs (bindgen) — hunt_server; the variadic sendto_one (local block, fmt *mut c_char). Globals — motd (*mut aMotd)/motd_mtime (time_t) bindgen statics; replies (P3 reply()). libc — localtime/tm for the date line.
    • Faithfulness notes. Exact control flow: the !IsUnknown(sptr) head guards both the check_linkRPL_TRYAGAIN(263) rate‑limit return (5) and the hunt_server(":%s MOTD :%s", 1, …) forward return (5); then localtime(&motd_mtime) (always called, before the null check, as in C), the motd==NULLERR_NOMOTD(422) early return (1), the RPL_MOTDSTART(375) + the inline :%s %d %s :- %d/%d/%d %d:%02d date line (RPL_MOTD=372 passed as the %d arg, not a replies[] index — verbatim s_serv.c:2997) + the for (temp=motd; …; temp=temp->next) listing loop emitting RPL_MOTD(372) per temp->line + RPL_ENDOFMOTD(376), and the IsUnknown(sptr) ? 5 : 2 tail. Never reads an aClient/aMotd by value (raw‑pointer invariant — (*temp).next/(*temp).line).
    • Classification: handler cluster, no L1. MOTD {m_nop, m_motd, m_motd, m_nop, m_unreg} (registered client col 1 + server col 2, min‑params 0). Pure wire handler terminating in sendto_one (no socket‑free data op, no cref_ oracle — cf. m_version/m_time/m_admin P5ii, m_list, m_names) → no L1. The hunt_server forward is server‑reachable → L2‑S2S required.
    • format! per user request — N/A. Every wire line goes straight to the C variadic sendto_one (the numeric replies via reply(), the date line via the inline :%s … literal). Nothing assembled with format!.
    • L2 + L2‑S2S are the gates. golden_s_serv_motd (L2): a lone registered client drives MOTD422 ERR_NOMOTD (:irc.test 422 alice :MOTD File is missing) under the no‑MOTD‑file test config — the realistic local path (exercises the !IsUnknown/check_link==0/hunt_server==HUNTED_ISME/motd==NULL flow), ref‑C == Rust byte‑identical. The full‑listing path (375/372 + the localtime date line/376) is not reached under this config — review‑covered (the date line is wall‑clock data from the MOTD file mtime; were a fixture MOTD added, both processes read the same file/mtime in one run → still byte‑identical, but the existing config keeps every other test on the 422 banner). golden_s2s_s_serv_motd (L2‑S2S): MOTD peer.test after Peer::link() → the peer receives the byte‑identical forwarded … MOTD … command (the hunt_server short‑circuit, the only server‑reachable path in m_motd), diffed peer‑side. cargo build 0 warnings; both new golden binaries + the s_serv golden regression suite (golden_s_serv_info/golden_s_serv_stats) green; no new canonicalizer masks (the 422 carries no volatile token).
  • 2026‑06‑04 — P5qq (s_serv.c server‑registration data‑structure cluster — find_server_string/find_server_num/add_server_to_tree/remove_server_from_tree/check_servername) merged. The server_name[] string‑interning pair (s_serv.c:3411/3428), the aServer‑tree pointer rewiring (s_serv.c:3574/3592), and the server‑name validator check_servername (s_serv.c:3519) ported into ircd-common/src/s_serv.rs, extending the existing s_serv_link.o partial port (‑DPORT_SERV_P5). Ninth s_serv.c sub‑phase — the first s_serv.c utility/callee cluster (pure data ops, no m_*/msgtab row). Plan: docs/superpowers/plans/2026-06-04-p5qq-s_serv-server-tree.md.

    • Guard + RED→GREEN. Three #ifndef PORT_SERV_P5 guards: the server_name/server_max/server_num statics + find_server_string+find_server_num (s_serv.c:3402‑3451); and the contiguous check_servernameadd_server_to_treeremove_server_from_tree span (s_serv.c:3509‑3619). RED left exactly find_server_string/find_server_num/check_servername/add_server_to_tree/remove_server_from_tree undefined (rust-lld: undefined reference to …), matching the nm checklist; after GREEN nm target/debug/ircd | grep ' T (…)$' → all five defined from Rust; cargo build 0 warnings.
    • check_servername_errors[3][2] stays C (faithfulness gotcha). The exported error‑message table is declared EXTERN const char *check_servername_errors[3][2]; in s_serv_ext.h, and with EXTERN expanding empty inside s_serv.c that decl is a C tentative definition → guarding only the initialized def (s_serv.c:56) leaves a zeroed BSS symbol (nm s_serv_link.oB check_servername_errors) that would collide with a Rust #[no_mangle] strong def (‑fno‑common, two strong defs = multiple‑definition link error). Since the ported check_servername never reads the table (it only returns the 1‑based rc index; the callers m_server/ircd.c boot index it), the table is left in C with a comment, to be ported when s_serv.o is dropped outright. The L1 still asserts c_check_servername_errors == cref_check_servername_errors (both the C table) as a regression check on the bytes the boot path depends on.
    • Module‑private statics (the file‑static server_name[]). SERVER_NAME: *mut *mut c_char/SERVER_MAX/SERVER_NUM: c_int — not ABI (cf. BUF/ASTERIX/REPORT_ARRAY). find_server_num grows the array by 50 (SERVER_MAX += 50 then MyRealloc(…, sizeof(*mut)*SERVER_MAX), or the first‑time MyMalloc(…SERVER_MAX=50)) and interns via mystrdup, byte‑for‑byte the C; find_server_string's bogus‑index path emits the sendto_flag(SCH_ERROR, …) notice verbatim (unreachable from valid callers).
    • Callees, all already resolved. Rust — MyMalloc/MyRealloc/mystrdup (P1/P2). libc (local extern block) — strcasecmp (case‑insensitive intern lookup), isdigit, strlen, abort (the remove_server_from_tree non‑leaf serv->down != NULL guard, ported verbatim). C variadic — sendto_flag (bindgen decl, error path only). ServerChannels_SCH_ERROR(=0)/HOSTLEN(=63) bindgen consts.
    • Faithfulness notes. check_servername mirrors C's signed‑char loop exactly: the char is read as c_char (signed i8 on x86‑64; unsigned u8 on this aarch64 dev boxc_char = u8), the a‑z/A‑Z/-/* range tests + isdigit(cc as c_int) preserve C's sign‑extension, the dots==0→rc 3 / chars==0→rc 2 post‑checks keep their order. add_server_to_tree caches up = serv->up and head‑inserts (down→new, new.right=old down, old down.left=new, servers++); remove_server_from_tree decrements servers, abort()s on a non‑leaf, then relinks right.left/left.right/up.down. Never reads an aClient/aServer by value (raw‑pointer invariant — (*(*acptr).serv).up chains).
    • Classification: utility/callee TU, L1 is the gate. No msgtab row for any of the five — they are unconditional callees of the still‑C server‑link/registration machinery (m_server/m_server_estab s_serv.c:568/656/804/821/1227/1235, exit_server s_misc.c:755, boot ircd.c:728/1053, register_user s_user.c:1412/2452). So L1 differential is the primary gate. No new L2/L2‑S2S — none has an IsServer(cptr) dispatch branch of its own (step‑7 "skip + note regression"): every symbol already runs on the existing 13 golden_s2s_* scenarios — peer link (find_server_num+add_server_to_tree+check_servername validating the incoming SERVER name), remote users (find_server_string for user->server), SQUIT (remove_server_from_tree) — which stay byte‑identical as the regression gate.
    • format! per user request — N/A. Pure data ops; no wire string is assembled (find_server_num/mystrdup copies the name; find_server_string's only output is the bogus‑index sendto_flag notice, a C trampoline).
    • L1 + S2S‑regression are the gates. s_serv_servtree_diff (L1, zero‑diff vs cref_): (1) server_name[] interning round‑trip — repeat returns the same index (a,b,a,c0,1,0,2), find_server_string(i) round‑trips each, growth past server_max=50 (intern 60 names → sequential 0..59, MyRealloc fires), inverse re‑interning a pre‑growth name keeps its original index (no duplicate slot, earlier strings survive the realloc); (2) aServer‑tree add A/B/C → chain [C,B,A]+servers==3+left‑links, remove leaf B → [C,A] relink + servers==2, remove head C → [A]+A.left==null, re‑add C → clean back to head — each step diffed Rust vs cref_ and against the analytic structure (so a faithful insert + botched removal is caught); (3) check_servername rc corpus (ok/too‑long/invalid‑char/no‑dot/dots‑only/digits/-/*/empty) + the error‑table bytes. cargo build 0 warnings; the full ircd-golden golden_s2s_*/golden_s_serv_* regression suite stays green (ref‑C == Rust). Pre‑existing aarch64 caveat: several older L1 tests (e.g. s_user_umode_diff.rs) hardcode i8 for c_char and fail to compile on this aarch64 box (where c_char = u8) — confirmed broken on clean HEAD, unrelated to this port; the new s_serv_servtree_diff.rs uses std::os::raw::c_char and is portable.
  • 2026‑06‑04 — P5rr (s_serv.c oper‑maintenance cluster — m_close + m_rehash) merged. The CLOSE handler (s_serv.c:3019, drop pending/unknown connections) + the REHASH handler (s_serv.c:2619, reload the server config) ported into ircd-common/src/s_serv.rs, extending the existing s_serv_link.o partial port (‑DPORT_SERV_P5). Tenth s_serv.c sub‑phase. Plan: docs/superpowers/plans/2026-06-04-p5rr-s_serv-maint.md.

    • Cluster choice. The remaining s_serv.c oper commands (m_die/m_restart/m_sdie/m_connect/m_squit/m_close/m_rehash/m_set/…) are independent leaves (no shared file‑static). m_close + m_rehash are the two with a fully L2‑testable, non‑destructive success path (m_die/m_restart terminate the process → their success is unreachable in a golden run); grouping them keeps the "fully verified" bar high.
    • Guard + RED→GREEN. Two plain #ifndef PORT_SERV_P5 guards — around m_rehash (s_serv.c:2619‑2631) and m_close (s_serv.c:3019‑3049). Neither is a file‑static or anyone's static callee → no extern‑prototype switch; their msgtab decls (parse.rs imports) resolve to the Rust defs at link. RED left exactly m_close/m_rehash undefined (undefined reference to 'm_close'/'m_rehash' from .data.msgtab), matching the nm checklist; after GREEN nm target/debug/ircd | grep ' T (m_close|m_rehash)$' → both defined from Rust; cargo build 0 warnings.
    • Config‑resolved bodies. DELAY_CLOSE on (config.h:440=15) → m_close's #ifdef DELAY_CLOSE tail (closed = istat.is_delayclosewait; delay_close(-2); sendto_flag(SCH_NOTICE,…)) is compiled and ported. USE_SYSLOG offm_rehash's #ifdef USE_SYSLOG syslog(LOG_INFO,…) line is not compiled/ported. OPER_REHASH defined (config.h:130) → ACL_REHASH is grantable (not stripped at s_conf.c:344); oper flag letters l→ACL_CLOSE, r→ACL_REHASH (s_conf.c:313/318).
    • Callees, all already resolved. Rust — is_allowed (P5o), m_nopriv (P4), exit_client (P5c), mybasename (P1); the s_serv.rs helpers reply/me_name/bad_to/is_unknown (+ new is_connecting/is_handshake inline helpers). C externs (bindgen) — rehash (s_conf.c), get_client_name, delay_close (s_bsd.c); the variadic sendto_one/sendto_flag (the *mut c_char fmt / u_int channel decls). Globals — configfile (*mut c_char static, read via *addr_of!), me (addr_of_mut!(me) for the exit_client from arg), highest_fd/local[] (via the existing local_base() helper), istat, replies (P3 reply()). Consts — ACL_CLOSE(0x80)/ACL_REHASH(0x400) as c_long, ServerChannels_SCH_NOTICE(=1) as c_uint, RPL_CLOSING(362)/RPL_CLOSEEND(363)/RPL_REHASHING(382).
    • Faithfulness notes. m_close's for (i = highest_fd; i; i--) walk descends to 1 and never touches local[0] (ported as let mut i = highest_fd; while i != 0 { let acptr = local[i]; i -= 1; … }); replies[RPL_CLOSING] carries get_client_name(acptr,TRUE) (string) + acptr->status passed as c_int (C default‑arg promotion of the c_short); each pending fd closed via exit_client(acptr, acptr, &me, "Oper Closing"). m_rehash returns rehash(cptr, sptr, (parc > 1) ? parv[1][0] : 0) — the third arg is the first char of parv[1] (**parv.add(1) as c_int), 0 when absent; RPL_REHASHING carries mybasename(configfile) and is emitted before the SCH_NOTICE (and before rehash() runs). Never reads an aClient by value (raw‑pointer invariant).
    • Classification: handler cluster, no L1, no S2S. Dispatch columns are indexed by STAT_* value (parse.c:688): CLOSE/REHASH = [m_nop(server), m_nopriv(client), <handler>(oper), m_nop(service), m_unreg]only a STAT_OPER (col 2) reaches the handler; a non‑oper client hits the col‑1 m_nopriv/481 at the parse table (never enters the handler). No L1 — both are pure wire handlers terminating in sendto_one/sendto_flag (no socket‑free data op, no cref_ oracle — cf. m_motd P5pp, m_map P5mm, m_list, m_names). No S2S (skip + note per the skill's Classify step): neither has an IsServer(cptr) dispatch branch nor formats remote‑user fields — m_close acts only on local local[] fds, m_rehash reloads local config; the existing 13 golden_s2s_* scenarios stay byte‑identical as the regression gate.
    • format! per user request — N/A. Every wire line goes straight to the C variadic sendto_one(reply(…),…) (the numerics) or sendto_flag(SCH_NOTICE, "…%s…", …) (the oper notices). Nothing assembled with format!.
    • L2 is the gate. golden_s_serv_maint (4 scenarios, ref‑C == Rust byte‑identical after canonicalize()): the new maint_oper.conf (O‑line flags lr = ACL_CLOSE|ACL_REHASH) drives OPER alice secret then — REHASH → 382 RPL_REHASHING (the success path; rehash() reloads the identical config in both processes), CLOSE → 363 RPL_CLOSEEND (closed 0 — a lone client has no pending UNKNOWN/CONNECTING/HANDSHAKE fds); the inverse via oper.conf (O‑line with no flags) drives REHASH/CLOSE → 481 ERR_NOPRIVILEGES (the handler‑internal is_allowedm_nopriv branch, distinct from the col‑1 parse‑table reject). cargo build 0 warnings; the new golden binary + the s_serv golden regression suite (golden_s_serv_trace/golden_s_serv_stats) green; no new canonicalizer masks (382/363/481 carry no volatile token, and REHASH is read only through its 382 line before any wall‑clock notice).
  • 2026‑06‑04 — P5ss (s_serv.c end‑of‑burst cluster — m_eob + m_eoback) merged. The EOB handler (s_serv.c:3059 — a linked server signals its burst is complete) + the EOBACK ack (s_serv.c:3205 — clears our connection‑burst flag) ported into ircd-common/src/s_serv.rs, extending the existing s_serv_link.o partial port (‑DPORT_SERV_P5). Eleventh s_serv.c sub‑phase. Plan: docs/superpowers/plans/2026-06-04-p5ss-s_serv-eob.md.

    • Cluster choice. The remaining s_serv.c handlers are independent leaves (m_die/m_restart/m_sdie/m_connect/m_squit/m_set/m_smask/m_wallops/m_error/m_encap/m_eob/m_eoback) plus the protocol‑critical server‑introduction/burst recursive cluster (m_server/m_server_estab/introduce_server/send_server/send_server_burst/…). m_eob + m_eoback are the thematic end‑of‑burst pair: server‑only, already exercised in C by golden_s2s_s_serv_map (which sends a peer EOB), with a clean directly‑observable peer‑socket output (the EOBACK reply). Porting them advances s_serv.c without touching the burst recursion.
    • Guard + RED→GREEN. One contiguous #ifndef PORT_SERV_P5 guard spanning both bodies (s_serv.c:3059‑3214). Neither is a file‑static or anyone's static callee → no extern‑prototype switch; their msgtab decls (parse.rs imports) resolve to the Rust defs at link. RED left exactly m_eob/m_eoback undefined (undefined reference to 'm_eob'/'m_eoback' from .data.msgtab), matching the nm checklist; after GREEN nm target/debug/ircd | grep ' T (m_eob|m_eoback)$' → both defined from Rust; cargo build 0 warnings.
    • Config‑resolved bodies. No #ifdef inside either body under the locked config (USE_SERVICES off — the nearby check_services_butone belongs to m_wallops, not this cluster). Both compile unconditionally.
    • Callees, all already resolved. Rust — strtoken (P1), find_sid (P4), exit_client/check_split (P5c), istat (P2). C variadic (trampolines, P8) — sendto_one/sendto_flag (already in the s_serv.rs extern/bindgen set), sendto_serv_v (bindgen import, SV_UID). libc — memcpy (the existing reporter extern block). Globals — me/me.serv->sid, timeofday. New inline helper set_eob (flags |= FLAGS_EOB, struct_def.h:239); the rest (is_server/is_bursting/my_connect/is_masked) already in s_serv.rs. SIDLEN(=4) baked (cf. parse.rs/s_user.rs); SV_UID/FLAGS_CBURST/FLAGS_EOB/SCH_NOTICE/SCH_ERROR/SCH_SERVER/SCH_DEBUG bindgen consts.
    • Faithfulness notes. First guard if (!IsServer(sptr)) return 0; drops a non‑server prefix. IsBursting(sptr) arm: MyConnect(sptr) → the wall‑clock‑volatile "End of burst from %s after %d seconds." SCH_NOTICE ((timeofday - sptr->firsttime) as c_int, cf. m_map); the remote arm → "End of burst from %s" only when !IsMasked. Then SetEOB(sptr), istat.is_eobservers += 1, and (hopcount == 1, directly linked) sendto_one(sptr, ":%s EOBACK", me.serv->sid). parc<2sendto_serv_v(sptr, SV_UID, ":%s EOB", sptr->serv->sid) + return 1; the !IsBursting && parc<2 arm → the "Received another EOB" SCH_ERROR + return 1. The parc≥2 mass‑SID loop: eobmaxlen = BUFSIZE - 1 - SIDLEN - 6 - 2 - (SIDLEN+1); the for (; (sid = strtoken(&p, parv[1], ",")); parv[1] = NULL) C idiom ported as a loop { … s = null } with p state; find_sid(sid, NULL) → unknown‑SID / wrong‑direction (acptr->from != cptr) both return exit_client(cptr, cptr, &me, …) (link drop), already‑EOB'd acptr → SCH_ERROR + continue, else SCH_DEBUG + (overflow flush :%s EOB :%s via eobbuf+1) + SetEOB(acptr) + is_eobservers++ + append ,<sid> (*e++ = ','; memcpy(e, acptr->serv->sid, SIDLEN); e += SIDLEN). eobbuf is a function‑local stack array (not a file‑static) → a Rust let mut eobbuf = [0 as c_char; BUFSIZE] with e/base raw‑pointer walk (e.offset_from(base) for the (e - eobbuf) overflow test, base.add(1) for the leading‑comma skip). After the loop, flush the remainder (:%s EOB :%s) or a bare :%s EOB, then check_split(). m_eoback: cptr->flags &= ~FLAGS_CBURST; if (cptr->serv && cptr->serv->byuid[0]) cptr->serv->byuid[0] = '\0';. Never read an aClient/aServer by value (raw‑pointer invariant — (*(*acptr).serv).sid, (*(*acptr).from).name chains).
    • Classification: handler cluster, server‑reachable → L2‑S2S is the gate. Dispatch columns indexed by STAT_* value (parse.c:688): EOB/EOBACK = [m_eob/m_eoback (server), m_nop (client), m_nop (oper), m_nop (service), m_unreg]only a STAT_SERVER prefix (col 0) reaches the handler (m_eob's IsServer(sptr) guard is redundant‑but‑faithful). No L1 — pure server‑protocol handlers terminating in sendto_* + aClient flag mutation, no socket‑free data op with a cref_ oracle (the state is reachable only over a link; cf. m_njoin P5ee). No plain L2 — a local client can't present a STAT_SERVER prefix → unreachable from the #‑channel harness. L2‑S2S only (the skill's "server‑reachable path" → don't defer).
    • format! per user request — N/A. Every wire line goes straight to a C variadic sender (sendto_one/sendto_flag/sendto_serv_v); the only assembled bytes are the eobbuf comma‑SID list, built with memcpy byte‑for‑byte as the C (a format! would change the layout vs the reference and break the L2‑S2S diff).
    • L2‑S2S is the gate. golden_s2s_eob (ref‑C == Rust byte‑identical after canonicalize()): a freshly‑linked bursting peer's bare EOB:000A EOBACK reply on the peer socket (000A = local SID, fixtures/s2s.conf M‑line); the inverse — a second bare EOB now finds !IsBurstingno second EOBACK (proving SetEOB actually changed state, not just the happy path); m_eoback (peer sends EOBACK) → the link survives (a follow‑up remote‑user introduce + WHOIS bob → 311 still resolves). golden_s2s_s_serv_map (which drives a peer EOB for its non‑bursting MAP s branch) stays byte‑identical as the regression gate, as do golden_s2s_link/golden_s2s_squit. The parc≥2 mass‑SID multi‑server loop is review‑covered — the single‑peer harness reaches only the headline directly‑linked bare‑EOB path. cargo build (workspace) 0 warnings.
  • 2026‑06‑04 — P5tt (s_serv.c m_set handler — the SET oper command) merged. The SET handler (s_serv.c:3252 — read/change the runtime tunables POOLSIZE/ACONNECT/CACCEPT and echo each value back as a NOTICE) ported into ircd-common/src/s_serv.rs, extending the existing s_serv_link.o partial port (‑DPORT_SERV_P5). Twelfth s_serv.c sub‑phase. Plan: docs/superpowers/plans/2026-06-04-p5tt-s_serv-m_set.md.

    • Cluster choice. The remaining s_serv.c handlers are independent leaves (m_die/m_restart/m_sdie/m_connect/m_squit/m_set/m_smask/m_wallops/m_error/m_encap) plus the protocol‑critical server‑introduction/burst recursive cluster (m_server/m_server_estab/register_server/introduce_server/send_server/send_server_burst/get_version/check_version). m_set is the one remaining oper handler with a fully L2‑testable, non‑destructive success path (m_die/m_restart/m_sdie terminate the process → their success is unreachable in a golden; m_smask is server‑only and tied to the burst cluster's get_version/introduce_server/register_server). Porting it advances s_serv.c without touching the burst recursion.
    • Guard + RED→GREEN. One plain #ifndef PORT_SERV_P5 guard around m_set (s_serv.c:3252‑3403). Not a file‑static or anyone's static callee → no extern‑prototype switch; its msgtab decl (parse.rs imports) resolves to the Rust def at link. RED left exactly m_set undefined (undefined reference to 'm_set' from .data.msgtab), matching the nm checklist; after GREEN nm target/debug/ircd | grep ' T m_set$' → defined from Rust; cargo build 0 warnings.
    • Config‑resolved bodies. No #ifdef inside the body under the locked config — set_table[] is unconditional (POOLSIZE/ACONNECT/CACCEPT). TSET_SHOWALL = (int) ~0 = ‑1 (struct_def.h:1029) — bindgen drops the cast macro, so baked as const TSET_SHOWALL: c_int = -1 (all bits set → the no‑arg form's three acmd & TSET_* echoes all fire). TSET_POOLSIZE(2)/TSET_ACONNECT(1)/TSET_CACCEPT(4) are bindgen consts.
    • Callees, all already resolved. Rust — is_allowed (P5o), m_nopriv (P4), mycmp (P1); the s_serv.rs me_name() helper (ME). C externs (bindgen) — activate_delayed_listeners (s_bsd.c), the variadic sendto_one/sendto_flag (the *mut c_char fmt / u_int channel decls); libc atoi (added to the s_serv.rs extern block). Globals — iconf (iconf_t static, .aconnect/.caccept read/write), poolsize (u_int static), firstrejoindone (c_int static). Consts — ACL_SET(8192) as c_long, ServerChannels_SCH_NOTICE(=1) as c_uint.
    • Faithfulness notes. The C switch(acmd){…break;…} followed by the trailing if(acmd){ echo POOLSIZE/ACONNECT/CACCEPT } block is refactored into a set_finish(sptr, parv0, acmd) helper: the two "Illegal value for ACONNECT/CACCEPT" arms break out of the switch straight to the echo block → ported as an early return set_finish(...) (acmd is unchanged, so the echo for the recognized option still fires — exactly the C). poolsize > tmp preserves the C usual‑arithmetic signed→unsigned promotion (poolsize > tmp as c_uint); poolsize/tmp passed to %d are width‑matched (poolsize as c_int, tmp is c_int). The invalid‑option path (acmd == 0) emits ":%s NOTICE %s :Invalid option %s" with parv[0] directly (no BadTo, matching the C). The ternary %s value strings ("ND"/"ON"/"OFF"/"SPLIT") passed as c"…".as_ptr() as *mut c_char. Never reads an aClient by value (raw‑pointer invariant — (*sptr).name).
    • Classification: handler cluster, no L1, no S2S. Dispatch columns indexed by STAT_* value (parse.c:688): SET = [m_nop (server), m_nopriv (client), m_set (oper), m_nop (service), m_unreg]only a STAT_OPER (col 2) reaches the handler; a non‑oper client hits the col‑1 m_nopriv/481 at the parse table (never enters the handler), and the body re‑gates on is_allowed(sptr, ACL_SET). No L1 — a pure wire handler terminating in sendto_one/sendto_flag (no socket‑free data op, no cref_ oracle — cf. m_close/m_rehash P5rr, m_motd P5pp). No S2S (skip + note per the skill's Classify step): no IsServer(cptr) dispatch branch, no remote‑user fields — m_set reads/writes only local tunables (iconf/poolsize); the existing 13 golden_s2s_* scenarios stay byte‑identical as the regression gate.
    • format! per user request — N/A. Every wire line goes straight to a C variadic sender (sendto_one(":%s NOTICE %s :…", …) for the per‑client echoes / sendto_flag(SCH_NOTICE, "%s changed value of …", …) for the oper notices). Nothing assembled with format!.
    • L2 is the gate. golden_s_serv_set (4 scenarios, ref‑C == Rust byte‑identical after canonicalize()): the new set_oper.conf (O‑line flag e = ACL_SET, s_conf.c:321) drives OPER alice secret then — bare SET → the showall path (acmd = TSET_SHOWALL) echoing :POOLSIZE = N / :ACONNECT = OFF / :CACCEPT = ON; SET ACONNECT OFF → apply + the :ACONNECT = OFF echo; SET BOGUS:… :Invalid option BOGUS with no trailing echo (acmd stays 0 — the inverse of a recognized option); the inverse via oper.conf (O‑line with no flags) drives SET481 ERR_NOPRIVILEGES (the handler‑internal is_allowed(ACL_SET)m_nopriv branch, distinct from the col‑1 parse‑table reject). cargo build 0 warnings; the new golden binary + the s_serv golden regression suite (golden_s_serv_maint/golden_s_serv_stats/golden_s_serv_stubs) green; no new canonicalizer masks (the POOLSIZE value + the echo strings are deterministic under the pinned harness). Pre‑existing flake noted (not P5tt): golden_s2s_s_serv_stats diverges on the volatile …kB sq (peer sendQ) field of report_myservers — reference‑C reads uninitialized memory for a freshly‑linked fake peer (value varies run‑to‑run); confirmed failing identically on clean HEAD (pre‑P5tt), a masking gap in that test from P5oo, untouched by this cluster.
  • 2026‑06‑04 — P5uu (s_serv.c m_wallops handler — the WALLOPS broadcast) merged. The WALLOPS handler (s_serv.c:2561 — relay an operwall to every other server and emit it to local &WALLOPS watchers) ported into ircd-common/src/s_serv.rs, extending the existing s_serv_link.o partial port (‑DPORT_SERV_P5). Thirteenth s_serv.c sub‑phase. Plan: docs/superpowers/plans/2026-06-04-p5uu-s_serv-m_wallops.md.

    • Cluster choice. The remaining s_serv.c handlers are independent leaves (m_squit/m_smask/m_error/m_connect/m_wallops/m_restart/m_sidtrace/m_die/m_encap/m_sdie) plus the protocol‑critical server‑introduction/burst recursive cluster (m_server/m_server_estab/introduce_server/send_server/send_server_burst). m_wallops is the smallest remaining leaf with a fully observable, non‑destructive S2S path (a single sendto_ops_butone), advancing s_serv.c without touching the burst recursion or the process‑terminating handlers (m_die/m_restart/m_sdie).
    • Guard + RED→GREEN. One plain #ifndef PORT_SERV_P5 guard around m_wallops (s_serv.c:2561‑2569). Not a file‑static or anyone's static callee → no extern‑prototype switch; its msgtab decl (parse.rs imports) resolves to the Rust def at link. RED left exactly m_wallops undefined (undefined reference to 'm_wallops' from .data.msgtab), matching the nm checklist; after GREEN nm target/debug/ircd | grep ' T m_wallops$' → defined from Rust; cargo build (workspace) 0 warnings.
    • Config‑resolved body. USE_SERVICES off → the check_services_butone(SERVICE_WANT_WALLOP, …) block is not compiled and not ported; sptr/parc are then unused (_sptr/_parc). No other #ifdef in the body.
    • Callees, all already resolved. The entire body is sendto_ops_butone(IsServer(cptr) ? cptr : NULL, parv[0], "%s", parv[1]). sendto_ops_butone is a C variadic (the P8 trampoline, common/send.c:987 — it sendto_serv_butones :%s WALLOPS :%s to every other server and sendto_flag(SCH_WALLOP)s the !from! %s framing to local &WALLOPS members); stable Rust can call a variadic extern, so it is imported from bindings (not redeclared). is_server() is the existing s_serv.rs helper.
    • Faithfulness notes. IsServer(cptr) ? cptr : NULLif is_server(cptr) { cptr } else { core::ptr::null_mut() }, ported verbatim (under the col‑0‑only dispatch cptr is always a server, so it always yields cptr — the origin‑link exclusion that keeps the WALLOPS from looping back). The "%s"/parv[1] pass straight through; the !from! framing + the vsprintf happen inside the C trampoline, so no bytes are assembled in Rust. Never reads an aClient by value (raw‑pointer invariant — only is_server(cptr) touches (*cptr).status).
    • Classification: handler cluster, server‑reachable → L2‑S2S is the only gate. Dispatch columns indexed by STAT_* value (parse.c:688): WALLOPS = [m_wallops (server), m_nop (client), m_nop (oper), m_nop (service), m_unreg]only a STAT_SERVER prefix (col 0) reaches the handler; a local client/oper/service all hit m_nop. No L1 — a pure wire handler terminating in the variadic, no socket‑free data op with a cref_ oracle (cf. m_eob/m_eoback P5ss). No plain L2 — a local client can't present a STAT_SERVER prefix, so the standalone #‑channel harness can't reach it. L2‑S2S only (the skill's "server‑reachable path" → don't defer).
    • format! per user request — N/A. The single wire call is the C variadic sendto_ops_butone; nothing is assembled with format! (a Rust format! here would diverge from the byte‑exact trampoline framing the reference produces).
    • L2‑S2S is the gate. golden_s2s_wallops (ref‑C == Rust byte‑identical after canonicalize() on all three transcripts): a freshly‑linked peer's :001B WALLOPS :… → a local &WALLOPS member (alice) receives :irc.test NOTICE &WALLOPS :!peer.test! the building is on fire (the sendto_flag(SCH_WALLOP) path; from = parv[0] resolves to the source server name, cf. the :peer.test resolution in golden_s2s_kill). Inverse 1 (scoping): a non‑member (carol, fenced by a PING/PONG round‑trip) receives nothing. Inverse 2 (the one=cptr exclusion — the very reason this is the server path): the originating peer does not get its own WALLOPS echoed back, fenced by a PRIVMSG to a remote user that routes out the peer link (a wrongly‑NULL one would loop the WALLOPS back to the peer). cargo build (workspace) 0 warnings; the s_serv golden + golden_s2s_link regression stay green.
  • 2026‑06‑04 — P5vv (s_serv.c m_error handler — the server‑protocol ERROR message) merged. The ERROR handler (s_serv.c:2245 — pass an error received over a server link on to local &ERRORS watchers) ported into ircd-common/src/s_serv.rs, extending the existing s_serv_link.o partial port (‑DPORT_SERV_P5). Fourteenth s_serv.c sub‑phase.

    • Cluster choice. A leaf server‑only handler with a fully observable, non‑destructive S2S path (a single sendto_flag(SCH_ERROR)), advancing s_serv.c without touching the burst recursion (m_server/m_server_estab/introduce_server/send_server/send_server_burst) or the process‑terminating handlers (m_die/m_restart/m_sdie). Structurally parallel to P5uu m_wallops, so the L2‑S2S harness pattern is proven.
    • Guard + RED→GREEN. One plain #ifndef PORT_SERV_P5 guard around m_error (s_serv.c:2245‑2268). Not a file‑static or anyone's static callee → no extern‑prototype switch; its msgtab decl resolves to the Rust def at link. RED left exactly m_error undefined (undefined reference to 'm_error' from .data.msgtab); after GREEN nm target/debug/ircd | grep ' T m_error$' → defined from Rust; cargo build (workspace) 0 warnings.
    • Config‑resolved body. DEBUGMODE off → the Debug((DEBUG_ERROR, …)) line is not compiled and not ported. No other #ifdef in the body.
    • Callees, all already resolved. para = (parc>1 && *parv[1]!='\0') ? parv[1] : "<>". The reachable server branch is sendto_flag(SCH_ERROR, "from %s -- %s", get_client_name(cptr,FALSE), para) (cptr==sptr, direct link) / sendto_flag(SCH_ERROR, "from %s via %s -- %s", sptr->name, get_client_name(cptr,FALSE), para) (the "via" form). sendto_flag is the C variadic P8 trampoline (it vsprintfs nbuf then sendto_channel_butservs :%s NOTICE %s :%s = :irc.test NOTICE &ERRORS :<nbuf> to local &ERRORS members) — imported from bindings, not redeclared; get_client_name already resolves (P5 import); SCH_ERROR = ServerChannels_SCH_ERROR.
    • Faithfulness notes. name is a *mut c_char self‑pointer → (*sptr).name passed straight to %s (no .as_mut_ptr()). Returns 2 (not FLUSH_BUFFER = ‑2) → parse keeps the link. The IsPerson(cptr)||IsUnknown(cptr)||IsService(cptr) → return 2 ignore guard is ported verbatim for faithfulness but is unreachable via the col‑0‑only dispatch (those client types hit m_nop/m_unreg, never m_error). New private is_service helper (IsService macro, struct_def.h:141 — status==STAT_SERVICE && service). Never reads an aClient by value (raw‑pointer invariant).
    • Classification: handler cluster, server‑reachable → L2‑S2S is the only gate. Dispatch columns indexed by STAT_*: ERROR = [m_error (server), m_nop (client), m_nop (oper), m_nop (service), m_unreg]only a STAT_SERVER prefix (col 0) reaches the handler. No L1 — a pure wire handler terminating in the variadic, no socket‑free data op with a cref_ oracle. No plain L2 — a local client can't present a STAT_SERVER prefix.
    • format! per user request — N/A. The single wire call is the C variadic sendto_flag; nothing is assembled with format! (a Rust format! here would diverge from the byte‑exact trampoline framing).
    • L2‑S2S is the gate. golden_s2s_error (ref‑C == Rust byte‑identical after canonicalize() on both transcripts): a freshly‑linked peer's :001B ERROR :the disk is full → a local oper &ERRORS member (alice) receives :irc.test NOTICE &ERRORS :from peer.test -- the disk is full. Inverse (scoping): a non‑member (carol, fenced by a PING/PONG round‑trip) receives nothing. The boot‑created &ERRORS server channel is +i (invite‑only) — can_join (channel.c:2091) admits it only for an oper — so the new s2s_oper.conf fixture = s2s.conf (same M/C/N/H lines the Peer identity needs) plus an O|*@*|secret|alice|0|10| line, and alice OPERs before subscribing. The cptr != sptr "via" form (needs a server introduced behind the peer — the same sendto_flag, a different format string) is review‑covered. cargo build (workspace) 0 warnings; golden_s2s_wallops regression stays green.
  • 2026‑06‑04 — P5ww (s_serv.c server network‑registration cluster — register_server + unregister_server) merged. The svrtop doubly‑linked‑list (aServer nexts/prevs/bcptr) manipulators (s_serv.c:3922/3940 — head‑insert / unlink a server's aServer whenever it (re)associates or loses its client struct) ported into ircd-common/src/s_serv.rs, extending the existing s_serv_link.o partial port (‑DPORT_SERV_P5). Fifteenth s_serv.c sub‑phase.

    • Cluster choice. The first pure utility/callee s_serv.c cluster since P5qq — two tiny intrusive‑list ops with no msgtab row, no socket, no wire — advancing s_serv.c without touching the burst recursion (m_server/m_server_estab/introduce_server/send_server/send_server_burst) or the process‑terminating handlers (m_die/m_restart/m_sdie). Structurally parallel to P5qq's add_server_to_tree/remove_server_from_tree (a different list — svrtop network registration vs the serv->down/right tree), so the L1‑differential harness pattern is proven.
    • Guard + RED→GREEN. One plain #ifndef PORT_SERV_P5 guard around register_server+unregister_server (s_serv.c:3922‑3956). Both are EXTERN (s_serv_ext.h:79‑80), not file‑static and not anyone's static callee → no extern‑prototype switch; their callers (boot ircd.c:814/1095, link estab s_serv.c:568/816/1238, s_misc.c:757 exit_one_client) resolve to the Rust defs at link. RED left exactly register_server + unregister_server undefined; after GREEN nm target/debug/ircd | grep -E ' T (register_server|unregister_server)$' → both defined from Rust; cargo build (workspace) 0 warnings.
    • Config‑resolved body. No #ifdef in either body (DEBUGMODE‑gated servs.inuse accounting lives in make_server/free_server, not here). Faithful pointer rewiring: register_server head‑inserts (svrtop->prevs = cptr->serv; cptr->serv->nexts = svrtop; svrtop = cptr->serv); unregister_server splices out (nexts->prevs/prevs->nexts relink, svrtop advance when removing the head) then clears prevs/nexts/bcptr. Operates only through the cptr->serv raw pointer (raw‑pointer invariant — never an aServer by value).
    • Classification: utility/callee TU → L1 is the gate. No msgtab row (not an m_* handler), no IsServer(cptr) dispatch branch of its own, no remote‑user field formatting → no new L2/S2S (cf. P5qq). The two are unconditional callees of the still‑C link handler / exit cascade.
    • format! per user request — N/A. No wire string — pure list‑pointer arithmetic.
    • L1 is the gate. s_serv_register_diff.rs (server_register_unregister_matches_reference, zero‑diff vs the cref_* oracle on parallel svrtop lists built via make_client/make_server): register A/B/C head‑inserts → chain [C,B,A] with the full nexts/prevs cross‑link assertions (C.prevs=∅, C.nexts=B, B.prevs=C, B.nexts=A, A.prevs=B, A.nexts=∅). Inverses: unregister the middle B (relink C.nexts=A / A.prevs=C; B's nexts/prevs/bcptr all cleared — bcptr stamped by hand first since make_server zeroes it), the head C (svrtop advances to A, A.prevs=∅), then the sole node A (svrtop empties); finally re‑register A then C → clean [C,A] round‑trip with no stale linkage. A faithful insert + botched removal passes a positive‑only test and corrupts the network server list — the inverses are the real gate.
    • Regression gate. The existing golden_s2s_* peer‑link/SQUIT scenarios exercise both live: a link runs register_server (golden_s2s_link — 2 tests green), a SQUIT/exit_server runs unregister_server (golden_s2s_squit — green), both still byte‑identical (ref‑C == Rust) after the port. cargo build (workspace) 0 warnings.
  • 2026‑06‑04 — P5xx (s_serv.c operator CONNECT handler — m_connect) merged. The CONNECT handler (s_serv.c:2429 — an oper forces an outbound server link) ported into ircd-common/src/s_serv.rs, extending the existing s_serv_link.o partial port (‑DPORT_SERV_P5). Sixteenth s_serv.c sub‑phase.

    • Cluster choice. A self‑contained msgtab handler leaf (only a local port/tmpport/aconf — no file‑static shared with another cluster), advancing s_serv.c without touching the burst recursion (m_server/m_server_estab/introduce_server/send_server/send_server_burst) or the process‑terminating handlers (m_die/m_restart/m_sdie). Unlike P5uu/P5vv (server‑only leaves) this is the first s_serv.c handler reached from a local oper and — over a link — a forwarded remote oper, so it carries both an L2 and an L2‑S2S gate.
    • Guard + RED→GREEN. One plain #ifndef PORT_SERV_P5 guard around m_connect (s_serv.c:2429‑2557). Not a file‑static or anyone's static callee → no extern‑prototype switch; its msgtab decl (parse.rs imports) resolves to the Rust def at link. RED left exactly m_connect undefined (undefined reference to 'm_connect' from .data.msgtab), matching the nm checklist; after GREEN nm target/debug/ircd | grep ' T m_connect$' → defined from Rust; cargo build (workspace) 0 warnings.
    • Config‑resolved body. USE_SYSLOG / SYSLOG_CONNECT off → the syslog(LOG_DEBUG, "CONNECT …") line inside the !IsAnOper(cptr) block is not compiled and not ported. No other #ifdef in the body.
    • Callees, all already resolved. is_allowed/m_nopriv/hunt_server/find_name/find_mask/match_/get_client_name/connect_server from bindings (the still‑C definitions); conf global walked directly; sendto_one/sendto_flag/sendto_ops_butone are the C variadic P8 trampolines (sendto_one via the module's local extern "C" block, sendto_flag/sendto_ops_butone imported — stable Rust can call a variadic extern). New libc externs abs + strerror (faithful to the C abs(tmpport) / strerror(retval)). is_an_oper() is the existing s_serv.rs helper.
    • Faithfulness notes. is_allowed(sptr, parc>3 ? ACL_CONNECTREMOTE : ACL_CONNECTLOCAL)==0 → m_nopriv gate; BOOT_STANDALONE short‑circuit (unset in the -t -s L2 boots but ported); hunt_server(…,3,…) != HUNTED_ISME → return 1; the find_name || find_mask "already exists from acptr->from->name" notice; the two‑pass conf walk (CONF_CONNECT_SERVER/CONF_ZCONNECT_SERVER by name, then by host) where the C index(aconf->host,'@')+1 is replicated as strchr((*aconf).host, b'@').wrapping_add(1)wrapping_add mirrors C's NULL+1 = (char*)1 when the host has no @ (the short‑circuited || keeps it unreached for @‑less names that match the first match_); port resolution (atoi(parv[2]) / abs(tmpport) / the port<=0 "Illegal port" guard); the !IsAnOper(cptr) "Remote CONNECT %s %d from %s" ops notice; and the connect_server retval switch (0 Connecting / ‑1 Couldn't / ‑2 unknown host / default strerror), restoring aconf->port = tmpport. The local me_name() binding is named myname (a let me would shadow the imported static me). Never reads an aClient/aConfItem by value (raw‑pointer invariant).
    • Classification: handler cluster, server‑reachable → L2 + L2‑S2S the gates. Dispatch columns indexed by STAT_* (parse.c:688): CONNECT = [m_nop (server), m_nopriv (client), m_connect (oper), m_nop (service), m_unreg] — col 2 (STAT_OPER) only; a plain client hits m_nopriv (481) at the parse table. No L1 — every reachable branch needs a live server/socket; there is no socket‑free data op with a cref_ oracle.
    • format! per user request — N/A. Every wire line is a C variadic (sendto_one/sendto_flag/sendto_ops_butone); a Rust format! would diverge from the byte‑exact trampoline framing the reference produces.
    • L2 is a gate. golden_s_serv_connect (ref‑C == Rust byte‑identical after canonicalize()): new connect_oper.conf fixture (O‑line flags C = ACL_CONNECT) → opered alice runs CONNECT nosuch.invalidNOTICE alice :Connect: Host nosuch.invalid not listed in ircd.conf (the conf walk misses → returns before connect_server, so no live socket / nondeterminism). Inverse via plain oper.conf (no O‑line flags) → the handler‑internal is_allowed(ACL_CONNECTLOCAL) reject → 481 ERR_NOPRIVILEGES (the return m_nopriv(...) branch, distinct from the col‑1 parse‑table m_nopriv).
    • L2‑S2S is a gate. golden_s2s_s_serv_connect (boot_s2s + Peer, ref‑C == Rust byte‑identical): a remote oper (001BAAAAA, umode +o → STAT_OPER via SetClient) behind the peer issues a remote‑form CONNECT nosuch.invalid 0 irc.test — arriving over the link the prefix resolves to the remote oper (STAT_OPER, col 2) while cptr is the server link, so m_connect runs with IsServer(cptr) true (the only non‑MyConnect entry); is_allowed short‑circuits to 1 for a non‑local client, the conf walk misses, and the "not listed" notice routes back out the peer to oper1. Inverse: a plain remote client (+, col 1 → m_nopriv at parse) issuing the same CONNECT produces no such notice — proving the col‑2 dispatch is gated on the prefix being an oper, not merely arriving over a server link. The !IsAnOper(cptr) "Remote CONNECT" ops notice + the connect_server retval tail run only once a real C‑line matches the host (a live outbound socket) → no deterministic golden form; faithful C‑call passthroughs covered by review (noted in the test docs). cargo build (workspace) 0 warnings; golden_s2s_link / golden_s2s_squit / golden_s_serv_maint regression stay byte‑identical.
  • 2026‑06‑04 — P5yy (s_serv.c m_squit) merged. Ported the SQUIT server-removal handler to ircd-common/src/s_serv.rs via the s_serv_link.o partial port.

    • Cluster choicem_squit is a connected component of one: its sole module-local state is the function-local static char comment2[TOPICLEN+1] (the rewritten "<comment> (by <nick>)" buffer that must survive past parv[2]). It deliberately avoids the server-introduction file-statics (introduce_server/send_server/send_server_burst/get_version/m_smask) that bind the rest of the remaining C in s_serv.c into one large cluster — so it is cleanly separable and was taken next after P5xx (m_connect).
    • Guard#ifndef PORT_SERV_P5 wraps s_serv.c:149–311 (the whole handler, incl. its function-local comment2); the C def drops out of s_serv_link.o, the Rust #[no_mangle] m_squit resolves the two msgtab references (SQUIT col 0 = server, col 2 = oper). nm confirms m_squit is T in the binary post-port.
    • Config‑resolved bodyUSE_SYSLOG is #undef (config.h:244) → the #if defined(USE_SYSLOG) && defined(SYSLOG_SQUIT) syslog line is not compiled and not ported. A local oper reaches m_squit (parse col 2 = STAT_OPER) only because OPER_SQUIT+OPER_SQUIT_REMOTE are #defined (config.h:133‑134) and the O-line carries flag letter S (s_conf.c:311 → ACL_SQUIT). The *-mask expansion (my_name_for_link(ME, nline->port)) and the MyPerson(sptr) comment-rewrite are ported faithfully.
    • Classification — handler cluster (msgtab row SQUIT { m_squit, m_nopriv, m_squit, m_nop, m_unreg }): server-link (col 0) and oper (col 2) reachable; plain client (col 1) → m_nopriv at the parse table. Both a standalone-oper L2 and a remote-oper S2S path are reachable, so both tests are required.
    • L1 — N/A. No module-local data op to drive against a cref_ oracle (same as P5xx m_connect): every state mutation delegates to already-ported, already-L1-tested callees (exit_client, find_*). Gate is L2 + L2-S2S.
    • L2golden_s_serv_squit.rs: a local oper under squit_oper.conf (new fixture, O-line flag S) drives SQUIT irc.test:irc.test NOTICE alice :You can QUIT, but you cannot SQUIT me. and SQUIT nosuch.invalid402 ... :No such server; the inverse under oper.conf (no flags) hits the handler-internal is_allowed(ACL_SQUIT) reject → 481 with no 402. Reference-C == Rust byte-identical after canonicalize.
    • S2Sgolden_s2s_s_serv_squit.rs (mirrors golden_s2s_s_serv_connect.rs): a remote oper (+o, STAT_OPER) behind Peer::link() issues SQUIT nosuch.invalid; is_allowed short-circuits to 1 for the non-local prefix → no server found → 402 oper1 nosuch.invalid :No such server routed back out the peer link (positive). A plain remote client issuing the same SQUIT (col 1 → m_nopriv) produces no 402 (inverse), proving col-2 prefix-is-oper gating. The exit_client teardown tail (real-link SQUIT) is a faithful passthrough exercised by the existing golden_s2s_squit.rs peer-EOF netsplit cascade — not re-driven here (SQUITting a live link is nondeterministic to diff).
    • Pre-existing flake notedgolden_s2s_s_serv_stats.rs fails on clean HEAD too (reference-C reads uninitialized peer sendq during BURST: …3544950785030750208kB sq…); unrelated to P5yy, confirmed by stash-and-rerun.
  • 2026‑06‑04 — P5zz (s_serv.c oper-shutdown cluster: m_die + m_restart) merged. The leaf oper shutdown/restart pair — the direct analogue of P5rr's m_close/m_rehash maintenance pair.

    • Cluster choice — the remaining C of s_serv.c (nm s_serv_link.o) is dominated by the server-link handshake connected component (m_server/m_server_estab/introduce_server/send_server/send_server_burst/get_version/check_version/m_smask, coupled via the file-static buf + the static helpers) plus the propagation leaves m_sdie/m_encap. m_die/m_restart are the two cleanly-independent oper-shutdown leaves, ported here; m_sdie/m_encap/m_smask go with the server-link finale (the cluster that drops s_serv.o).
    • Guard / mechanism — both already sit under the s_serv_link.o partial port (-DPORT_SERV_P5); each gets a plain #ifndef PORT_SERV_P5 guard. No extern-prototype switch (neither is a file-static or anyone's static callee), no parse.rs/build.rs edit — their msgtab decls resolve to the #[no_mangle] Rust defs at link.
    • Config‑resolved bodyDIE { m_nop, m_nopriv, m_die, m_nop, m_unreg } / RESTART { m_nop, m_nopriv, m_restart, m_nop, m_unreg } → col 2 (STAT_OPER) only; a registered non-oper hits m_nopriv/481 at the parse table. Faithful: the for (i=0; i<=highest_fd; i++) local[] walk, the IsClient||IsService :%s NOTICE %s :Server {Terminating,Restarting}. %s notice + exitc=EXITC_DIE ('d') + (clients only) exit_client, the IsServer :%s ERROR :{Terminated,Restarted} by %s line, flush_connections(me.fd), then s_die(0)/restart(buf). killer[HOSTLEN*2+USERLEN+5]=141 → a Rust stack array; the defensive strcpy(killer, get_client_name(sptr,TRUE)) replicated. m_restart's C sprintf(buf, "RESTART by %s", …) used the file-static buf as in-call scratch (passed straight to restart()); the still-C check_version/m_server_estab keep that static, so the Rust port writes a module-private RESTARTBUF — behaviorally identical, not ABI.
    • Classification — handler cluster, oper-only (col 2), pure-wire + destructive-on-success.
    • L1 — none (no socket-free data op; no cref_ oracle — the bodies are an fd-loop of sendto_one terminating in the process-killing s_die/restart).
    • L2golden_s_serv_shutdown (the non-destructive is_allowed reject): a flag-less oper (oper.conf) issues DIE/RESTART → the handler-internal is_allowed(ACL_DIE/ACL_RESTART) fails → m_nopriv → 481 ERR_NOPRIVILEGES, byte-identical ref-C vs Rust. The success path (notice loop → s_die(0)/restart(buf)) tears down the process → review-covered (the skill's destructive→review rule, as P5rr left the rehash/s_die tails). Regression: golden_s_serv_maint/golden_s_serv_set stay byte-identical.
    • S2S — none. Neither handler has an IsServer(cptr) dispatch branch nor formats remote-user fields; the IsServer(acptr) arm in the local fd-loop sends an ERROR over an already-linked fd (not a cptr-dispatch / remote-field path), so the single-peer harness adds no coverage. Stated explicitly per the skill's S2S rule.
  • 2026-06-04 — P5aaa (s_serv.c ENCAP/SDIE server-broadcast stub cluster: m_encap + m_sdie) merged. Two server-only leaf handlers ported to ircd-common/src/s_serv.rs via the existing s_serv_link.o partial port (-DPORT_SERV_P5); s_serv.o stays C for the still-unported server-introduction cluster (m_server/m_server_estab/send_server/send_server_burst/introduce_server/get_version/check_version/m_smask).

    • Cluster choice — the next small independent remaining s_serv.c component: both handlers' sole action is a sendto_serv_v(cptr, SV_UID, …) broadcast and they share no file-static with the big server cluster, so they port cleanly under one new #ifndef PORT_SERV_P5 guard (no extern-prototype switch — neither is anyone's static callee; their msgtab decls resolve to the Rust defs at link).
    • Config-resolved bodiesm_encap (s_serv.c:3884) rebuilds :<src-sid> ENCAP <mask> <cmd> [args…] into a function-local char buf[BUFSIZE] (→ a Rust stack array, not a module static), i>=3 && i==parc-1 selecting the :-prefixed trailing param, then sendto_serv_v(cptr, SV_UID, "%s", buf); the over-length guard emits ENCAP too long (…) to local &ERRORS (SCH_ERROR) and returns 1. The 2.11.0 /* FIXME: in 2.11.1 */ local-dispatch block is a no-op (not yet implemented) → ported as the bare rebuild+forward. m_sdie (s_serv.c:3919) is a one-liner: forward :<src-sid> SDIE. Faithful: the len + strlen(parv[i]) >= BUFSIZE-2 size-promotion compare ported as usize; sptr->serv->sid (a [c_char;5] array) passed to the %s variadics via .as_ptr().
    • Classification — handler cluster, both server-only (msgtab ENCAP { m_encap, m_nop, m_nop, m_nop, m_nop }, SDIE { m_sdie, m_nop, m_nop, m_nop, m_unreg } → col 0 only; a local client/oper hits m_nop). No L1 (pure wire/broadcast handlers, no socket-free cref_ data op), no plain L2 (a local client can't present a STAT_SERVER prefix). L2-S2S is the only gate.
    • L2-S2S golden_s2s_encap — a peer's :001B ENCAP *.test SOMECMD arg1 :trailing arg then :001B SDIE; the inverse gate (sendto_serv_v(cptr=peer, …) excludes the origin link → the peer must NOT receive either command echoed back) + a follow-up PRIVMSG bob routing fence (proves both handlers returned 0/cleanly and the link survives). Byte-identical ref-C vs Rust on the peer transcript. The forward-to-an-additional-server arm and m_encap's SCH_ERROR over-length path are review-covered — the single-peer harness has no second direct server link to receive the butone broadcast (SID 001B is hardcoded to fixtures/s2s.conf), and the 512-capped reader can't deterministically overflow BUFSIZE-2 in the rebuilt buffer. Regression: golden_s2s_link/golden_s2s_squit/golden_s2s_eob stay byte-identical; workspace builds 0 warnings.
  • 2026‑06‑04 — P5bbb (s_serv.c server‑introduction / link‑establishment / burst — FINAL s_serv.c cluster) merged. s_serv.c is now fully Rust; s_serv.o dropped outright.

    • Cluster choice — the connected component left in s_serv_link.o: m_server (the SERVER command), m_server_estab (the ~530‑line local‑link accept + full network burst), m_smask (masked‑server introduction), check_version/get_version (the PASS‑staged version classifier), and the burst emitters send_server/introduce_server/send_server_burst — plus the check_servername_errors[3][2] string table that P5qq deliberately kept C (a #[no_mangle] strong def would have collided with the C BSS symbol while s_serv.o was still linked).
    • Drop mechanism — every symbol s_serv.c defines now lives in ircd-common/src/s_serv.rs, so s_serv.o is added to PORTED and dropped from the link set outright (mirroring P5hh's channel.o drop): the P5ii…P5aaa s_serv_link.o second‑compile (‑DPORT_SERV_P5, which also carried ‑DIRCDMOTD_PATH for the then‑still‑C m_motd) and the s_serv.os_serv_link.o link‑set mapping are removed; the cref oracle keeps the unguarded s_serv.o for differential testing.
    • Config‑resolved bodies — USE_SERVICES off (the check_services_butone arms in m_server/m_server_estab not compiled); ZIP_LINKS off (the zip_init/FLAGS_ZIP/connect‑burst‑stats arms gone; encr = cptr->passwd directly); JAPANESE off (no FLAGS_JP); CRYPT_LINK_PASSWORD off (no crypt() link‑password); #ifndef HUB IS compiled (a leaf refuses a second server link); USE_SYSLOG off. check_servername_errors is reproduced as a #[no_mangle] pub static mut [[*const c_char; 2]; 3] (a static of raw pointers isn't Sync → the replies[]‑in‑s_err.rs pattern) because ircd.c:1056 reads [i‑1][1] for its fatal‑error message.
    • Faithfulness notes — the in‑place cptr->info split in check_version (*id++ = '\0' chains) ported via raw‑pointer writes; the get_version 0210/021/else classification kept exact (id arg unused, retained for signature fidelity); set_server/strncpyzt/str_eq(StrEq)/TIMESEC=60 helpers added; the m_server_estab EOB‑list builder (raw‑pointer eobbuf walk + memcpy of each aServer.sid) and the client/service UNICK/SERVICE burst loop (for acptr=&me; acptr; acptr=acptr->prev) ported faithfully; the dead SV_OLD (==0) remote‑too‑old branch kept including the C string‑concat typo "%s is"+"too""%s istoo".
    • Classification — handler cluster, server‑reachable. L1 is s_serv_version_diff (the only socket‑free data op with a cref_ oracle: check_version + transitively get_version); the five statics + the three handlers terminate in sockets/exit cascade → no L1.
    • L1s_serv_version_diff.rs: check_version vs cref_check_version on parallel make_client'd clients with an empty conf (so find_two_masks/find_conf_flags return 0 → the happy path, no exit_client). Corpus covers the 0210‑beta 991199 (old) vs 991200 (ok) boundary, the 021x fast‑path, else→SV_OLD, the in‑place info truncation, and the ZFLAGS_ZIPRQ flag (with a P flag so the faithful BOOT_STRICTPROT "Unsafe mode" exit — bootopt defaults to BOOT_PROT|BOOT_STRICTPROT — isn't taken, keeping it a clean data op) + the DefInfo pointer‑identity early‑return. Zero diff. This is the only gate on the version branches: the live S2S harness only links with the fixed PEER_VERSION 0211030000 (one get_version branch).
    • L2‑S2S — the headline gate is the whole existing 37‑test golden_s2s_* suite: every test's Peer::link() PASS/SERVER handshake drives m_server(local IsUnknown→handshake)→check_versionm_server_estab (N/C conf + password validation, det_confs_butmask, hash/tree/svrtop registration, add_fd, the server/client/service/channel burst, the EOB list)→send_server_burst→EOB, so none can pass unless the handshake is byte‑exact; golden_s2s_reject drives m_server's unauthorized‑link reject. All byte‑identical (the lone golden_s2s_s_serv_stats failure is the documented pre‑existing reference‑C uninitialized peer‑burst‑sendq flake — …3544950458613235712kB sq left vs Rust's correct 0kB sq — not a regression). New golden_s2s_server.rs closes the gap the user‑only UNICK introduce can't: m_server's IsServer(cptr) remote‑server‑introduction branch — a peer :001B SERVER sub.test 1 002C 0211030000 :Sub Server (the H|*||peer.test|1| hub line admits it at hop 1) → make_client/make_server, the L/H/Q‑line gating, the remote aServer creation, add_server_to_tree + SID/client‑hash registration, introduce_server — observed via LINKS (the new server's up/sid/info formatted by m_links), byte‑identical ref‑C vs Rust.
    • S2S inverse / no‑path note — the server‑removal inverse, m_smask, and the multi‑server send_server/introduce_server paths are review/golden_s2s_squit‑covered: the minimal single‑peer harness can only half‑form a sub‑server (the fake peer never completes a burst/EOB for it), and SQUITting such a sub‑server segfaults even the reference‑C ircd — so remove_server_from_tree stays covered byte‑for‑byte by golden_s2s_squit.rs (a peer‑EOF netsplit), and send_server's SMASK/SERVER multi‑link output needs ≥2 concurrent peer links the harness lacks (mirrors the P5ss mass‑SID / P5qq multi‑server review‑coverage precedent).
  • 2026-06-04 — P5ccc (s_user.c hunt_server / next_client, the final s_user.c cluster) merged. s_user.c is now FULLY ported → s_user.o dropped outright; only s_auth.c remains C among the P5 TUs.

    • Cluster choice — the two routing callees left in s_user_link.o (nm showed only hunt_server + next_client still C): next_client (s_user.c:119) finds the next client-list entry matching a wild mask (exact-hash shortcut via find_client + wildcard next->next walk); hunt_server (s_user.c:155) resolves parv[server] (find_client→find_sid→find_server→find_service, each nulling a self-loop hit, then collapse+next_client wildcard walk), rewrites parv[server] to the matched server's real name, and sendto_one-forwards command (or 402s). Pure callees — not in msgtab; reached from m_who/m_whois (s_user), m_list/m_names (channel), m_version/m_info/m_links/m_stats/m_time/… (s_serv), m_whowas (whowas).
    • Guard / Config — both unguarded (always compiled, no #ifdef). Added is_registered (status >= STAT_SERVER || == STAT_ME) + my_service (MyConnect && IsService) inline helpers; imported find_sid/find_service/client. Faithful: the 9-slot sendto_one(acptr, command, parv[0..8]) forward, the IsService(sptr) dist-vs-acptr->serv->sid reject arm, and the for-loop continue→increment semantics ported byte-for-byte.
    • Builds_user.o added to PORTED; removed the s_user.o → s_user_link.o substitution in link_objs() and the s_user_link.o second-compile recipe (-DPORT_USER_P5 + FNAME_USERLOG/CONNLOG/OPERLOG). The cref oracle keeps the full unguarded s_user.o.
    • Classification — utility/callee TUs (no msgtab row). L1 is the socket-free gate; L2-S2S covers the live IsServer/remote-link routing.
    • L1 (s_user_hunt_diff.rs, zero-diff vs cref_) — next_client: parallel client lists (add_client_to_list) + client hash (add_to_client_hash_table) on each side's own globals (hash_diff pattern); exact-name hit, alpha* wildcard full enumeration, continuation past the first hit, and the inverse no-match→NULL. hunt_server: the send-free HUNTED_ISME fast-paths (parc≤server, BadPtr, match-ME), HUNTED_PASS (route to a directly-linked peer.test → return + parv[server] rewrite + the :alice VERSION :peer.test\r\n forwarded line buffered into the target sendQ, send_diff pattern), and HUNTED_NOSUCH (unknown target → 402 buffered to the source).
    • S2S (golden_s2s_hunt.rs, byte-identical) — VERSION drives all three verdicts: VERSION → ISME 351 local; VERSION peer.test → PASS :<src> VERSION :peer.test observed at the peer; VERSION peer.* → the next_client wildcard walk → same forward; VERSION nosuch.server → NOSUCH 402; inverse re-drive stays 402. The whole existing golden_s2s_* suite routes via hunt_server (info_links/lusers/motd/trace/who re-verified green), so it is the de-facto regression gate.
    • Known flakegolden_s2s_s_serv_stats still fails on the documented reference-C uninitialized peer-burst sendq garbage (…kB sq); pre-existing, not a regression. s_user_umode_diff fails to compile on clean HEAD too (bindgen c_char signedness rot) — unrelated to this cluster.
  • 2026‑06‑04 — P5whowas (whowas.c m_whowas, whowas.o dropped) merged. The P2‑deferred WHOWAS command handler ported → whowas.c fully Rust.

    • Cluster choicem_whowas was the only C remnant of whowas.o, kept behind whowas_link.o (-DPORT_WHOWAS_P2) since the P2 ring port because its reply path needed the variadic sendto_one (P3) and routing callees canonize (P5m) / hunt_server (P5ccc), all now Rust. With those landed it ports cleanly; this is the natural next droppable command‑handler cluster after the three big P5 TUs (channel/s_serv/s_user) finished.
    • Drop vs _link.o — every symbol whowas.o defines is now Rust → whowas.o added to PORTED and dropped outright (the rename match arm "whowas.o" => "whowas_link.o" and the P2c second‑compile step removed from ircd-sys/build.rs). The cref oracle still compiles the full unguarded whowas.o (in CREF_OBJS, unaffected by PORTED), so cref_add_history/cref_get_history/… survive for the L1 whowas_diff.
    • Config‑resolved bodyparc<2→431; parc>2atoi(parv[2]) max; parc>3hunt_server(...,":%s WHOWAS %s %s :%s",3,...) forward; canonize(parv[1]); !MyConnect(sptr)max=MIN(max,20); the do‑while ring scan from &was[(ww_index?ww_index:ww_size)-1] decrementing with wrap, matching mycmp(nick,ww_nick) → 314 RPL_WHOWASUSER (up->username/host, ww_info) + 312 RPL_WHOISSERVER (up->server, myctime(ww_logout)); j>=max early break; not‑found → NICKLEN‑truncate + 406; trailing 369.
    • Faithfulness notesup is declared once and reset to NULL only via the else up=NULL arm (so a found token leaves it set across the if up.is_null() check, then clears it for the next token) — ported literally. p[-1]=',' token restore via *p.sub(1). The helpers me_name/reply/bad_to/my_connect mirror s_user.rs.
    • Classification — handler cluster (msgtab WHOWAS row), server‑reachable: it formats up->server (a remote‑user field) and forwards via hunt_server → L2 + L2‑S2S required; no L1 (pure wire handler; the ring it reads is already L1‑covered by whowas_diff).
    • L1 — none new; whowas_diff (P2) stays zero‑diff vs cref_ (ring add/get/find/off/count) and now links against the dropped‑whowas.o build.
    • L2golden_whowas: alice registers + QUITs (→ add_history), bob WHOWAS alice → 314/312/369 found path, WHOWAS alice 1 exercises the j>=max break, WHOWAS ghost → 406 inverse; byte‑identical ref‑C vs Rust. 312's myctime(ww_logout) is wall‑clock volatile → masked locally (the shared canonicalize leaves 312 untouched for the stable‑server‑info WHOIS goldens). golden_s_user_canonize (406/369 dedup) is the regression.
    • S2Sgolden_s2s_whowas: (1) a peer introduces+QUITs rmuser → local WHOWAS rmuser renders 312 with peer.test (the remote up->server, not irc.test); (2) WHOWAS ghost 1 peer.testhunt_server matches parv[3] and forwards the command out the link, observed peer‑side. byte‑identical. The remote‑sender max>20 cap is review‑covered (the single‑peer harness reply‑routing doesn't reach a remote sender passing max>20).
  • 2026-06-04 — P5hash (hash.c m_hash / HAZH) merged. The last C remnant of hash.c — m_hash was kept in C via the P2d hash_link.o partial-compile (-DPORT_HASH_P2) while the 6-table hash machinery moved to ircd-common/hash.rs in P2. Porting it lets hash.o be dropped outright.

    • Cluster choicenm hash_link.o defines exactly one symbol, m_hash; every symbol it references (the 6 tables, the cl/uid/ch/sid/cn/ip × hits/miss/size counters, the _*SIZE globals, the 5 fixed-arity hash fns, hash_channel_name, is_allowed, m_nopriv, sendto_one) is already Rust or a C trampoline. So this is a clean drop, parallel to P5whowas.
    • Guard / build — added "hash.o" to PORTED, removed the "hash.o" => "hash_link.o" link-set remap, and deleted the P2d hash_link.o second-compile step in ircd-sys/build.rs. The cref oracle keeps the unguarded hash.o (so cref_* hash symbols survive the L1 hash_diff test).
    • Config-resolved body — DEBUGMODE/HASHDEBUG OFF → the show/list subcommands and show_hash_bucket are not compiled and not ported; USE_HOSTHASH+USE_IPHASH ON → HashTables[] carries all 6 entries (+ NULL sentinel). Faithfully replicated the original's channel-entry quirk (nentries = &sidsize, not &chsize). The %f ratio / %%Full / Av Hits lines reproduce the C (float)(double_expr) cast + varargs float→double promotion via (expr as f32) as f64 (incl. the inner (float)tothits/(float)used casts).
    • Classificationhandler cluster (msgtab HAZH 0 MPAR { m_nop, m_nopriv, m_hash, m_nop, m_nop }); columns index by client status, so col 2 = STAT_OPER is the ONLY path to m_hash (a local oper). Re-gated internally on is_allowed(sptr, ACL_HAZH).
    • L1 — none new; m_hash is a read-only pure-wire handler (no add/remove/alloc data op). The hash data structures' round-trips are covered by the P2 hash_diff.rs (re-run green after the import changes).
    • L2ircd-golden/tests/golden_s_hash.rs + fixtures/hazh_oper.conf (O-line flag h = ACL_HAZH). oper_hazh: register alice, OPER alice secret (→ STAT_OPER + ACL_HAZH), HAZH (the parc<2 usage/table-list NOTICEs) + HAZH c (the client-table statistics block) byte-identical. nonoper_hazh_denied: a registered non-oper bob running HAZH c is routed to m_nopriv → 481 with NO statistics text — the inverse of the dispatch-column gate (a broken col-2 gate would leak the stats).
    • S2S — none. m_hash has no IsServer(cptr) branch and formats no remote-user fields (no ->user->server/hopcount/SID trailers); genuinely unreachable over a server link under the locked config.