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 toircd-common/src/s_service.rsands_service.odropped outright (added toPORTED, likeparse.o). Establishes the P5 handler‑porting mechanism: a Rustm_*reachable through themsgtabfn‑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/svctopfromircd_sys::bindings(it imports all symbols there, even already‑ported ones); those declarations resolve to the Rust definitions at link onces_service.ois 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_flagvia a localextern "C"block (the s_numeric.rs pattern); fmt must be declared*mut c_char(matching the bindgen/other‑module decls) to avoid theclashing_extern_declarationslint, so format‑string literals are castc"…".as_ptr() as *mut c_char.MyFree= libcfree;strncpyzt/index(=strchr)/myncmpported. - Config (USE_SERVICES off) shapes the port:
m_servset/check_services_*/find_conf_service+ the local‑service branch ofm_serviceare not compiled and not ported; a clientSERVICEreaches the#ifndef USE_SERVICESreject (ERROR :Server doesn't support services). - msgtab dispatch findings (caught at L2): a registered client's
SERVICEmaps tom_nop(msgtab index 1 = registered‑client column ism_nop), so it produces no reply —m_serviceis only reached from index 0 (unregistered) / 4 (server→SERVICE). BareSQUERYis rejected byparse()with461(min‑params) before the handler. So L2 coverage of the ported code is:m_servlist(235),m_squery+best_service(408, empty list), andm_servicereject (unregistered client →ERROR). - L2 harness hardening:
ircd-goldenboots now serialize through a process‑widePORT_LOCKheld by theIrcdguard —cargo testruns#[test]s on parallel threads and they all bind the one fixture port (16667). (Single‑testgolden_registrationhad masked this; the 2‑testgolden_servicesurfaced it.) - Verification: L1
s_service_diffzero‑diff vscref_(make_servicesvctop linkage +cptr->nameself‑pointer + idempotency;free_servicehead/middle unlink +service=NULL); L2golden_service2/2 byte‑identical (registered SERVLIST/SQUERY + unregistered SERVICE reject); the Rustircdlinks withs_service.odropped (m_service/svctopresolve to ircd‑common); fullcargo testgreen; 0 warnings.
- parse.rs needs no change: it already imports
-
2026‑06‑02 — P5b (s_debug.c — first utility/callee TU) merged; s_zip.c dropped. All 4 compiled symbols (
serveroptsconfig‑resolved option string,send_usage,send_defines,count_memory) ported toircd-common/src/s_debug.rsands_debug.odropped outright;s_zip.oalso added toPORTED(ZIP_LINKS off → its only symbol is astatic rcsid, nothing to port). The variadicdebug()is#ifdef DEBUGMODE(off) → not compiled, not ported. This is the first utility TU (nomsgtabentry): the three report fns are reached from still‑Cm_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. STATSd→send_defines(RPL_STATSDEFINE 248, all compile‑time constants — fully deterministic), STATSz→count_memory(.., 0)(RPL_STATSDEBUG 249 memory counts fromistat/sizes). Both reachable by a plain registered local client (no oper gate ond/z). STATSr/R→send_usageis 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.oand pinned by L1).send_definesbakes ~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.BUFFERPOOLresolved 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 aftersbrk(0)-etext:). Everything else in STATS z is deterministic —cres_memsends two RPL_STATSDEBUG lines but with the DNS cache empty (NO_DNS) they're all‑zero, andtotis config/counter‑derived.count_memory'sif(debug)[REAL] branches (the client/channel/conf list walks) are ported faithfully but never execute on the STATS z path (debug=0). libcforgetrusage/rusage: sys/resource.h isn't in bindgen's wrapper.h, sosend_usageuseslibc::{rusage, getrusage, RUSAGE_SELF}rather than a hand‑rolled struct;sbrk/time/strerrorvia a localextern "C"block. Variadicsendto_onecalled the s_service.rs way (fmt*mut c_char, literals cast).- Verification: L1
s_debug_diffzero‑diff vscref_serveropts(+ pins the resolved value); L2golden_debug2/2 byte‑identical (STATS dsend_definesblock incl.:HUB:no MS:1; STATS zcount_memoryblock incl. maskedsbrk(0)-etext: <BRK>); the Rustircdlinks withs_debug.o+s_zip.odropped (serveropts/send_*/count_memoryresolve to ircd‑common); fullcargo testgreen; 0 warnings.
- Classification → L2 strategy: s_debug has no client‑reachable
-
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 toircd-common/src/s_misc.rs;s_misc.odropped outright (every symbol portable → added toPORTED, no_link.o). This is the largest utility TU so far (1217 LOC):exit_client→exit_server↔exit_one_clientis the mutually/self‑recursive client/server exit cascade (the critical network path), plusdate/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_NDELAYoff;DELAY_CLOSE=15/USE_HOSTHASH/USE_IPHASH/USE_IAUTH/CACCEPT_DEFAULT=2on. Crucially the entireexit_clientper‑client logging block (s_misc.c:465‑507) is gated on(FNAME_USERLOG||FNAME_CONNLOG||USE_SERVICES||(USE_SYSLOG&&…))— all undefined → omitted, sosendto_flogis never called from s_misc (one fewer variadic to thread). The#ifdef USE_SERVICEScheck_services_butonecalls throughout the cascade are likewise not compiled/not ported. - Keystone finding — variadic
%lu‑from‑u_intis a caller‑side faithfulness hazard, caught by L2.tstatspasses ~30u_int(32‑bit)istat/statsfields 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 undefined →STATS tprinted garbage (refused 94888712470528). Fix: cast every integer arg to exactly its conversion width (%lu→u_long,%llu→c_ulonglong,%d→c_int,%u→c_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‑longintegers. (Thegolden_s_miscSTATS t diff is what surfaced it — exactly what L2 is for.) format!/byte‑builder instead of libcsprintf(per user request): the buffer‑formatting fns no longer callsprintf.date(pure ASCII) uses Rustformat!then copies into its static[c_char;80].get_client_{name,host}+exit_client'scomment1use small byte‑exactpush_cstr/push_cstr_prec/storehelpers — notformat!, because C's%.*sbyte‑precision over arbitrary (possibly non‑UTF‑8) C‑string bytes can't pass through a UTF‑8Stringwithout 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 earlysprintf %soverran into adjacent rodata; the byte‑builder rewrite removes that footgun class.)- Globals owned here:
ircst/ircstp(*ircstp = &ircst) as#[no_mangle] static mut—ircstzero‑inits viaMaybeUninit::zeroed().assume_init()(const),ircstpviaaddr_of_mut!(ircst);motd=NULL,motd_mtime=0.months/weekdaysstay module‑private&strtables. All Is*/Got*/strncpyzt macros reproduced inline;match/mycmpimported 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_diffzero‑diff vscref_(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). L2golden_s_misc2/2 byte‑identical: QUIT →exit_clientlocal path (ERROR :Closing Link: alice[~alice@127.0.0.1] ("gone fishing")), STATS t →tstats(full RPL_STATSDEBUG block;:time connectedmasked as wall‑clock volatile). Added two canonicalizer masks::time connectedand a drop of the racySPLIT_CONNECT_NOTICE(register‑time NOTICE whose post‑422 TCP chunking is nondeterministic). Fullcargo testgreen; 0 warnings. Deferred to S2S/multi‑client L2: theexit_one_clientchannel‑QUIT broadcast, serverSQUITpropagation, masked‑server paths (ported faithfully, covered by review).
- Config gates that shaped the port (all verified in cbuild/config.h):
-
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 toircd-common/src/s_user.rs. First in‑TU partial port of P5:s_user.o→s_user_link.o(‑DPORT_USER_P5#ifndef‑guards the cluster out of the link set; the cref oracle keeps the full unguardeds_user.o). The rest of s_user.c (register_user/m_nick/m_kill/m_umode/m_oper/m_message/… + the shared staticsbuf/buf2/user_modes) stays C.s_user_link.obuilt viamake --evalso the per‑objectFNAME_USERLOG/CONNLOG/OPERLOGpath defines (used by the surviving C register_user/m_oper) expand identically (the send_link.o pattern).s_auth.cdeferred (not a P5 cluster): it has nom_*handlers — it's the ident/iauth glue (start_auth/send_authports/read_authports/read_iauth/sendto_iauth), pure socket/pipe I/O touchingadfd/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_whoisfromircd_sys::bindings; onces_user.o→s_user_link.odrops them, those decls resolve to the Rust defs.parse_who_argis#[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_oneis the C variadic trampoline. format!per user request: WHOX (354) is built field‑by‑field into aVec<u8>— numeric fields (%dhopcount,%ldidle) viaformat!, arbitrary C‑string fields (username/host/info/…) copied raw viapush_cstr(a UTF‑8 String would corrupt non‑ASCII bytes), then truncated toBUFSIZE‑1(faithful to C's incrementalsnprintf/snprintf_appendtruncation).send_whois's channel‑list accumulation keeps the byte‑exact pointer/lenlogic on a privatestatic mut BUF(the C file‑staticbufstays with the C remnant). The placeholderUnknownUseranUser (unreachable on the IsPerson path) reproduced as astatic mutvia aconst 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 Rustcid_ok. - Verification: L1
s_user_who_diffzero‑diff vscref_parse_who_argover 12 WHOX arg strings (o/%flags/,tokenlen‑cap/unknown chars). L2golden_s_user2/2 byte‑identical: WHOIS alice (311/312/317‑masked/318) + WHO alice (352/315). The Rustircdlinks withs_user_link.osubstituted (m_who/m_whois/parse_who_argresolve to ircd‑common). Fullcargo testgreen; 0 warnings. Deferred to S2S/multi‑client L2: WHOX, channel WHO/WHOIS, wildcard WHOIS, thehunt_serverremote 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) andm_ison(s_user.c:3127) ported intoircd-common/src/s_user.rs, extending the existings_user_link.opartial port — two more#ifndef PORT_USER_P5regions guard them out of the link set; the cref oracle keeps the full unguardeds_user.o. Pure handler cluster (both aremsgtabrows, client‑reachable): no parse.rs edit (them_userhost/m_isondecls parse.rs imports fromircd_sys::bindingsresolve to the Rust defs at link). The shared C file‑staticsbuf/buf2are replaced by private RustVec<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 ownbuf.format!per user request: both replies are byte‑exact reconstructions ofreplies[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 viapush_cstr— a UTF‑8String/format!would corrupt non‑ASCII bytes — and these handlers have no integer conversions, so the onlyformat!‑eligible tokens are the literal numerics/separators (kept as byte literals, matching the existingwho_one354 reconstruction). Documented in‑module:format!is used for ASCII/numeric, raw copy for C‑string bytes.- Faithfulness notes:
m_userhostwalks up to 5 targets acrossparv[]via theidxindex (parv[++idx]), space‑separates entries (theif(*buf2)"not the first entry" guard → tracked bybuf2staying non‑empty), reproduces thestrncat(buf, buf2, sizeof(buf)-len)cap and C'slen += strlen(buf2)(the full length even when truncated), and the re‑head/flush atlen > BUFSIZE-(NICKLEN+5+HOSTLEN+USERLEN)=419(effectively dead for ≤5 short targets, ported anyway).m_isonstrtoken‑splits the single trailing param, appendsname+ a trailing space per hit, and breaks atlen+i > BUFSIZE-4("leave room for\r\n\0"). - Harness fix (cross‑process boot lock). Adding a 6th
ircd-goldentest binary surfaced a latent flake:cargo testruns each test file as its own process and runs several in parallel, but the boot serializer was an in‑processMutex— so two binaries (e.g.golden_debug+golden_s_user_userhost) raced to bind the one fixture port 16667 →BrokenPipe/diff failures. Replaced theMutexwith a cross‑process advisory file lock (libc::flock(LOCK_EX)onfixtures/.port.lock, held in theIrcdguard, released when its fd closes afterchild.kill()+wait()); separate fds contend even within one process, so it also serializes the parallel#[test]threads inside a binary.libcadded as anircd-goldendep; the lock artifact is gitignored. - Verification: L2
golden_s_user_userhost2/2 byte‑identical (USERHOST alice nemo → 302 with the multi‑targetidxwalk + afind_personmiss on nemo; ISON :alice nemo → 303 with a hit + a miss); both replies fully deterministic (no time tokens → no canonicalizer change). The Rustircdlinks withm_userhost/m_isondefinedTfrom ircd‑common. Fullcargo testgreen and stable across repeated runs (flake fixed); 0 warnings. Deferred to multi‑client L2: the>BUFSIZEbatch‑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]wrappersm_private/m_noticeported intoircd-common/src/s_user.rs, extending the existings_user_link.opartial port (thePORT_USER_P5guard now spans m_message through the WHO/WHOIS cluster as one contiguous region — the redundant inner#ifndefwas removed). Pure handler cluster (both wrappers aremsgtabrows: PRIVMSG→m_privatecol 1, NOTICE→m_notice): no parse.rs edit (the decls parse.rs imports fromircd_sys::bindingsresolve to the Rust defs onces_user.o→s_user_link.odrops them). RED confirmed via undefinedm_private/m_notice(m_messageisstatic→ inlined into both, never a link symbol).- First multi‑client L2. The user required
#/+/&channel relay coverage, which needs ≥2 simultaneous clients (sendto_channel_butonedelivers to the other members, never the sender). Added toircd-golden: a persistentClientstruct (connect/register/send/read_until/drain) andboot_standalone(-p standalone). Newgolden_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), socheck_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+(notUseModes) and&(excluded). So#golden tests must boot-p standalone(iconf.split = -1→IsSplit()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 substringNOTICE alice, which falsely satisfied the NOTICE‑selfread_until(" NOTICE alice "). Fixed by draining after register before sending. (PRIVMSG's sentinelPRIVMSG alicedoesn't collide, so only NOTICE‑self failed at first.) format!per user request — N/A here, documented.m_messageis 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 fromircd_sys::bindings(bindgen declarespattern: *mut c_char→ noclashing_extern_declarations);strrchr(rindex) added to the local extern block.- Faithfulness notes:
syntaxis declared outside the per‑target loop (an oper's$$/$#match leaks into later targets — verbatim C); the C for‑loop post‑exprnick = strtoken(…), penalty++(which must run even oncontinue) is modeled with a labeled'targetblock where each Ccontinue→break 'target, followed by the advance; the nick!user@host and user[%host]@server paths NUL‑terminatenickin place then restore!/@/%, exactly as C mutates the strtoken slice. - Verification:
golden_s_user_message7/7 byte‑identical;m_private/m_noticedefinedTfrom ircd‑common in the Rustircd; fullcargo testgreen (all 12*_diffL1 + 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).
- First multi‑client L2. The user required
-
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 intoircd-common/src/s_user.rs, extending the existings_user_link.opartial port (two new#ifndef PORT_USER_P5guard regions; the cref oracle keeps the full unguardeds_user.o). Pure handler cluster (AWAY→m_awayis amsgtabrow) — L2 golden is the gate, RED confirmed via undefinedm_away/send_away; no parse.rs edit.send_awaywas previously imported fromircd_sys::bindingsand called by the already‑portedm_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 ans_user_ext.hsymbol). Plan:docs/superpowers/plans/2026-06-02-p5g-s_user-away.md.- Config that shaped the port (verified in
cbuild/config.h):USE_SERVICESoff →m_away's two#ifdef USE_SERVICES check_services_butoneblocks not compiled/not ported.AWAY_MOREINFOdefined (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#elseRPL_AWAY‑"Gone" — ported the#ifdef AWAY_MOREINFObranch. Both callers guard on FLAGS_AWAY ⇒away != NULLin practice, so the live path is theif (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)sptrmarking away, CMyMallocs the buffer into the localawaybut only assignssptr->user->away = away(andstrcpys) inside theif (MyConnect(sptr))block — so the remote‑away allocation is leaked. The Rust port mirrors this exactly (allocate + setFLAGS_AWAY+ the+aserver propagation unconditionally; store + strcpy + 306 ack only undermy_connect). Not "fixed" (P8 territory). - Types/
format!:istat.is_away/is_awaymemareu_long(cast(strlen+1)/lentou_long);MyMalloc/MyRealloc(P2 Rust ports) takeusizeand return*mut c_chardirectly (no cast);MyFree→libcfree;(*user).uidis[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 thec"…"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_away3/3 byte‑identical: AWAY :gone fishing / AWAY (306 RPL_NOWAWAY then 305 RPL_UNAWAY — bothm_awayarms), WHOIS alice while away (send_away→301 alice alice :brbinside the WHOIS block; 317 idle/signon masked by the existing canonicalizer), bob PRIVMSG alice while away (two clients → bob gets301 bob alice :brbviasend_awayfrom the Rustm_message).m_away/send_awaydefinedTfrom ircd‑common in the Rustircd; fullcargo testgreen (all 12*_diffL1 + layout + all 8 golden suites); 0 warnings. No new canonicalizer masks needed. Deferred to S2S L2: the:%s MODE %s :±aserver‑burst propagation (no server link under‑s/‑p standalone; ported faithfully, covered by review).
- Config that shaped the port (verified in
-
2026‑06‑02 — P5h (s_user.c UMODE cluster) merged.
m_umode(s_user.c:3164),send_umode(:3358),send_umode_out(:3409) + theuser_modes[]table (:38) ported intoircd-common/src/s_user.rs, extending the existings_user_link.opartial port (one new#ifndef PORT_USER_P5region spans the three fns; theuser_modes[]static is guarded separately since only this cluster uses it). Handler cluster (UMODE→m_umodeis a msgtab row;MODE <nick>routes through still‑Cm_mode(channel.c) → Rustm_umodeonce the target isn't a channel) — L2 golden is the gate. RED confirmed via undefinedm_umode/send_umode/send_umode_out; no parse.rs edit. Plan:docs/superpowers/plans/2026-06-02-p5h-s_user-umode.md.send_umodeis called from three still‑C TUs (register_userin s_user.c,s_service.c,s_serv.c— all viasend_umode(NULL, …, buf)); porting it means those C callers now resolve to the Rust def at link, so byte‑faithfulness is load‑bearing beyond justm_umode. This is whysend_umodegets its own L1 differential (s_user_umode_diff): the +/- mode‑diff string builder withcptr=NULLis socket‑free, so it's L1‑testable directly (10 cases over old/current‑flags/sendmask/MyClient combos — pure/grouped/mixed add‑del/SEND_UMODESvsALL_UMODESfiltering).- Config (verified
cbuild/config.h):USE_SERVICESoff → m_umode's threecheck_services_butoneblocks (away‑clear, oper‑±o, umode propagation) and send_umode_out's trailing block are not compiled/not ported. The activeMODE_*values for s_user areMODE_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 RustString+CString(the format!‑family path), then handed to the C variadicsendto_oneas thereplies[RPL_UMODEIS]%s.send_umodewrites its +/- diff into the caller‑owned*mut c_charbuffer with the add/del grouping algorithm — that cannot be aString(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+ais a no‑op (continue, the C switchbreak). The misleading‑indentreturn 1;after the USERSDONTMATCHelseis inside the mismatch‑ifblock (unconditional once the guard trips) — reproduced. Inner loop advances the char pointer first socontinue= C's switchbreak. ClearOper/ClearLocOp also setstatus=STAT_CLIENT; SetClient =IsAnOper?STAT_OPER:STAT_CLIENT— reproduced. The privateUMODE_BUFstatic replaces the s_user.c file‑staticbuf(transient within send_umode_out; the C remnant keeps its ownbuf).istat.is_user/is_oper/is_awaymemareu_long;servp->usercntis[c_int;3]. - Verification: L1
s_user_umode_diffzero‑diff vscref_send_umode(10 cases). L2golden_s_user_umode2/2 byte‑identical: MODE alice +i ; MODE alice (echo:alice MODE alice :+ivia 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_outdefinedTfrom ircd‑common in the Rustircd; fullcargo testgreen (all*_diffL1 + layout + all 9 golden suites); 0 warnings. No new canonicalizer masks. Deferred to S2S L2: thesend_umode_outserver‑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 intoircd-common/src/s_user.rs, extending the existings_user_link.opartial port (one new#ifndef PORT_USER_P5region spans both; the cref oracle keeps the full unguardeds_user.o). Pure handler cluster (PING→m_ping, PONG→m_pongare both msgtab rows, col 1) — L2 golden is the gate. RED confirmed via undefinedm_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 ops → no L1 (like P5e/P5g). New bindings imports:find_target/FLAGS_PINGSENT/ERR_NOSUCHSERVER/ERR_NOORIGIN; reused the existingis_mehelper (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 thec"…"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 originalparv[1](verbatim C);match(destination, ME) != 0= destination does not match the server name. m_pong clearsFLAGS_PINGSENT(flagsisc_long) before the target lookup; the&meno‑destination default usesaddr_of_mut!(me). - Verification: L2
golden_s_user_ping2/2 byte‑identical:PING :hello→:irc.test PONG irc.test :hello(the find‑miss → else echo path),PING alice noserver.invalid→402 ... :No such server(thefind_servermiss).m_ping/m_pongdefinedTfrom ircd‑common in the Rustircd; fullcargo testgreen (all*_diffL1 + layout + all 10 golden suites); 0 warnings. No new canonicalizer masks. No L2 path form_pong(clearsFLAGS_PINGSENT; only emits on the!IsMeforward path → needs a server/target link) — ported faithfully, covered by review.
- Cleanest cluster yet — pure routing, no statics. Both handlers call only external lookup/send fns (
-
2026‑06‑02 — P5j (s_user.c OPER cluster) merged.
m_oper(s_user.c:2866) ported intoircd-common/src/s_user.rs, extending the existings_user_link.opartial port (one new#ifndef PORT_USER_P5region aroundm_oper; the cref oracle keeps the full unguardeds_user.o). Handler cluster (OPER→m_operis a msgtab row, client column) — L2 golden is the gate. RED confirmed via undefinedm_oper(no other symbol; it has no file‑statics of its own under this config). No parse.rs edit. Reuses the already‑Rustsend_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_PASSWORDoff (#undef:311 →encr = password, no crypt),FNAME_OPERLOG/USE_SYSLOG/FAILED_OPERLOG(#undef:300)/UNIXPORT/USE_SERVICESoff,NO_OPER_REMOTEoff (#undef:555 → the#ifndefarm:if (aconf->flags & ACL_LOCOP) SetLocOp else SetOper). ⇒ the trailinglogstring/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 existingclear_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_flagadded to the local extern block. - Faithfulness notes:
encr = parv[2]may be NULL (OPER min‑params is 2) but is only dereferenced past afind_Olinehit, so the wrong‑name 491 path is safe;StrEqreproduced via libcstrcmp(same UB as C if a matching O‑line is hit with no password — not exercised). The transient host splits = index(host,'@'); *s='\0'; …; *s='@'is reproduced verbatim even thoughs(after‑'@') is unused under NO_OPER_REMOTE‑off (net no‑op; mutates the C‑owned host in place and restores it).istat.is_operisu_long;servp->usercntis[c_int;3]→usercnt[2] += 1.%d/%cargs (the:%s %d %s :Too many …lines + the SCH_NOTICE operator‑letter) cast toc_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/%cargs (the sender formats per‑recipient); there is no intermediate string to build under this config (the onlysprintf(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 ofboot_args, which now calls it withminimal.conf) so a test can use a dedicated config; newoper.conf= minimal.conf + a full‑operator O‑lineO|*@*|secret|alice|0|10|(uppercase O → no ACL_LOCOP → SetOper; class 10 exists), isolated from every other golden test. - Verification: L2
golden_s_user_oper1/1 byte‑identical over three OPER attempts on one registered client:OPER nope wrong→491 ERR_NOOPERHOST(find_Oline miss),OPER alice wrong→464 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_operdefinedTfrom ircd‑common in the Rustircd; fullcargo testgreen (67 passed, 0 failed: all*_diffL1 + layout + all 11 golden suites); 0 warnings. No new canonicalizer masks (no time tokens). Deferred to S2S L2: thesend_umode_outserver‑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.
- Config that shaped the port (verified
-
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 intoircd-common/src/s_user.rs, extending the existings_user_link.opartial port (one new#ifndef PORT_USER_P5region around s_user.c:2466‑2498; the cref oracle keeps the full unguardeds_user.o). Pure handler cluster (QUIT→m_quit, POST→m_postare both msgtab rows) — L2 golden is the gate. RED confirmed via undefinedm_quit/m_post; no parse.rs edit. Both handlers terminate in the already‑Rustexit_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‑localstatic char comment[TOPICLEN+1], reproduced as the private module staticQUIT_COMMENT(not ABI);m_posthas no state. They share no file‑statics with the C remnant (buf/buf2/user_modesuntouched).m_posttail‑callsm_quitafter aSCH_LOCALserver‑notice. New bindings imports:exit_client/get_client_host/ServerChannels_SCH_LOCAL; reusedis_server/my_connecthelpers. - Columns
[server, client, oper, service, unreg]decide reachability: QUIT =[m_quit ×5]→ a registered client (col 1) hitsm_quit; POST =[m_nop,m_nop,m_nop,m_nop,m_post]→ only col 4 (unregistered) hitsm_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 inexit_client, which closes the connection) → no L1, like P5e/g/i. format!per user request — N/A for the comment (documented).m_quit'scommentis built fromparv[1], an arbitrary possibly‑non‑UTF‑8 C‑string under Csnprintftruncation atTOPICLEN; per the standing P5c rule that can't pass through a UTF‑8String/format!without corruption → it's a faithful byte‑builder (snprintf_prefix_stremulatessnprintf(buf,size,"<prefix>%s",s)truncation; the MyConnect path thenstrcats the closing"exactly as C).m_post's notice is handed straight to the C variadicsendto_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 issnprintf(comment, TOPICLEN, "\"%s", parv1)thenstrcat(comment, "\"")(="+ parv1 +"), the remote path issnprintf(comment, TOPICLEN+1, "%s", parv1)(raw, no quotes — reproduced even though it needs an S2S link to reach);parv[1]isNULL‑guarded to""exactly as C ((parc>1 && parv[1]) ? parv[1] : ""). - Verification: L2
golden_s_user_quit2/2 byte‑identical:QUIT :gone fishing(registered) →ERROR :Closing Link: alice[~alice@127.0.0.1] ("gone fishing"),POST(unregistered) → the020connection notice +ERROR :Closing Link: [unknown@127.0.0.1] ("").m_quit/m_postdefinedTfrom ircd‑common in the Rustircd; fullcargo testgreen (all*_diffL1 + 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).
- Cleanest cluster after PING/PONG — self‑contained, one tiny static.
-
2026‑06‑03 — P5l (s_user.c PASS cluster) merged.
m_pass(s_user.c:3043) ported intoircd-common/src/s_user.rs, extending the existings_user_link.opartial port (one new#ifndef PORT_USER_P5region around s_user.c:3043‑3076; the cref oracle keeps the full unguardeds_user.o). Handler cluster (PASS→m_pass, msgtab row[m_nop, m_reg, m_reg, m_nop, m_pass]→ only the unregistered column hitsm_pass, i.e. a registration‑time command on a raw connection). RED confirmed via undefinedm_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 —
buftouched only transiently.m_passstoresparv[1]intocptr->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‑staticbuf[BUFSIZE]then immediately copies it out viacptr->info = mystrdup(buf)—bufis never read across calls, so a private module staticPASS_BUF: [c_char; BUFSIZE]is faithful and the C remnant'sbuf(register_user/m_user/m_userhost…) is untouched. No allocators of its own, no list linkage.MyFree→libcfree;mystrdup/DefInfoare the P1/P2 Rust ports (resolve via bindings);strncpy/strncatadded to the local extern block;strncpyztreproduced 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 fromparv[2..4]— arbitrary possibly‑non‑UTF‑8 C‑strings under byte‑precisionstrncpyzt/strncattruncation caps; per the standing P5c rule a UTF‑8String/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).passwdis likewise a byte‑faithfulstrncpyzt. No integer conversions → the P5c%lu‑width rule is N/A.m_passemits no wire output (returns 1; nosendto_*).- L1 is the workhorse (m_pass is socket‑free).
s_user_pass_diffdrivesc_m_pass(resolves to Rust) andcref_m_passon two parallel fresh clients and asserts identicalpasswd[0..21]bytes +infoC‑string over 6 cases: passwd‑only (parc=2), full info‑staging (parc=5), passwd 21‑byte truncation,buf[0]15‑cap, thecptr->user‑set early return (info staysDefInfo), and the strncat 100/5 caps. L2golden_s_user_pass1/1 byte‑identical: a raw unregistered clientPASS secret ircd‑2.11 +optionsthen 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_passdefinedTfrom ircd‑common in the Rustircd; L1s_user_pass_diff1/1 zero‑diff; fullcargo test(all*_diffL1 + layout) + all 14 golden suites green; 0 warnings. Deferred to S2S L2: the server‑linkparv[2..4]version/options consumption inm_server(the stagedinfois read there; ported faithfully on the m_pass side, covered by the L1 staging differential + review).
- Self‑contained —
-
2026‑06‑03 — P5m (s_user.c canonize utility) merged.
canonize(s_user.c:283 — the O(n²) comma‑list deduplicator) ported intoircd-common/src/s_user.rs, extending the existings_user_link.opartial port (one new#ifndef PORT_USER_P5guard region around s_user.c:277‑319; the cref oracle keeps the full unguardeds_user.o). Utility / callee fragment (nomsgtabrow) — it is called fromm_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 findcanonizeonce it was removed from thebindingsimport; defining it locally resolves those Rust callers + all the still‑C callers to the Rust def at link (thesend_away/send_umodepattern 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‑localstatic char cbuf[BUFSIZ](BUFSIZ = 8192 on this glibc, verified viacpp -dM), reproduced as the module‑privateCANON_BUF: [c_char; 8192](not ABI). It shares no file‑statics with the C remnant (buf/buf2/user_modesuntouched); it calls onlystrtoken+mycmp(P1 Rust ports) + libcstrcpy. - Faithful raw‑pointer translation (the L1 differential is the safety net). The C body is intricate in‑place buffer surgery: the outer
strtokenNUL‑splits the input; the inner loop walkscbufwithstrtoken, restoring each comma it split (p2[-1] = ',') except at a dup match (break); unique tokens arestrcpy'd andcpadvances 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))).mycmpis IRC case‑insensitive →ABC,abcdedups. format!per user request — N/A, documented in‑module.canonizecopies arbitrary client‑supplied token bytes (channel names / nicks) verbatim viastrcpyand mutates the buffers in place — there is nothing to format, and a UTF‑8String/format!could neither reproducestrcpyof non‑ASCII bytes nor the in‑placestrtoken/comma‑restore surgery.format!stays reserved for the ASCII/numeric reply cases (cf.m_umodequery reply,who_onenumeric fields).- Verification: L1
s_user_canonize_diffzero‑diff vscref_canonizeover 14 inputs (empty, single, adjacent/interior/casefold dups, channel names, trailing/leading/empty tokens, a longer list exercising thecp += (p‑s)advance). L2golden_s_user_canonize1/1 byte‑identical: a registered client'sWHOWAS alice,alice→m_whowascanonizes toalice→ exactly one406 ERR_WASNOSUCHNICK+369 RPL_ENDOFWHOWAS(the dedup‑count assertion proves the branch fired live in both binaries).canonizedefinedTfrom ircd‑common in the Rustircd; fullcargo testgreen (all*_diffL1 + 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 ins_user_link.o(C) for later P5 sub‑phases.
- Self‑contained — its own static, no shared state.
-
2026‑06‑03 — P5n (s_user.c do_nick_name utility) merged.
do_nick_name(s_user.c:245 — the nickname validator/canonicalizer) ported intoircd-common/src/s_user.rs, extending the existings_user_link.opartial port (one new#ifndef PORT_USER_P5guard region around s_user.c:245‑274; the cref oracle keeps the full unguardeds_user.o). Utility / callee fragment (nomsgtabrow) — it is called from still‑Cm_nick/m_user/register_user; defining it#[no_mangle]resolves those C callers to the Rust def at link (thecanonize/send_awaypattern). RED confirmed via undefineddo_nick_nameonce 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_modesuntouched). Calls only libcstrcasecmp+ the P1 Rustchar_atribstable. - 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).MINLOCALNICKLENis#undef(config.h:732) → its#ifdefshort‑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 P1crate::match_::char_atribstable;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'sisdigitwould pass UB‑ly). format!per user request — N/A, documented in‑module.do_nick_namemutates the caller's nick buffer in place (*ch = '\0') — there is nothing to format and a UTF‑8Stringcould not reproduce the in‑place byte truncation.format!stays reserved for the ASCII/numeric reply cases.- Verification: L1
s_user_do_nick_name_diffzero‑diff vscref_do_nick_nameover 17 cases (valid nick server/client, leading-/digit,anonymous/ANONYMOUScasefold, embedded space/./~truncation,[/]/`/|/}valid‑char retention,>15length cap, empty, single char) — comparing both the returned length and the mutated buffer bytes.do_nick_namedefinedTfrom ircd‑common in the Rustircd; fullcargo testgreen (all*_diffL1 + layout + all golden suites; 0 failed); 0 warnings. No new golden scenario or canonicalizer mask — the existing registration banner already drivesdo_nick_namevia the still‑Cm_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 ins_user_link.o(C) for later P5 sub‑phases.
- Cleanest leaf yet — pure string surgery, no statics, no allocators. Drops the nick (return 0) if it leads with
-
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 intoircd-common/src/s_user.rs, extending the existings_user_link.opartial port (one new#ifndef PORT_USER_P5guard region around s_user.c:3523‑3564; the cref oracle keeps the full unguardeds_user.o). Utility / callee fragment (nomsgtabrow). RED confirmed via undefinedis_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_allowedreads onlycptr(fd/status/service->wants/theconfsLink chain) and touches no globals and no hash tables, so the same constructed client is handed to bothc_is_allowed(→ Rust) andcref_is_allowed(→ oracle) and equal returns are asserted — unlikenext_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_modesuntouched). - Seam — resolves the still‑C and the ported‑Rust callers.
is_allowedis called from portedparse.rs(penalty checks at parse.rs:698/779) and still‑Cm_kill/s_serv.c/s_conf.c/channel.c/res.c/hash.c/s_bsd.c. Removing it from parse.rs'sircd_sys::bindingsimport anduse crate::s_user::is_allowed;makes the Rust caller resolve to the new local def; the C callers resolve at link onces_user.o→s_user_link.odrops the symbol (thecanonize/send_awaypattern).IsService/IsServer/MyConnectreproduced via the existingis_service_cl/is_server_cl/my_connecthelpers;CONF_OPERATOR/ACL_TKLINE/ACL_KLINE/SERVICE_WANT_TKLINE/SERVICE_WANT_KLINEimported from bindings. format!per user request — N/A, documented in‑module.is_allowedemits 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) hitsm_noprivdirectly (parse sends 481 itself,is_allowednever runs); only an oper (col 2) reaches still‑Cm_kill→is_allowed(sptr, ACL_KILL). The O‑line flagsis_allowedwalks 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_REMOTEdefined → theKflag survivesoline_flags_parse's gating.) - Verification: L1
s_user_is_allowed_diffzero‑diff vscref_is_allowedover 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). L2golden_s_user_is_allowed2/2 byte‑identical: grant (kill_grant.confKflag →is_allowed=1 → operKILL ghostproceeds →401 ERR_NOSUCHNICK) and deny (kill_deny.confTflag, no ACL_KILL →is_allowed=0 →m_nopriv→481 Permission Denied).is_alloweddefinedTfrom ircd‑common in the Rustircd; fullcargo testgreen (all*_diffL1 + 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 ins_user_link.o(C).
- Cleanest L1‑differentiable callee — a pure function of its argument.
-
2026‑06‑03 — P5p (s_user.c KILL handler) merged.
m_kill(s_user.c:2512 — the KILL command) ported intoircd-common/src/s_user.rs, extending the existings_user_link.opartial port (one new#ifndef PORT_USER_P5region around s_user.c:2512‑2692; the cref oracle keeps the full unguardeds_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) hitsm_nopriv). RED confirmed via undefinedm_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_killgates on the Rustis_allowed(P5o), resolves the victim via Rustfind_uid/find_client/get_history(hash/whowas P2), and terminates in the Rustexit_client(P5c);m_noprivis 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+ constsACL_KILL(6)/ACL_KILLREMOTE(4)/EXITC_KILL(75)/SV_UID(1)/ServerChannels_SCH_ERROR/SCH_KILL/FLAGS_KILLED/ERR_CANTKILLSERVER. Reuses theis_an_oper/is_oper/is_server/is_me/is_service_cl/my_connect/is_client/bad_tohelpers. - Config (verified
cbuild/config.h):UNIXPORToff → theIsUnixSocket(cptr)inpath branch not compiled →inpath = cptr->sockhostunconditionally;USE_SERVICESoff → thecheck_services_butone(SERVICE_WANT_KILL,…)block not compiled/not ported;USE_SYSLOG/SYSLOG_KILLoff → the syslog block not compiled.KILLCHASETIMELIMIT=90,TOPICLEN=255. The service‑target SCH_KILL branch and theisdigit(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 twosprintftargets (buf="%s%s (%s)"killer+(L)?+comment;buf2="Local Kill by %s (%s)"/"Killed (%s)") interpolate arbitrary client‑supplied bytes (the KILL commentparv[2]/pathand theindex(path,' ')killer substring), so per the standing P5c/P5l rule a UTF‑8String/format!would corrupt non‑ASCII bytes and can't reproduce the byte‑exactsprintfassembly → faithful byte‑builders (private module staticsKILL_BUF/KILL_BUF2replace the shared Cbuf/buf2, written only transiently within the call → the C remnant'sbuf/buf2are untouched). The wire replies go straight to the C variadic senders (no intermediate string). No sub‑longinteger 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; thechasing && !IsClient(cptr)echo‑back;acptr->flags |= FLAGS_KILLEDset only inside the server‑propagation branch;exitc = EXITC_KILLonly 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‑clientgolden_s_user_kill1/1 byte‑identical: oper alice (kill_grant.confKflag)KILL bob :reasonof a registered target → bob's victim transcript = the:alice!… KILL bob :<inpath>!alice (reason)notice (sendto_prefix_one, MyConnect path) + theERROR :Closing Link: bob[…] (Local Kill by alice (reason))fromexit_client. Two local clients with no servers ⇒ the!MyConnect||!IsAnOperserver‑burst branch is skipped (nosendto_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 bygolden_s_user_is_allowed. Fully deterministic → no new canonicalizer masks.m_killdefinedTfrom ircd‑common in the Rustircd; fullcargo testgreen (45 passed, 0 failed: all*_diffL1 + layout + all golden suites incl. the new kill scenario); 0 warnings. Deferred to S2S L2: the server‑sourced KILL, thesendto_serv_v SV_UIDbroadcast +get_historychase, and the server/service‑targetERR_CANTKILLSERVERpaths (need a server link; ported faithfully, covered by review).
- Every callee already resolved → clean extraction.
-
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_killgating, 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 intoircd-common/src/s_user.rs, extending the existings_user_link.opartial port (one new#ifndef PORT_USER_P5region around s_user.c:332‑846; the cref oracle keeps the full unguardeds_user.o). Utility / callee (nomsgtabrow) — called from still‑Cm_nick(1213)/m_user(2461)/m_unick(1446); defining it#[no_mangle]resolves all three C callers to the Rust def at link (thecanonize/send_away/is_allowedseam). RED confirmed via the sole undefinedregister_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_userthrough the still‑Cm_nick/m_user. No clean L1 data op (side‑effect‑heavy: hash inserts, counters, bannersendto_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/UNIXPORToff → the X‑lineconfwalk, the isvalidusername check, and the IsUnixSocket host branch are not compiled/not ported.NO_PREFIXabsent → the~/^/+/-/=ident‑prefix logic is live (ported).USE_IAUTHon → the iauth early‑parse/XOPT_REQUIRED/null‑username block (incl. thestatic last/count→ module staticsIAUTH_LAST/IAUTH_COUNT) is compiled and ported faithfully (inert under‑ssinceiauth_options==0).WHOISTLS_NOTICE/SPLIT_CONNECT_NOTICEdefined → both NOTICEs ported (the split notice is dropped by the existing canonicalizer).USE_HOSTHASH/USE_IPHASHon →add_to_hostname/ip_hash_tableported.USE_SERVICES/CLIENTS_CHANNELoff → the services block (incl. the secondsend_umode+check_services_num) and the CONN client‑channel notice omitted. - Shared‑static hazard resolved.
register_userwrites the s_user.c file‑staticsbuf/buf2only transiently within the call (the K‑line reason, thenick!user@hostwelcome string, thesend_umodeoutput read at the UNICK loop); the still‑C callers allreturn register_user(...)and never read them afterward, andregister_usernever reads a caller‑set value → private module staticsREG_BUF/REG_BUF2are faithful, the C remnant'sbuf/buf2untouched (cf. P5lPASS_BUF, P5pKILL_BUF). format!per user request — documented in‑module. The welcomenick!user@hoststring and theK‑lined: %.80sreason interpolate arbitrary client‑supplied C‑string bytes (validated nick / ident / resolved host) under byte‑precision copies; per the standing P5c/P5f rule a UTF‑8String/format!would corrupt non‑ASCII bytes, so they stay faithful byte‑builders (push_cstr/store_cbuf). The username‑prefix assembly is byte‑precisionstrncpy. Every wire reply goes straight to a C variadic sender (no intermediate string). The P5c%lu‑width rule applied: every sub‑longint arg to the senders (theis_m_users/is_myclnt/is_l_myclntu_longcounters,fd,flags) is cast to its exact%‑width (%d→c_int,%X→u_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 forcheck_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.aconfvia theSLinkunion; the leaf‑server UNICK loop walksfdas.fd[]/local[](vialocal_base()); the(*buf)?buf:"+"UNICK umode arg.aClient.flagsisc_long→ all flag masks castas c_long. - Verification:
register_userdefinedTfrom ircd‑common in the Rustircd(the still‑Cm_nick/m_user/m_unicknow call the Rust def);golden_registration2/2 byte‑identical; fullcargo testgreen (all*_diffL1 + layout + all golden suites, 0 failed);cargo build0 warnings. No new canonicalizer masks. Deferred to S2S L2 / review: the!MyConnectUNICK‑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) + them_nick/m_unick/m_user/save_user/m_savehandlers stay ins_user_link.o.
- L2 is the gate — the registration banner (the project's anchor scenario since P0‑L2) directly drives
-
2026‑06‑03 — P5r (s_user.c m_user — the USER registration handler) merged.
m_user(s_user.c:2337‑2469) ported intoircd-common/src/s_user.rs, extending the existings_user_link.opartial port (one new#ifndef PORT_USER_P5region around s_user.c:2337‑2470; the cref oracle keeps the full unguardeds_user.o). Handler cluster (msgtab rowUSER=[m_nop, m_reg, m_reg, m_nop, m_user]→ only the unregistered column (4) reachesm_user; a registered client's USER hits the parse stubm_reg). RED confirmed via the sole undefinedm_user; no parse.rs edit. Both wrappers it calls (make_userP2,register_userP5q,mystrdup/inetntopP1,get_client_host/exit_clientP5c) 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, thenregister_user/exit_client; needs a live fd) → like P5q, RED = undefined symbol, GREEN = port, gate =golden_registrationbyte‑identical. ItsUSER alice alice localhost :Aliceline exercisesmake_user+ the "no flags" umode‑parse path ('a'not a digit, not+/-→what==0→ break) + theregister_usertail‑call. A wrong port diffs the banner immediately. - Config‑resolved scope (verified
cbuild/config.h):DEFAULT_INVISIBLEoff (config.h:103#undef) → noSetInvisible;XLINEoff (config.h:460#undef) → nouser2/user3;REALLEN=50 (real‑name truncation live);USERLEN=10 (strncpyzt(user->username,…,USERLEN+1));UFLAGS=FLAGS_INVISIBLE|FLAGS_WALLOP|FLAGS_RESTRICT=28; the function‑localumodes_arr[]={('i',INVISIBLE),('r',RESTRICT),('w',WALLOP)}.aConfItem.flagsisc_long→PFLAG_SERVERONLY/PFLAG_TLScastas c_long;DefInfois*const c_char→ comparedas *mut c_char. - No shared‑static hazard.
m_userhas no file‑statics of its own (umodes_arrfunction‑local;ipbufa stack[c_char; BUFSIZE]) → the C remnant's sharedbuf/buf2/user_modesare 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 viastrchr, real‑nameinfoviamystrdup,usernameviastrncpyzt) under byte‑precision truncation → direct libc copies, notformat!(a UTF‑8 String would corrupt non‑ASCII bytes — the standing P5c/P5f/k rule);ipbufis numeric ASCII frominetntopstrcpy'd intouser->sip; both wire emissions (theSCH_LOCALserver‑only notice viasendto_flag, the462reply viasendto_one) go straight to the C variadic senders.- Faithfulness notes: the umode
+/-token loop reproduces C'sswitch{case '+'/'-':…continue; default:break;}thenif(what==0)breakas a labeled'outerloop (thedefaultbreakfalls through to thewhat==0check, the for‑loopbreakisbreak 'outer); the RFC‑bit branch (isdigit(*umodes)→ scan rest digits →UFLAGS & atoi) ported viais_ascii_digit+atoi; the newis_unknownhelper (status == STAT_UNKNOWN).(*me).serv->refcnt += 1;find_server_string((*me).serv->snum). - Verification:
m_userdefinedTfrom ircd‑common in the Rustircd(the still‑Cm_reg/parse path now routes an unregistered USER to the Rust def);golden_registrationbyte‑identical; fullcargo testgreen (40 test binaries, 0 failed: all*_diffL1 + layout + all golden suites);cargo build0 warnings. No new canonicalizer masks. Deferred to S2S/review: the server‑only‑P‑line reject (find_bounce+exit_client), the TLS P‑line branch, the462re‑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_savestay ins_user_link.o.
- L2 is the gate — the registration banner (the P0‑L2 anchor scenario) directly drives
-
2026‑06‑03 — P5s (s_user.c NICK cluster) merged.
m_nick(s_user.c:855),m_unick(:1261), the file‑staticsave_user(:3470, the SAVE emitter), andm_save(:3506) ported intoircd-common/src/s_user.rs, extending the existings_user_link.opartial port (two new#ifndef PORT_USER_P5regions: m_nick+m_unick at :850‑1450, save_user+m_save at :3462‑3530; the cref oracle keeps the full unguardeds_user.o). Handler cluster —m_nick/m_unick/m_saveare msgtab rows; they form one connected component because all three callsave_user. RED confirmed via undefinedm_nick/m_unick/m_save(save_useris Cstatic→ inlined, never a link symbol). No parse.rs edit. This leaves onlynext_client/hunt_serverins_user_link.o(hash‑dependent → S2S/L2 per P4). Plan:docs/superpowers/plans/2026-06-03-p5s-s_user-nick.md.gotocontrol flow refactored into helper fns (faithful). m_nick has two interior labels reached bygoto:badparamcountkills(the bad‑server‑NICK complaint, returns 0; reached on the server‑parc≠2 fall‑through, thesptr==cptrcollision, andIsServer(sptr)in nickkilldone) andnickkilldone(the validated‑nick tail; reached from ~6 sites). Ported as two private fns —bad_param_count_kills(cptr,sptr,parc,parv)andnick_kill_done(cptr,sptr,nick,parc,parv)(the latter re‑calls the former for itsIsServer(sptr)arm) — so every Cgoto Xbecomesreturn X(...).nick's mutated buffer (possibly rewritten to the UID viastrncpyzt) is threaded through as a*mut c_char;user/hoststay local to m_nick (only the nick‑collision/do_nick_name‑fail blocks use them, never nick_kill_done). Thelpchannel‑walk (sets thereturn 15bigger‑penalty signal) lives entirely in nick_kill_done.- Config that shaped the port (verified
cbuild/config.h):USE_SERVICESoff → everycheck_services_butoneblock (m_nick nick‑change, save_user) omitted;CLIENTS_CHANNELoff → theSCH_CLIENT%s … NICK %sblocks in m_nick/save_user omitted;DISABLE_NICK0_REGISTRATION/DISABLE_NICKCHANGE_WHEN_BANNEDabsent → 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 helpersmy_person/is_conf_serveronly/is_quiet;strncmp/strncasecmpadded to the local extern block;sendto_common_channelsadded 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‑collisionpathstrings (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 Csprintf→ per the standing P5c/P5f/p rule a UTF‑8String/format!would corrupt non‑ASCII bytes, so they're faithful byte‑builders (push_cstrinto the private staticNICK_PATH_BUF, which replaces m_nick/m_unick'schar path[BUFSIZE]and badparamcountkills'schar buf[BUFSIZE]— written only transiently within the call, so the C remnant's sharedbuf/buf2are untouched). Every wire reply goes straight to a C variadic sender. No sub‑longint 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_client→add_client_to_list→make_user→servp/refcnt/server→mystrdup(realname)capped at REALLEN→strncpyztusername/host→reorder_client_in_list→name/uid/sip set + hash inserts→m_umode(NULL,acptr,3,pv)with the stackpv[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 : MEand thecptr ? '!' : ' 'separator in save_user's SAVE propagation;(*sptr).flags |= FLAGS_KILLED as c_long. The leading‑-/digit/anonymousrejects come from the already‑Rustdo_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_nick3/3 byte‑identical: local nick change (registered aliceNICK alice2→sendto_common_channelsechoes:alice!~alice@127.0.0.1 NICK :alice2back to the sender — the nickkilldone "changing" branch: add_history, del/add client hash, strcpy(name)), erroneous nick (NICK -bad→do_nick_name=0 →432 ERR_ERRONEOUSNICKNAME), nick in use (two clients; bobNICK alice→ find_client hit,!IsServer→433 ERR_NICKNAMEINUSE).m_nick/m_unick/m_savedefinedTfrom ircd‑common in the Rustircd; fullcargo testgreen (all*_diffL1 + layout + all golden suites incl. the 3 new nick scenarios, 0 failed);cargo build0 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 toircd-common/src/channel.rs. First channel.c sub‑phase — establishes thechannel_link.opartial‑port mechanism (‑DPORT_CHANNEL_P5#ifndef‑guards the four C defs out of the link set; the cref oracle keeps the full unguardedchannel.o). These are the only truly‑leaf channel.c exports (no file‑static, no sharedbuf/modebuf/parabuf/uparabufuse). Plan:docs/superpowers/plans/2026-06-03-p5t-channel-leaf-predicates.md.channel_link.owiring gotcha (cost one RED cycle): a partial port must NOT addchannel.otoPORTED—link_objs()filters PORTED entries before thechannel.o→channel_link.o.map, so the substitution never fires and the whole TU goes undefined. The seam is: leavechannel.oout ofPORTED, add thechannel.o→channel_link.oarm tolink_objs(), and a plainsh -ccompile step (channel.o's recipe carries no per‑object‑D, unlike send/s_user, so nomake --evalneeded). After fixing: RED showed exactly the four leaf symbols undefined; GREEN resolves themTfrom ircd‑common.- Classification & seam:
is_chan_op/has_voiceare callee predicates,find_channel/clean_channelnamecallee utilities (none a msgtab row). All four are already imported by still‑C channel.c callers and the already‑Rusts_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 (thecanonize/send_awaypattern). No parse.rs edit.find_user_link/hash_find_channelare the P2 Rust ports;my_connect/is_person/is_restrictedinline helpers mirror s_user.rs.can_senddeferred to the mode‑manipulation cluster (it calls the file‑staticmatch_modeid). JAPANESE off →clean_channelname's comma‑flagblock isn't compiled (flagalways 0);jp_*/get_channelmaskabsent. format!per user request — N/A, documented in‑module. None of the four builds an ASCII/numeric reply string:clean_channelnamemutates the caller's buffer in place,find_channelreturns a pointer,is_chan_op/has_voicereturn ints.format!stays reserved for the reply‑building clusters.- Verification: L1
channel_leaf_diffzero‑diff vscref_(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 afterinithashtables; clean_channelname over 10 strings comparing return + mutated bytes). L2golden_channel1/1 byte‑identical (registered aliceJOIN #chanunder‑p standalone→ JOIN echo +353with@alice[is_chan_op marks the creator chanop] +366). Fullcargo testgreen (0 failed);cargo build0 warnings. No new canonicalizer masks. Deferred:has_voicehas 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 inchannel_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. Newircd-goldenprimitives (committed first, then a 12‑agentultracodeworkflow authored one test per cluster, each probing both binaries before writing):boot_s2s+ aPeerfake‑server that completes the IRCnet 2.11 server handshake (PASS linkpass 0211030000 IRC|aHmM/SERVER peer.test 1 001B …→ drains the burst throughEOB) and then speaks the SID/UID‑prefixed server protocol; thes2s.conffixture (M/A/P/Y/I + C/N/H lines authorizing an inbound link from127.0.0.1, namepeer.test, passlinkpass). 13golden_s2s_*scenarios, all byte‑identical ref‑C == Rust aftercanonicalize()(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_cidstarts 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 SID001B, server SID000A;‑p standalonerefuses all links ("Running in standalone mode") so S2S boots use plain‑t ‑s(server starts in split‑mode until the peer links, whichcanonicalize()already handles via the split‑mode NOTICE drop). No new canonicalizer masks were required by any scenario. golden_s2s_link(P5sm_unick+ P5dm_whois): peer links, a local client rides the burst as:000A UNICK alice 000AAAAAA …, then the peer introduces remotebob(001BAAAAA) via UNICK and the local clientWHOIS bob→311 … peer.test/312/318. The foundation proof.golden_s2s_message(P5f): bidirectional PRIVMSG through the portedm_message/m_privatecore. remote→local::001BAAAAA PRIVMSG alice :…→ alice gets:bob!~bob@remote.host PRIVMSG alice :…(sendto_prefix_one full‑mask prefix). local→remote:PRIVMSG bobroutes 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(P5gsend_away): peer sends:001BAAAAA AWAY :gone fishing; alice PRIVMSGes + WHOISes bob, hittingsend_awayfrom the m_message and m_whois remote‑target branches. Faithful subtlety pinned:m_awaystoresuser->awayonly underMyConnect(sptr), so a remote user's away text is never stored here →send_awaytakes itselsebranch and emits the generic301 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(P5hsend_umode_out/send_umode): alice (in the burst) sendsMODE 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 beforePeer::link().golden_s2s_quit(P5km_quit+exit_one_client): localQUIT :leaving→ peer receives:000AAAAAA QUIT :"leaving". Pins two faithful details: local QUIT reason is wrapped in literal double‑quotes bym_quit(the sentinelexit_one_clientuses to classify a genuine QUIT), and the propagation source is the UID, not the nick.golden_s2s_kill(P5pm_killIsServer(cptr)branch): peer sends:001B KILL 000AAAAAA :peer.test (spam)→ victim sees:peer.test KILL alice :peer.test!peer.test (spam)thenERROR :Closing Link: alice[~alice@127.0.0.1] peer.test (Killed (peer.test (spam)))(theKilled (<killer>)branch, notLocal Kill by); socket close confirmed on both binaries.golden_s2s_squit(P5cexit_client→exit_server): introduce remotebob, confirm live WHOIS, drop the peer TCP socket (netsplit) → after settle,WHOIS bob→401 … :No such nick/channel/318on both binaries (the recursive reap of dependents behind the dead link).golden_s2s_nick(P5sm_nickserver branch): peer introducesbobthen sends:001BAAAAA NICK :newbob; WHOISnewbob→311 … peer.test, WHOISbob→401(thesendto_serv_butone(":%s NICK :%s")propagation + del/add client‑hash retarget).golden_s2s_save(P5sm_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, SAVEpatha fixed literal.golden_s2s_userhost(P5e): remotebob→USERHOST bob→302 alice :bob=+~bob@remote.host(+not away, no*not oper);ISON alice bob→303 alice :alice bob(trailing space per the C loop).golden_s2s_who(P5dwho_oneclassic‑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) then315 … bob :End of WHO list.(mask keyed on the literalbob, not*).golden_s2s_register_feed(P5q): peer links and drains the burst before any local client exists (burst has no UNICK), then a fresh localcarolregisters → peer receives:000A UNICK carol 000AAAAAA ~carol 127.0.0.1 127.0.0.1 + :carol(theregister_userleaf‑server feed loop, unreachable by the standalone harness).golden_s2s_service(P5am_service/m_squery): peer introduces a remote service via:001B SERVICE chanserv@peer.test * 0 :Channel Service(theIsServer(cptr)introduce branch; USE_SERVICES off so the client SERVICE path is rejected — only the common tail runs), then localSQUERY chanserv@peer.test :hello thereroutes 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 build0 warnings. The S2S harness lives entirely inircd-golden/src/lib.rs(Peer,boot_s2s) +ircd-golden/fixtures/s2s.conf— no change to any portedircd-commonsource (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), andm_ping/m_pongserver‑col paths (P5i) — left for the channel mode‑cluster + a future burst harness.
- Foundation findings (verified empirically, both binaries): the handshake reply + burst is byte‑identical; local UIDs are deterministic & sequential (first local user =
-
2026‑06‑03 — P5u (channel.c invite cluster) merged.
m_invite(channel.c:3260), the file‑staticsadd_invite(:2176) +list_length(:85),check_channelmask(:2100), and the exporteddel_invite(:2233) ported toircd-common/src/channel.rs, extending the existingchannel_link.opartial port (five new#ifndef PORT_CHANNEL_P5regions; the cref oracle keeps the full unguardedchannel.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/modebufused transiently can be duplicated — established P5 practice). Verified edges:list_length→onlyadd_invite;add_invite→onlym_invite;del_invitenon‑static (no linkage issue);check_channelmask→10 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 exceptcheck_channelmask, handled by the extern‑prototype switch below. - Keystone mechanism — static function with remaining C callers → extern‑prototype switch.
check_channelmaskis a file‑staticwhose definition we drop but whose callers mostly stay C. Astaticsymbol 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_P5the 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‑Cfree_channel/m_kick/m_njoincallers + the already‑Rusts_misc.rsexit‑cascade caller to the Rust def (thecanonize/send_awayseam). No parse.rs edit (m_inviteresolves from the bindings import once the C def drops). - Config (verified
cbuild/config.h):JAPANESEoff →m_invite's#ifdef JAPANESE jp_validblock not compiled/not ported;get_channelmask(x)is the macrorindex(x, ':')→ incheck_channelmaskit is just libcstrrchr, no static dragged in.USE_SERVICESoff (nocheck_services_*in this cluster).MAXCHANNELSPERUSER=21 (config.h:505) — bindgen drops the macro → hardcoded as a resolved literal inadd_invite. - Faithfulness notes:
del_invite/add_invitereproduce the original's crossed istat bookkeeping verbatim —add_invitechannel‑side doesis_useri++, client‑sideis_banmem += len/is_invite++;del_invitechannel‑sideis_invite‑‑, client‑sideis_banmem ‑= strlen(who)+1/is_useri‑‑(note the original's+= lenvs‑= len+1off‑by‑one is kept). Thewhostring ("<nick>!<user>@<host>") is built into a[u8;91](NICKLEN+USERLEN+HOSTLEN+3) thenMyMalloc+strcpy'd, byte‑exact.m_invitemirrors the twochptr ? chptr->chname : parv2ternaries and theMyConnect(acptr) && chptr && sptr->user && is_chan_opadd_invite guard. format!per user request — N/A, documented in‑module.add_invite'swhointerpolates arbitrary client‑supplied C‑string bytes → byte‑builder, notformat!(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‑longint 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_channelmaskare C file‑statics → thecref_prefix‑rename keeps them local, so there is nocref_*oracle symbol to diff against (the first P5u attempt hitundefined symbol: cref_check_channelmaskat link). Per the established practice they are covered by L2 instead.del_invite(exported) is the lone L1 target:channel_invite_diffbuilds parallelchptr->invites(Link) +cptr->user->invited(invLink, with libc‑malloc'dwho) 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) + identicalis_invite/is_useri/is_banmemdeltas (the two tests serialize on aMutexsince they mutate the process‑globalistat/cref_istat). - Verification: L1
channel_invite_diff2/2 zero‑diff vscref_del_invite. L2golden_channel_invite4/4 byte‑identical: success (alice chanop of #chanINVITE bob #chan→341 RPL_INVITINGto alice +:alice!~alice@127.0.0.1 INVITE bob :#chanto bob — the only path that fires the privateadd_invite/list_lengthlive),401(INVITE ghost → find_person miss),442(alice not a member of bob's #other),476(check_channelmaskreject on#chan:nomatch.zzz+ the MyClient ERR_BADCHANMASK send).m_invite/del_invite/check_channelmaskdefinedTfrom ircd‑common in the Rustircd; fullcargo testgreen;cargo build0 warnings. No new canonicalizer masks (no time tokens). Deferred to S2S L2: the!chptrremote‑INVITE forward (sendto_prefix_oneto a remote acptr), the&‑channel +!MyClient(acptr)reject, andcheck_channelmask's server/chan:maskpropagation 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 inchannel_link.o.
- 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
-
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) andmatch_modeid(channel.c:314 — the ban/exceptionmlistmatcher) ported toircd-common/src/channel.rs, extending the existingchannel_link.opartial port (three new#ifndef PORT_CHANNEL_P5regions: thematch_modeidforward proto switch at :75, thematch_modeidbody, thecan_sendbody; the cref oracle keeps the full unguardedchannel.o). Third channel.c sub‑phase. Completes the P5t deferral ("can_senddeferred to the mode cluster — it calls the staticmatch_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 onlyIsMember/find_user_link/match_modeid;match_modeid(static) calls onlymatch/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 forwardstatic→externat channel.c:75 under the guard, so the C remnant resolves to the Rust#[no_mangle]def at link; linkage‑only, faithful).can_sendis already imported by the Rustm_message(P5f, s_user.rs:1155) + the WHO path (s_user.rs:3439) fromircd_sys::bindings→ those bindgen decls resolve to the Rust def once the C def drops (theis_chan_op/has_voiceP5t 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 amsgtabrow). - Classification → L1/L2 split.
can_sendis exported →cref_can_sendexists → L1‑differentiable (the workhorse).match_modeidis a C file‑static→ thecref_prefix rename keeps it local (nocref_match_modeidoracle symbol, cf. P5ucheck_channelmask) → not L1‑differentiable in isolation; its empty‑mlistNULL‑return is exercised viacan_send's ban path in L1, and the populated+bmatch is L2/review‑covered. - Keystone finding — a latent P5u
is_memberfaithfulness bug, fixed here. Theis_memberhelper (added in P5u form_invite) cited the commented‑outstruct_def.h:773(find_user_link(c->members, u)). The activeIsMember(struct_def.h:775, lines 771‑773 are inside a/* */block) isu && 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 viachptr->members, the oracle didn't via the emptyuser->channel→0vs256). Fixedis_membertofind_channel_link(matching the active macro); the P5ugolden_channel/golden_channel_invite+channel_invite_diff/channel_leaf_diffall 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 bothchptr->members(→lp) andwho->user->channel(→IsMember). - Config (verified
cbuild/config.h):JAPANESEoff →can_sendhas no JP branch.MODE_MODERATED=32/MODE_NOPRIVMSGS=256/MODE_BAN=1024/CHFL_BAN=8/CHFL_EXCEPTION=16 (bindgen consts);(*chptr).mode.modeisu_int;aConfItem.flagsisc_long(castCFLAG_NORESOLVEMATCH as c_long). New helpersis_an_oper/is_conf_no_res_matchmirror s_user.rs;match_ipmask/find_channel_link/aConfItem/Link+ the mode/flag consts added to the bindings import;strchradded to the local extern block. - Faithfulness notes:
match_modeidreproduces thefor‑loop post‑continueadvance (tmp = tmp->next; continue;at eachcontinuesite, the natural advance at the bottom for the flags‑mismatch fall‑through), theisdigit(nick[0])UID‑ban fallback, the n!u→host/sockhost/sip/CIDR cascade, and the oper conf‑chain scroll (acf = acf->nextwhenIsAnOper); arrays (username/uid/host/sip/sockhost) passed viaaddr_of_mut!,name/alist->nickare pointers.can_sendcomputesmember+lpunconditionally, returns 0 for!MyConnect, and the+beban 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_diffzero‑diff vscref_can_sendover 8 cases (+m non‑voiced/voiced/chanop member + non‑member, +n member/non‑member, plain‑member empty‑mlist ban path, remote!MyConnectbypass). L2golden_channel_can_send2/2 byte‑identical: +m moderated (alice chanop sets +m, bob joins as a plain member → bob PRIVMSG →404 ERR_CANNOTSENDTOCHANvia the Rustm_message→Rustcan_sendMODE_MODERATED branch) and +n no‑external (bob non‑member → MODE_NOPRIVMSGS branch); read‑barriers (366/MODEechoes) serialize the cross‑connection ordering so +m/+n is in effect before bob sends.can_send/match_modeiddefinedTfrom ircd‑common in the Rustircd; fullcargo testgreen (0 failed);cargo build0 warnings. No new canonicalizer masks (no time tokens). Deferred to S2S L2 / review:match_modeid's populated‑mlist+b/+ematch (incl. the UID‑ban and CIDRmatch_ipmaskpaths) and the remote‑clientcan_sendbypass 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 inchannel_link.o.
- Cluster = a closed connected component over static call edges.
-
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'smbufand the+l/+kparameters intopbuf) ported toircd-common/src/channel.rs, extending the existingchannel_link.opartial port (one new#ifndef PORT_CHANNEL_P5region around the body; the cref oracle keeps the full unguardedchannel.o). Fourth channel.c sub‑phase. Plan:docs/superpowers/plans/2026-06-03-p5w-channel-channel_modes.md.- Classification: exported true‑leaf callee (no
msgtabrow).channel_modestouches no file‑static — it writes into the caller‑providedmbuf/pbuf— and calls only theIsMember/IsServermacros + libcsprintf/strcat. So a plain#ifndefguard drops the exported def and the still‑C callers resolve to the Rust def at link: them_modequery path (channel.c:1112,MODE #chanwithparc < 3→channel_modesthenreplies[RPL_CHANNELMODEIS]),set_mode(mode‑change echo), andsend_channel_modes(the S2S burst — the symbol that referencedchannel_modesat link, confirming RED). The#ifdef USE_SERVICESm_join call (channel.c:1987) is not compiled. No parse.rs / build.rs edit. - Config findings (verified): the body (831‑866) has no
#ifdefinside it — the full bit cascade compiles verbatim.nm cbuild/channel.oshowsT channel_modes(exported).JAPANESE/USE_SERVICESoff — 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/variadicsprintfexterns to channel.rs. - Faithfulness notes: the
+prefix then each bit in exact C order (s else p; m; t; i; n; a; q; r), thenl(sprintf(pbuf,"%d ",limit)ifIsMember||IsServer), thenk(strcat(pbuf, key)ifIsMember||IsServer), then the terminator. The C*mbuf++ = '\0'post‑advance is dead (mbuf never re‑read) → written in place to avoid a spuriousunused_assignmentswarning (behaviorally identical).IsMember= the activefind_channel_link(cptr->user->channel, chptr)(the P5v‑corrected helper);IsServer=is_server.format!per user request — N/A, documented in‑module: the+l/+kparams must match Csprintf("%d ")/strcatbyte‑for‑byte into the caller's rawchar*buffer;format!would change framing. Kept as the libc calls. - Verification: L1
channel_modes_diffzero‑diff vscref_channel_modesover 17 cases (empty+, every single bit, secret‑wins‑over‑private precedence,+ntl/+nk/+tlkparams for a server cptr, the same+tlkfor a non‑member [params suppressed → emptypbuf], and the all‑bits+l+k full house) — both thembufandpbufbyte buffers asserted identical. L2golden_channel_modes1/1 byte‑identical: alice (chanop → member) sets+tnl 5then+k secret, then theMODE #chanquery →324 RPL_CHANNELMODEIScarrying the mode string + the+l 5(sprintf) and+k secret(strcat) params.channel_modesdefinedTfrom ircd‑common in the Rustircd;cargo build0 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 inchannel_link.o.
- Classification: exported true‑leaf callee (no
-
2026‑06‑03 — P5x (channel.c membership cluster) merged.
add_user_to_channel(channel.c:424),remove_user_from_channel(:493, exported), andchange_chan_flag(:564) ported toircd-common/src/channel.rs, extending the existingchannel_link.opartial port (‑DPORT_CHANNEL_P5). Fifth channel.c sub‑phase — themembers/clist/user->channellist manipulators + theistatchannel 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/uparabufstatic (unlike the mode/display clusters) — their only remaining‑C edge is the staticfree_channel, whichremove_user_from_channelcalls when the last user leaves. Twostatics (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 → addedexternforward decls under the guard.free_channelis kept C but switchedstatic→externunder#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_channelis exported and already called from the Rusts_misc.rsexit 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.o→t 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_SERVICESoff → thecheck_services_butonecalls inside add/remove (channel.c:460‑470, :540‑550) are#ifdef'd out (skipped). ActiveMODE_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_channel—chptr->users++ == 0is the unconditional post‑increment (was_zerocapture then+= 1); the locked‑channel history resetbzero(&chptr->mode, sizeof(Mode))→ptr::write_bytes(addr_of_mut!((*chptr).mode) as *mut u8, 0, size_of::<Mode>())gated on*chname != '!'; the per‑uplinkclistlink found/created onwho->fromthenflags++.remove_user_from_channel— a leavingCHFL_CHANOParmschptr->reop = timeofday + LDELAYCHASETIMELIMIT + myrand()%300(the L1 fixture uses non‑chanop members so this non‑deterministicmyrand()path is not exercised — it would diverge Rust's livemyrandfrom the oracle'scref_myrand);if (tmp2 && !--tmp2->flags)predecrement drops the clist link at 0;--chptr->users <= 0→ theis_chan/is_chanmem/is_hchan/is_hchanmemswap +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_diffzero‑diff vscref_remove_user_from_channel: a 2‑member channel with both members linked intochptr->members+ eachwho->user->channel+ a per‑uplinkclistentry; removing one member asserts identical survivingmembers/clist/user->channelchains,chptr->users,joined--, and theis_userc/is_chanusersdeltas — deliberately keepingusers >= 1so the last‑userfree_channelteardown (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‑Rustadd_user_to_channeldirectly (a C static → nocref_oracle) to add two members, remove one and assert it is actually gone frommembers/clist/user->channelwith the counters decremented, then re‑add and assert no duplicate node + theis_userc/is_chanuserscounters return exactly to the two‑member values (a botched removal passes the positive‑only differential but leaks/double‑counts here).change_chan_flagis a C static → nocref_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_flagset the flag,is_chan_opreads it).golden_channel(JOIN) still coversadd_user_to_channel. S2Sgolden_s2s_membership— a remote NJOIN (m_njoinC → Rustadd_user_to_channel; the server‑sideget_channel(&me, CREATE)creates the federated#chana split lone link won't let a local client create) then a local observer JOINs and sees@bob, then a remote PART (m_partC → Rustremove_user_from_channel) the observer sees + a NAMES no longer listing bob.cargo build0 warnings; fullcargo testgreen (41 test binaries incl. the newchannel_membership_diffL1 +golden_channel_part/golden_s2s_membershipL2). Split‑mode finding: inboot_s2sa single configured uplink keeps the server permanently split (IsSplit()), so a local client cannot create a federated#channel (m_join:2591ERR_UNAVAILRESOURCE) — but can JOIN an existing one (the split guard is insideif(!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 inchannel_link.o.
- Cluster shape & the linkage moves. The three touch no shared
-
2026‑06‑03 — P5y (channel.c
m_topichandler) merged.m_topic(channel.c:3208‑3300 — the TOPIC command: per‑channel query [331/332/333] or set [propagate + 482 reject]) ported toircd-common/src/channel.rs, extending the existingchannel_link.opartial port (one#ifndef PORT_CHANNEL_P5region around the body; the cref oracle keeps the full unguardedchannel.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_topicis amsgtabrow (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 usesfind_channel, notget_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#ifndefguard drops the C def and them_topicdecl parse.rs already imports fromircd_sys::bindingsresolves to the Rust def at link. No extern‑prototype switch, no parse.rs/build.rs edit. RED confirmed via the sole undefinedm_topic. - Config (verified
cbuild/config.h):TOPIC_WHO_TIMEdefined (config.h:522) → thetopic_t/topic_nuhblocks (the 333 RPL_TOPIC_WHO_TIME reply on query, thetopic_nuh = "%s!%s@%s"build +topic_t = timeofdayon set) are compiled and ported.USE_SERVICESoff → thecheck_services_butone(SERVICE_WANT_TOPIC, …)block not compiled/not ported.JAPANESEoff (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 importscanonize/strtoken/cid_ok/RPL_NOTOPIC/RPL_TOPIC/RPL_TOPIC_WHO_TIME/ERR_NOCHANMODES/ERR_NOSUCHCHANNEL; new externssendto_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 clientname/username/hostC‑strings) is a byte‑builder (build_nuh, mirroring theadd_invitewhobuilder) — notsprintf/format!(a UTF‑8Stringwould corrupt non‑ASCII bytes; the standing P5c/P5f rule).topicis a byte‑precisionstrncpyzt. Every wire reply (the TOPIC echo, 332/333, the rejects) goes straight to a C variadic sender. The 333topic_t(time_t) passed to%luis cast tou_long(the P5c width rule).- Faithfulness notes: the
strtoken(&p, parv[1], ",")per‑target loop withparv[1]=NULLadvance; thefind_channelmiss doesreturn penalty(notcontinue);IsChannelNamereject → 442,!UseModes→ 477,!IsMember→ 442,check_channelmask→ continue; query (topic==NULL) → empty topic → 331, else 332 + (iftopic_t > 0) 333 withIsAnonymous(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_servsUID‑sourced +sendto_channel_butservname‑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_topic2/2 byte‑identical (-p standaloneso#chancan 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). S2Sgolden_s2s_topic1/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 viasendto_channel_butservand her query returns 333 carrying bob'stopic_nuh(the remote‑user fields only an S2S link produces); then local alice sets the topic →sendto_match_servspropagates:000AAAAAA TOPIC #chan :…(UID‑sourced) to the peer. New canonicalizer mask: 333 RPL_TOPIC_WHO_TIME's trailingtopic_t(=timeofday) is wall‑clock volatile between the separately‑booted ref‑C/Rust → masked to<T>.m_topicdefinedTfrom ircd‑common in the Rustircd;cargo build0 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 inchannel_link.o.
- The cleanest channel.c handler — no linkage juggling.
-
2026‑06‑03 — P5z (channel.c
m_listhandler) merged.m_list(channel.c:3402‑3542 — the LIST command: bare lists all visible channels, or per‑name/!‑mask forms) ported toircd-common/src/channel.rs, extending the existingchannel_link.opartial port (one#ifndef PORT_CHANNEL_P5region around the body; the cref oracle keeps the full unguardedchannel.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_listis amsgtabrow (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_serverstill C; the variadicsendto_one). So a plain#ifndefguard drops the C def and them_listdecl 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 undefinedm_list. - The
sendto_onereturn type.m_listis the first ported caller that needssendto_one's return value (rlen += sendto_one(…)— the remote‑LIST reply‑length throttle vsCHREPLLEN). The ircd‑common module‑local extern decls had declared it-> (); sinceclashing_extern_declarationsis 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 — ac_intisn't#[must_use], so no warning). The cross‑crate bindings decl already returnedc_int. - Config (verified
cbuild/config.h):LIST_ALIS_NOTEdefined (config.h:516) → both#ifdef LIST_ALIS_NOTENOTICE blocks compile and are ported (the opening notice whenMyConnect(sptr)in the bare branch, and the trailing notice whenMyConnect && listedchannels > 24); the note string is reproduced as a byte‑exactc"…"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 importschannel(aschannel_list)/get_sendq/hunt_server/RPL_LIST/RPL_LISTEND/ERR_TOOMANYMATCHES. format!per user request — N/A, documented in‑module.m_listbuilds no reply string of its own — every line goes straight to the C variadicsendto_one(the RPL_LISTchname/topicare arbitrary client bytes). Themaxsendq = (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; theDBufLength > maxsendqtest mirrors C's unsigned comparison (maxsendq as u_int).- Faithfulness notes:
parc > 2 && hunt_server(…, 2, …) != 0→return 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 oversptr->user->channellisting onlySecret||Hiddenchannels, then loop 2 over the globalchannellist skipping!users || Secret || Hidden— each with theDBufLength > maxsendq→ ERR_TOOMANYMATCHES throttle (loop 1'sgoto end_of_listmodelled asbreak 'lists, loop 2'sbreakfalls through), then the trailing notice. Named arg (else):canonizethen per‑tokenstrtoken(&p, parv[1], ",")withparv[1]=NULLadvance;find_channel && ShowChannel && sptr->user→ RPL_LIST;*name=='!'→hash_find_channels(name+1, …)mask loop with thescr = SecretChannel && !IsMemberusers‑-1/topic‑""masking; both with the!MyConnect && rlen > CHREPLLENbreak. Tail:!MyConnect && rlen > CHREPLLEN→ ERR_TOOMANYMATCHES; always RPL_LISTEND;return 2. - L2 + L2‑S2S are the gates (no clean L1 data op —
m_listonly reads channel state and sends wire output). L2golden_channel_list1/1 byte‑identical (-p standalone): bareLISTshows two public channels (322) + the ALIS notice + 323, and a+ssecret channel a non‑member (alice) is not on does not appear (the inverse); namedLIST #pub→ just that channel;LIST #nonexistent→ only 323 (thefind_channelmiss). S2Sgolden_s2s_list1/1 byte‑identical: thehunt_serverforwarding path the standalone harness can't reach — a local client issuesLIST #chan peer.test(parc > 2, parv[2] = the linked peer) and the peer receives the forwardedLISTcommand byte‑for‑byte under ref‑C and Rust, confirming the Rustparc > 2branch invokeshunt_serverand short‑circuits (return 10) exactly as C.m_listdefinedTfrom ircd‑common in the Rustircd;cargo build0 warnings; the six channel goldens (channel/topic/part/modes/can_send/invite) stay green after thesendto_onesig 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 inchannel_link.o.
- A second true‑leaf handler (like P5y).
-
2026‑06‑03 — P5aa (channel.c
m_parthandler) merged.m_part(channel.c:2952‑3038 — the PART command: leave one or more channels) ported toircd-common/src/channel.rs, extending the existingchannel_link.opartial port (one#ifndef PORT_CHANNEL_P5region 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_partis amsgtabrow (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 usefind_channel), it resolves each named channel viaget_channel(sptr, name, 0)— astaticin C that stays C (the channel‑lifecycle cluster isn't ported yet). So the P5u/P5x extern‑switch was applied toget_channel: the forward decl (channel.c:69) and the definition storage class (channel.c:2174) both flipstatic→externunder#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_butoneadded to the extern block;sendto_match_servs/sendto_channel_butservalready there).get_channelmask(name)is the JAPANESE‑off macrorindex(name,':')=strrchr. RED confirmed via the sole undefinedm_part(get_channel resolved as C). - Config (verified):
JAPANESEoff → 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 JAPANESEexternget_channelmask/jp_validdecls are skipped and the#elsemacro form applies.USE_NEWMSGoff (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 helpermy_person(MyConnect && IsPerson, struct_def.h:791). - The shared
static buf[BUFSIZE]→ a module‑privatestatic mut BUF. channel.c'sbufis shared across the TU, butm_partuses 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 privatestatic mut BUF: [c_char; BUFSIZE]in the Rust module is faithful (not ABI → no#[no_mangle]; accessed viaaddr_of_mut!). Thesize = BUFSIZE - strlen(parv[0]) - 10C size_t arithmetic is mirrored withwrapping_suband thestrlen(buf)+strlen(name)+1 > sizetest comparessize as usize(the exact C int→size_t promotion). format!per user request — N/A, documented in‑module.m_partbuilds no reply string of its own; the comma‑joinedbufis assembled with libcstrcatbyte‑for‑byte (arbitrary client channel‑name bytes — aStringwould corrupt non‑ASCII), and every reply/relay goes straight to a C variadic sender. PartFmt (":%s PART %s :%s") is inlined as ac"…"literal at each call site.- Faithfulness notes:
comment = BadPtr(parv[2]) ? "" : parv[2]readsparv[2]directly (the parser NULL‑pads parv past parc, soparcis unused —_parc, matching C);strlen(comment) > TOPICLEN→comment[TOPICLEN] = '\0'. Per‑token loop (strtoken(&p, parv[1], ","),parv[1]=NULLadvance):get_channelmiss →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 intobuf(flush‑on‑overflow); otherwisesendto_match_servsrelays it immediately with the per‑channel name; alwayssendto_channel_butservechoes:nick PART #chan :commentto members andremove_user_from_channelunlinks the parter. Tail: a non‑emptybufgets one finalsendto_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 withpart_errors_match_reference: m_part's own 403 (PART a nonexistent channel) and 442 (PART a channel you're not on) numerics, previously unexercised. S2Sgolden_s2s_membershipalready covers theIsServer(cptr)/MyPersonremote‑PART branch — a remote user behind the peer PARTs (:001BAAAAA PART #chan), routed through the now‑Rust m_part'ssptr->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 build0 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 inchannel_link.o.
- First handler in channel.c that calls the file‑static
-
2026‑06‑03 — P5bb (channel.c
m_kickhandler + file‑staticfind_chasing) merged.m_kick(channel.c:3068‑3221 — the KICK command) and the file‑staticfind_chasing(channel.c:139 — history‑aware nick resolver) ported toircd-common/src/channel.rs, extending the existingchannel_link.opartial port (-DPORT_CHANNEL_P5). Ninth channel.c sub‑phase. Plan:docs/superpowers/plans/2026-06-03-p5bb-channel-m_kick.md.find_chasingis the cluster's shared static — the P5u/P5x extern‑switch case.m_kickis its caller, butfind_chasingis also called by the still‑Cset_mode/m_mode(channel.c:~1301), so the C body is guarded out under#ifndef PORT_CHANNEL_P5and the#elsebranch adds anextern 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_kickdoesn't call it (onlymake_bei/m_modedo). RED confirmed via the two undefined symbolsfind_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 flag0=!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→ libcfree,strlen/strcatlibc; the C variadic senderssendto_flag/sendto_match_servs_vadded to the local extern block (sendto_one/sendto_channel_butservalready there). Inline macro helpers all present except astd::cmp::maxfor the CMAX. Numerics 403/477(ERR_NOCHANMODES)/442/482 + 401 (find_chasing) + 441 (ERR_USERNOTINCHANNEL, added to the import). ConstsMAXPENALTY=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 sharedbuf/modebuftouched. The C size_t arithmetic is mirrored exactly:maxlen = BUFSIZE - MAX(strlen(sender),strlen(name)) - strlen(comment) - 10viawrapping_sub … as c_int, andclen = maxlen - strlen(name) - 1via(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 servercptr, find_chasing only when that misses).sender= SID (server) / UID (person) / name; the relay echoessptr->name/who->nameto members but the comma‑batchedsendto_match_servs_v(SV_UID,…)carries UIDs (who->user->uid). Returnspenalty. format!per user request — N/A, documented in‑module.find_chasingreturns a pointer (its one 401 goes to the C sender);m_kickassembles the comma‑joined victim‑id listnbufwith libcstrcatbyte‑for‑byte (arbitrary client nick/UID bytes — aStringwould corrupt non‑ASCII) and routes every wire line through the C variadic senders.- L2 + L2‑S2S are the gates (no L1 —
find_chasingis file‑static so nocref_oracle symbol, cf. P5u/P5x statics;m_kickis a socket‑driven handler with no extractable data‑op core, cf. m_topic/m_list/m_part). Newgolden_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). Newgolden_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 :…) → theIsServer(cptr)+find_uidvictim 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 build0 warnings; no new canonicalizer masks. Also fixed a pre‑existing timing flake ingolden_s2s_membership(s2s_deop_and_clean_readd): the re‑NJOIN:bob JOINrelay raced the 300 ms drain window — now consumed deterministically viaread_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 inchannel_link.o.
-
2026‑06‑03 — P5cc (channel.c display cluster —
m_nameshandler + file‑staticnames_channel) merged.m_names(channel.c:3730 — the NAMES command) and the file‑staticnames_channel(channel.c:3577 — the per‑channel353 RPL_NAMREPLY/366 RPL_ENDOFNAMESemitter) ported toircd-common/src/channel.rs, extending the existingchannel_link.opartial port (-DPORT_CHANNEL_P5). Tenth channel.c sub‑phase. Plan:docs/superpowers/plans/2026-06-03-p5cc-channel-m_names.md.names_channelis the cluster's shared static — the P5u/P5x extern‑switch case.m_namesis one caller, butnames_channelis also called by the still‑Cm_join(channel.c:2673, the JOIN echo). So itsstaticforward decl (channel.c:105) is switched toexternunder#ifndef PORT_CHANNEL_P5, and its body is guarded out, so the Cm_joinremnant resolves to the Rust#[no_mangle]def at link (linkage‑only, faithful).m_njoindoes not call it (verified — the only other channel.c caller besides m_names is m_join).m_namesis a plain#ifndefguard (no extern switch — its decl is imported by parse.rs fromircd_sys::bindings, resolving to Rust oncechannel_link.odrops the C def). RED confirmed via the two undefined symbolsnames_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 variadicsendto_one(P8 C trampoline). Inline macro helpers all present except a newis_invisible(x->user->flags & FLAGS_INVISIBLE, mirroring s_user.rs:144); globalsclient/channel/me+ numericsRPL_NAMREPLY=353/RPL_ENDOFNAMES=366 +FLAGS_INVISIBLEadded to the import.BUFSIZE/MAXCHANNELSPERUSER/MAXPENALTYalready module consts. - Shared
buf— the keystone faithfulness point. In C,m_namesandnames_channelboth use the file‑staticchar buf[BUFSIZE](channel.c:115). They are not reentrant:names_channelfillsbufand flushes it (sendto_one) within each call, andm_namesonly writesbufitself (the thirdstrcpy(pbuf,"* * :")remaining‑users section) after all itsnames_channelcalls — so reusing the existing moduleBUF(introduced for P5aam_part) is faithful, exactly as C shares onebuf. Documented residual: the still‑Cm_joinkeeps its own Cbuf; in its!‑safe‑channel autocreate branch it doessprintf(buf,…); name=buf;and re‑readsnameafter thenames_channelcall (which, in ref‑C, clobbersbuf). Withnames_channelnow writing the RustBUF, Cm_join'snamesurvives 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#/&/+wherenameis the parv token, neverbuf) and disappears whenm_joinitself ports andbufbecomes Rust‑owned. Noted, not tested. - Faithfulness notes.
names_channel'scptrparam is unused in C (onlysptr/to/chptr/sendeonare read) →_cptr. Pointer arithmetic mirrored raw:*pbuf = ch; pbuf = pbuf.add(1)for the@/+/=/*prefix bytes; the353member list assembled withcopy_nonoverlapping(arbitrary client nick bytes — aStringwould corrupt non‑ASCII); themaxlen = BUFSIZE - 1 - strlen(ME) - 5 - strlen(to) - 1 - pxlen - 2budget + the(pbuf - buf) + nlen >= maxlenflush‑and‑restart‑after‑prefix exactly as C. The server‑member skip (strchr(name,'.')) precedes theIsInvisibleskip, matching the C order. m_names: theparc>2hunt_server(":%s NAMES %s %s",2,…)forward short‑circuit →MAXPENALTY; the named‑arg branch'ssentthrottle return (sent<2 ? 2 : (sent*MAXCHANNELSPERUSER)/MAXPENALTY); the all‑channels branch's three sections (secret‑on‑user, public viachannel/nextch, remaining users viaclient/next). The deadsent=1/sent=0writes in the third section (never read beforereturn MAXPENALTY) are omitted — observationally identical (local, unread). format!per user request — N/A, documented in‑module. Neither builds an ASCII reply string in Rust; the353member list is assembled intoBUFbyte‑for‑byte (copy_nonoverlapping+@/+/space prefixes), and every wire line goes to the C variadicsendto_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 #pubfrom a member →= #pub :@alice bob353 + 366 (the=public marker,@op prefix); a+ssecret channel's member view → the@ #secret353 marker; the inverse — a non‑member'sNAMES #secretyields only 366 (showusers=0, members hidden); bareNAMES→ all‑visible‑channels + the* * :remaining‑users third section ending366 … *. JOIN‑echo regression covered by the existinggolden_channel(its 353/366 now come from the Rustnames_channelvia the still‑Cm_join). Newgolden_s2s_names(L2‑S2S): theparc>2hunt_serverforwarding short‑circuit (NAMES #chan peer.test→ the peer receives the byte‑identical forwardedNAMESline). All byte‑identical ref‑C == Rust;cargo build0 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), andcheck_stringstay inchannel_link.o.
-
2026‑06‑03 — P5dd (channel.c JOIN cluster —
m_joinhandler + file‑staticcan_join) merged.m_join(channel.c:2395‑2710 — the JOIN command) andcan_join(channel.c:2036‑2119 — the JOIN access‑control predicate) ported toircd-common/src/channel.rs, extending the existingchannel_link.opartial port (‑DPORT_CHANNEL_P5). Eleventh channel.c sub‑phase. Plan:docs/superpowers/plans/2026-06-03-p5dd-channel-m_join.md.can_joinis a private Rustunsafe 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 nocref_can_joinoracle symbol it has no L1 — every return branch is L2‑observed throughm_join's reject numerics.m_joinis a plain#ifndefguard (msgtabJOIN=[m_nop,m_join,m_join,m_nop,m_unreg]→ client (col 1) + server (col 2, theIsServer(cptr)JOIN‑0 head); its decl parse.rs imports fromircd_sys::bindingsresolves to the Rust def oncechannel_link.odrops the C def). RED confirmed via the sole undefinedm_join(can_joinhad 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 both0=!CREATE andCREATE), the variadic senders (sendto_serv_v+sendto_channel_butoneadded to the local extern block;sendto_one/sendto_channel_butserv/sendto_match_servs/sendto_match_servs_v/sendto_flagalready there). New helperis_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_CHANNELoff (no&CLIENTSbranch),USE_IAUTHon (&AUTHin can_join's oper‑channel set),JAPANESEoff (nojp_valid),TOPIC_WHO_TIMEdefined (the topic‑on‑join 333 block ported). jbufvs the sharedbuf— the faithfulness point.m_joinuses two distinct buffers: its function‑static char jbuf[BUFSIZE](the validated, comma‑joined channel‑list accumulator) → a new module‑privatestatic mut JBUF; and the file‑staticbuf[BUFSIZE](the!‑channel‑idsprintf(buf,"!%.*s%s",CHIDLEN,get_chid(),name+2)scratch) → the existing moduleBUF. Both are pure in‑call scratch (never read across calls), so private Rust statics are faithful. Portingm_joinalso resolves the P5cc‑documented residual: the!‑autocreatename=bufaliasing is now wholly within the Rust module'sBUF(no cross‑languagebufclobber).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 onesprintf(buf,"!%.*s%s",…)!‑id builder is a byte build intoBUF(arbitrary client bytes — aStringwould misframe), faithful to C (cf. P5ytopic_nuh).- Faithfulness notes.
IsServer(cptr)head (the only JOIN over a link):parv[1]=="0"→ walksptr->user->channel, PART each (thePartFmt:%s PART %s :%sliteral + the now‑Rustremove_user_from_channel), thensendto_match_servs(NULL,cptr,":%s JOIN 0 :%s",uid,parv[0]); a non‑zero server JOIN is silently ignored. First pass: per‑tokencheck_channelmask/"0"‑literal/clean_channelname/the!‑channel block (!!/!#new‑id build +hash_find_channels/check_chidduplicate rejects; short!chanresolves via find_channel→hash_find_channels with the&&‑short‑circuit preserved)/IsChannelNamegate/jbuf‑overflowbreak. Second pass: parallelkeystrtoken on parv[2];JOIN 0part‑all;get_channel(!CREATE);IsMemberskip;MyConnect && joined>=MAXCHANNELSPERUSER→ 405; the 10‑byte virus‑stringstrncmp→exit_client; the split‑mode create gate → 437;get_channel(CREATE);can_join→replies[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), bothme.serv->sid‑sourced with the@@/@/""flag prefix). Returns 2. - L2 + L2‑S2S are the gates (no L1 —
m_joinonly manipulates membership via the already‑L1'dadd_user_to_channel/remove_user_from_channeland emits wire;can_joinis a file‑static, cf. m_names/m_list/m_topic). Newgolden_channel_join(L2,boot_standalone, 5 scenarios incl. inverses):+kwrong‑key 475 then the keyed JOIN succeeds (the key round‑trip);+i473 then alice's INVITE overrides can_join's invite gate;+l 1full 471; a duplicate JOIN is a silent no‑op (theIsMemberskip — assert no second echo);JOIN 0PARTs every channel then the NAMES inverse (she is gone, the channel destructed). The existinggolden_channel(basic JOIN + JOIN→PART→re‑JOIN round‑trip) now drives the Rust m_join as a regression. Newgolden_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 :000AAAAAAto the peer — thesendto_serv_vSV_UID tail formatting the LOCAL SID + the joiner's UID, fields the client‑facing echo never carries; (b) theIsServer(cptr)head — a peer‑sourced:001BAAAAA JOIN 0relays bob's:bob!~bob@remote.host PART #chanto a local co‑member. All byte‑identical ref‑C == Rust; the 10 channel goldens + 20 s2s goldens + the channel L1 diffs stay green;cargo build0 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, andcheck_stringstay inchannel_link.o.
-
2026‑06‑03 — P5ee (channel.c
m_njoinhandler — server NJOIN burst) merged.m_njoin(channel.c:2724‑2974 — the server‑to‑server channel‑membership burst command, carrying[@@+]uid,…member lists) ported toircd-common/src/channel.rs, extending the existingchannel_link.opartial 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 hitsm_nop). No standalone client path → no L2, and (a socket‑driven wire handler) no extractable L1 data op. A plain#ifndef PORT_CHANNEL_P5guard drops the C def; them_njoindecl parse.rs imports fromircd_sys::bindingsresolves 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 undefinedm_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 withCREATE), the variadic senderssendto_one/sendto_flag/sendto_channel_butserv/sendto_match_servs_v(all already in the block). New helperis_bursting(!(flags & FLAGS_EOB), struct_def.h:222;flagsisc_long). New imports:get_client_name/DELAYCHASETIMELIMIT/FLAGS_EOB/MAXMODEPARAMS/NICKLEN/ServerChannels_SCH_CHAN/ServerChannels_SCH_DEBUG; module constMODEBUFLEN=200.JAPANESEoff → theIsServer(sptr) ||clause in the JOIN‑relay:‑prefix test is dropped (justIsBursting(sptr)). - Shared
modebuf/parabuffile‑statics → module‑privateMODEBUF/PARABUF. These are shared with the still‑Cset_mode, butm_njoinuses them as pure in‑call scratch (zeroed at the first mode via thecnt==0reset, flushed viasendto_channel_butservbefore return — no state crosses calls or is read by another fn), so private Rust statics are faithful (cf. P5aa'sBUF).uidbuf[BUFSIZE]andmbuf[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; theuidbufNJOIN‑relay buffer (the re‑emitted UID member list) and themodebuf/parabufsynthetic‑MODE scratch are assembled byte‑for‑byte (arbitrary client UID/nick bytes — aStringwould misframe non‑ASCII); every wire line goes to a C variadic sender.- Faithfulness notes. The mode‑prefix parse (
@/@@/@@+/@+/+→mbufo/ov/v+chopCHFL bits,nameadvanced past the prefix,targetleft at the prefix for the verbatim uidbuf copy) is branch‑for‑branch. The MODE‑burst accumulator (thecntswitch): case 0 falls through to case 1 (strcat modebuf/parabuf, thembuf[1]double‑name for a+ovuser), case 2 flushes:%s MODE %s +%s%c %s %s(thembuf[0]%cpromotedas c_int),cnt == MAXMODEPARAMSflushes:%s MODE %s +%s %sand resets — modelled asif 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; theu.offset_from(u_base) >= maxlenflush test) mirrored as signedisize. 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%swith the:‑prefix‑unless‑bursting netjoin‑discriminator trick. Already‑on‑channel error class (IsBursting→ SCH_ERROR notice +ERRORline 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 existinggolden_s2s_membership(plain add/remove) doesn't cover — a peer NJOINs carol@+001BBBBBBonto a#chana LOCAL member (alice) is already on → alice observes the relayed:carol… JOIN :#chan+ the synthetic:peer.test MODE #chan +ov carol carolburst (exercising thembuf[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 build0 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, andcheck_stringstay inchannel_link.o.
- Server‑only → L2‑S2S is the only gate. msgtab
-
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 + thechptr->mlistadd/del — ported toircd-common/src/channel.rs, extending the existingchannel_link.opartial port (‑DPORT_CHANNEL_P5). Thirteenth channel.c sub‑phase; the connected component over the file‑staticasterix[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‑
staticin C → the P5u/P5x extern‑prototype switch on each. The still‑Cset_modecallscheck_string(:1295/:1400),make_bei(:1786),add_modeid(:1934),del_modeid(:1937);set_mode+ the still‑Cfree_channel(:2368) callfree_bei. So eachstaticforward decl flips toexternunder#ifdef PORT_CHANNEL_P5and 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_beihad forward decls (channel.c:97/98/112/113);check_stringhad none (defined before its callers) → a newextern char *check_string(char *)added under the guard (cf. P5xadd_user_to_channel). RED confirmed via exactly the five undefined symbols (asterixhas no external C reference → no link error, as predicted). asterixis module‑private (static mut ASTERIX: [c_char;2]). C never referencesasterixoutside the now‑portedcheck_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_beistores the Rust asterix ptr,free_beicompares against it (!= asterix_ptr()⇒ a MyMalloc'd component tofree), and the still‑Cset_mode/free_channelonly ever pass the resultingaListItem(never compare a component to their own asterix). No cross‑language pointer‑identity mismatch. The Casterixdef is#ifndef‑guarded out.- Callees, all already resolved: Rust —
collapse(P1 match_; theadd_modeidMyClient nick/user/host collapse),mycmp(P1;BanExact),make_link/free_link(P2 list),istat(P2;is_bans/is_banmemareu_long→wrapping_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_stringpasses the char sign‑extendedas c_intas C does). New imports:aListItem/collapse/ERR_BANLISTFULL/HOSTLEN/MAXBANLENGTH/MAXBANS/RPL_BANLIST/RPL_EXCEPTLIST/RPL_INVITELIST/RPL_REOPLIST/USERLEN. Inlineban_len/ban_exactmacros +asterix_ptr/strncpyzt_helpers. Theadd_modeid(len > MAXBANLENGTH) || (++cnt >= MAXBANS)short‑circuit modelled aslen > … || { cnt += 1; cnt >= … }(the++cntblock 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→ thecref_archive keeps them local (nocref_add_modeidoracle, cf. P5vmatch_modeid/ P5ddcan_join). The data‑op faithfulness is gated at L2: ref‑Cset_modecalls Cadd_modeid/make_bei; the Rust build's (still‑C)set_modecalls the Rust ones — byte‑identical wire output throughset_modeis the gate. format!per user request — N/A, documented in‑module.make_beicopies arbitrary client‑supplied ban‑mask bytes (nick/user/host, capped NICKLEN/USERLEN/HOSTLEN) viastrncpyzt_byte copies (a UTF‑8Stringwould corrupt non‑ASCII — the standing P5c/P5f rule); every reject reply (ERR_BANLISTFULL,RPL_BANLIST/etc.) goes straight to the C variadicsendto_one; no sub‑longint 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_bei3‑component split + asterix‑fill +add_modeidhead‑prepend), theMODE #chan b/e/Iquery (367/348/346reading the Rust‑builtmlist+368/349/347terminators), 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). Newgolden_s2s_ban(L2‑S2S): a peer server sets:001B MODE #chan +b …→set_modecalls the Rustadd_modeidwithMyClient(cptr)false (the pure non‑MyClient list‑add arm that skips everysendto_one, reachable only over a link), alice'sMODE #chan bquery confirms the ban, then the peer server‑b→del_modeid+free_beiand the re‑query shows it gone. All byte‑identical ref‑C == Rust; fullircd-goldensuite + the channel L1 diff suite (channel_leaf/invite/can_send/membership/modes_diff) green;cargo build --workspace0 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 themodebuf/parabuf/uparabufstatics) +reop_channel/collect_channel_garbage/setup_server_channels/free_channelstay inchannel_link.o.
- All five are file‑
-
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/applierset_mode(:1195), the+beIRlist emittersend_mode_list(:931), and the two server‑burst emitterssend_channel_modes(:1022) /send_channel_members(:1066) ported toircd-common/src/channel.rs, extending the existingchannel_link.opartial port (‑DPORT_CHANNEL_P5). Fourteenth (final large) channel.c sub‑phase — the connected component over the shared file‑static scratch buffersmodebuf/parabuf/uparabuf. Plan:docs/superpowers/plans/2026-06-03-p5gg-channel-mode-core.md.- All five take a plain
#ifndef PORT_CHANNEL_P5guard — no extern‑prototype switch.set_mode/send_mode_listare file‑staticin C, and after this port have no remaining C caller (their only callers —m_modeandsend_channel_modes— port here;m_njoin, the only othermodebuf/parabufuser, is already Rust from P5ee) → they become private Rustunsafe fns (nocref_oracle → L2/L2‑S2S tested).send_channel_modes/send_channel_membersare exported (channel_ext.h) and still called by the Csend_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 fromircd_sys::bindingsresolves 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 Cmodebuf/parabuf/uparabuffile‑statics have zero C users → reuse the existing module‑privateMODEBUF/PARABUF(P5ee in‑call scratch) + newUPARABUF. - 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(theis_rreop/is_fakecounters),m_umode,SLink__bindgen_ty_1(thechopsunion init),ERR_NEEDMOREPARAMS/ERR_RESTRICTED/ERR_UNKNOWNMODE,RPL_CHANNELMODEIS/RPL_UNIQOPIS/the fourRPL_ENDOF{BAN,EXCEPT,INVITE,REOP}LIST. Newc_intconstsMODE_DEL/MODE_KEY/MODE_LIMIT/MODE_EXCEPTION/MODE_INVITE/MODE_REOPLIST/KEYLEN/MODE_WPARAS(the importedMODE_BAN/… are bindgenu32→ cast at use;MODE_UNIQOP/MODE_CHANOP/MODE_VOICE== theCHFL_*locals).USE_SERVICESoff (thecheck_services_butoneblock skipped);V29PlusOnlyoff (theopcnt >= MAXMODEPARAMS+1inner guards compile);JAPANESEoff (nojp_valid). chops/flags[]+ the tri‑statewhatt.set_mode'sstatic Link chops[MAXMODEPARAMS+3](in‑call scratch) → a module‑privatestatic mut CHOPS: [Link; 6](const‑init via theSLink__bindgen_ty_1{cptr: null}union);static int flags[](the simple‑mode bit↔char table) → aconst SIMPLE_FLAGS: &[(c_int, u8)]walked in the exact order.whatt(Cu_int, used as bothMODE_ADD/MODE_DELand the1/-1/0+/- sign tracker across the emission loops) is keptc_int— every value (0x40000000/0x20000000/0/1/-1) compares identically.mode->modeisu_int→ the working copynew_isc_int(matching C'sint new), cast on read/write (all bits < 0x40000, no sign issue). The C switchbreaks (break the switch → continue tocurr++) became a labeled'swblock withbreak 'sw; the'O'→'o'/'v'fall‑through is aproceed‑bool computed in an'ob:labeled block; theopcntreconstruction'sif (tmplen == -1) break;(breaks the for loop) isbreak 'recon, while the per‑itemMODE_KEYskips are a nested'emit_key:block.format!per user request — N/A, documented in‑module. The whole cluster builds protocol‑byte strings into themodebuf/parabuf/uparabufscratch (*mbuf++,strcat, the+lsprintf("%-15d")) byte‑for‑byte as C (arbitrary client nick/ban‑mask bytes — a UTF‑8Stringwould misframe), then hands them to the C variadic senders; there is no owned ASCII reply toformat!.- Faithfulness notes.
set_modetail: the sender cascades = IsServer(sptr) ? serv->sid : sptr->user ? user->uid : sptr->name;sendto_match_servs_v(chptr, cptr, SV_UID, …mbuf, upbuf)(the UID‑form params inupbuf, name‑form inpbuf); the fake‑vs‑real branch (IsServer(cptr) && !IsServer(sptr) && !ischop→sendto_flag(SCH_CHAN, "Fake:…")+ircstp->is_fake++, elsesendto_channel_butserv(…pbuf));return ischop ? count : -count. Thekkey sanitizer (,→., the>0x7f/>0xa0high‑byte mask, KEYLEN truncation) ported on*mut u8exactly.reopscheduling (timeofday + LDELAYCHASETIMELIMIT + myrand()%300for chanop self‑deop / server+R;+rfrom server setschptr->reop) writes channel state (not wire —myrandnon‑determinism is invisible to L2). parseNUH splitsnick!user@hostin place viastrchr/strrchr,make_bei, restores the separators. - Bug caught by the burst S2S test (the inverse paid off).
aListItem.nick/user/hostare*mut c_charpointers, not arrays — the first port tookaddr_of!((*al).nick)(address of the pointer field) and%sprinted the pointer's bytes as garbage (:000A MODE #chan +b ``…). Thegolden_s2s_moderelink‑burst diff caught it immediately; fixed to pass(*al).nickdirectly at all three sites (send_mode_list, thebeIRlist query, the reconstructionnuh). The simpler L2/outbound tests passed without this fix (they never serialize a storedmlistentry) — 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 viasendto_channel_butserv, observed by bob; NAMES shows+bob; the inverse-v bob→ NAMES shows plainbob(change_chan_flagMODE_DEL clears CHFL_VOICE). The 2‑client harness (the per‑host clone limit is 2, the P5cc finding) makes bob both target and observer. Newgolden_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+mthen+l 5, and the peer receives the UID‑sourced:000AAAAAA MODE #chan +m/+l 5(set_mode'ssendto_match_servs_v(SV_UID)tail,upbufform); 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_modes→channel_modes+send_mode_list). Regression:golden_channel_modes/ban/part(L2) +golden_s2s_ban/membership(inbound server MODE through the Rustset_mode) stay byte‑identical ref‑C == Rust; fullircd-golden+ircd-testkit(L1) suites green;cargo build0 warnings; no new canonicalizer masks. Remaining channel.c (C): only the lifecycle group —reop_channel/collect_channel_garbage/setup_server_channels/free_channel/get_channel— stays inchannel_link.o. The channel.c mode + handler clusters are now fully Rust.
- All five take a plain
-
2026‑06‑03 — P5hh (channel.c lifecycle group —
get_channel/free_channel/reop_channel/setup_server_channels/collect_channel_garbage+ thechannelglobal) merged. The channel struct's birth/death cluster ported toircd-common/src/channel.rs. 15th and FINAL channel.c sub‑phase — every symbol channel.c defines now lives in ircd-common, sochannel.ois dropped outright (added toPORTEDinircd-sys/build.rs; the P5t…P5ggchannel_link.osecond‑compile + its-DPORT_CHANNEL_P5link substitution removed entirely; thecreforacle keeps the unguardedchannel.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"(theirextern‑block decls in channel.rs removed; the still‑Rust callersm_part/m_join/m_njoin/remove_user_from_channel/setup_server_channelsresolve to them).reop_channel(:3911, purely file‑static,collect_channel_garbage's only caller) → private Rustunsafe fn(no oracle).setup_server_channels(:812) +collect_channel_garbage(:4026) are channel_ext.h exports called fromircd.c:738(boot) /ircd.c:1188(io_loop timer) →#[no_mangle]. Thechannelglobal list head (:52) →#[no_mangle] pub static mut channel(replaces thechannel as channel_listbindgen import; the two read sites in m_list/the GC updated; the still‑Cs_debugSTATS resolves itsexterndecl to it). - Config findings (locked config). USE_IAUTH on →
setup_server_channelscreates the 14th channel&AUTH; CLIENTS_CHANNEL undef → no&CLIENTS; JAPANESE off →get_channel'sjp_chname/FLAGS_JPblock dead; DEBUGMODE off →collect'sdel/SCH_NOTICE +reop's SCH_NOTICE blocks dead; LOG_SERVER_CHANNELS off → the still‑Csetup_svchans(send.c) is just thefind_channelrelink loop.MyFree(chptr)→ plainfree(the macro's NULL‑out is dead on a by‑value param). The function‑local staticsmax_nb/splitincollect_channel_garbage→ module‑privatestatic mut MAX_NB/SPLIT(persist across calls, faithful).reop_channel's!CHFL_REOPLISTarg = the C logical‑not of a nonzero value =0(ported literally). istat decrements usewrapping_sub(cf. P5u/P5ff). - Classification: utility/callee TU (none of the six is a
msgtabhandler).get_channel/free_channel/reop_channelare file‑static → the--prefix-symbols=cref_rename keeps them local (no oracle);setup_server_channels/collect_channel_garbage/channel/add_to_channel_hash_tableare exported →cref_*oracles exist. - L1 (
ircd-testkit/tests/channel_lifecycle_diff.rs, the two exported entries vscref_; the three statics transitively covered). Both sides drive separate process globals (channelvscref_channel, separate channel hash tables init'd per‑test viainithashtables/cref_inithashtables— NULL until boot otherwise, the L1 segfault finding — separateistat). (1)setup_server_channelscreate‑cluster: 14 server channels, identical chname/topic/mode.mode/users==1‑lone‑chanop walk (get_channelCREATE + global‑list prepend + hash insert +add_user_to_channel+ topicstrcpy+ mode masks +setup_svchansrelink). (2) Inverse: the oracle'dremove_user_from_channel(mp, &ERRORS)→users→0, history==0 →free_channelunlinks&ERRORSfrom 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 returnnow+CHECKFREQ,&deadfreed (gone from list+hash),&livekept. Finding:free_channelre‑checks the globaltimeofday >= chptr->history(not collect'snowparam) → the test pinstimeofday/cref_timeofdaytonowso the expired‑free path fires; themyrandreop arm needs a live opless+rchannel (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 isMyClient(cptr)chname truncation).get_channel/free_channelare already L2/S2S‑covered transitively by every JOIN/PART golden (golden_channel*,golden_s2s_membership— remote NJOIN/PART driveget_channel(CREATE)/free_channel);collect_channel_garbage/reop_channelare timer‑driven (300 s, unreachable in a short golden). Regression: fullircd-golden(83 tests) +ircd-testkitL1 suites byte‑identical ref‑C == Rust;cargo build0 warnings; no new canonicalizer masks. channel.c is now FULLY ported to Rust — only the channel lifecycle remained after P5gg, and this closes it.
- Symbols.
-
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 newircd-common/src/s_serv.rs. Firsts_serv.csub‑phase —s_serv.cis the last (and largest) P5 TU (~3884 lines, ~14 file‑statics, ~40 handlers), ported in clusters; this one establishes thes_serv_link.opartial‑port mechanism. Plan:docs/superpowers/plans/2026-06-03-p5ii-s_serv-informational.md.- Mechanism — establish
s_serv_link.o(mirrorss_user_link.o). A second compile ofs_serv.cwith 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 inbuild.rslink_objs(); the cref oracle keeps the unguardeds_serv.oinCREF_OBJS. The recipe carries the per‑object define-DIRCDMOTD_PATH="\"$(IRCDMOTD_PATH)\""(Makefiles_serv.orecipe line 346 — needed by the still‑Cm_motd), driven throughmake --evalso the recursive path var expands identically. All three handlers take a plain#ifndef PORT_SERV_P5guard — no extern‑prototype switch: none is a file‑staticand none is anyone'sstaticcallee. RED→GREEN confirmed: binary links clean (no undefined, no multiple‑definition) andnm 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; localextern "C"block, fmt*mut c_char). Rust —replies[](P3, viareply(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_charresolves to the real 12‑byte Rust static at link). Globals/consts —me(me.nameself‑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 ahunt_serverforwarding branch (server‑reachable, exactly like P5zm_list) → L2‑S2S.m_adminadditionally has theIsRegistered(cptr)head‑gate (unregistered clients skip the forward — a local branch, L2‑covered). No L1 — likem_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 variadicsendto_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 drivesVERSION→351 RPL_VERSION,TIME→391 RPL_TIME (trailing localtime wall‑clock volatile → masked by the existingcanonicalize()391 rule),ADMIN→256‑259 RPL_ADMINME…RPL_ADMINEMAIL via minimal.conf'sAline (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 targetsVERSION/TIME/ADMIN peer.test→hunt_servermatches 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 build0 warnings; fullircd-golden+ircd-testkitsuites green; no new canonicalizer masks.
- Mechanism — establish
-
2026‑06‑03 — P5jj (s_serv.c network‑info query cluster —
m_info/m_links+ the file‑staticcheck_link) merged. The two remaining leaf network‑info query handlers (s_serv.c:1400/1438) + their shared per‑link load‑health gatecheck_link(s_serv.c:3419) ported intoircd-common/src/s_serv.rs, extending the existings_serv_link.opartial port (‑DPORT_SERV_P5; three new guard regions). Seconds_serv.csub‑phase. Plan:docs/superpowers/plans/2026-06-03-p5jj-s_serv-info-links.md.- First
s_serv.cextern‑prototype switch.check_linkis file‑staticin C and still called by the C remnantm_stats(×2) /m_motd→ the P5u/P5x mechanism: its forward decl at s_serv.c:36 flipsstatic→externunder#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_linkstake plain#ifndefguards (neither is a static / anyone's static callee — their decls parse.rs imports fromircd_sys::bindingsresolve to Rust). RED→GREEN: binary links clean andnm target/debug/ircd | grep -E ' T (m_info|m_links|check_link)$'shows all three defined from Rust (check_linkis nowT, was a localt). - 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[](P3reply()),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 aconst 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->nameis(*(*(*acptr).serv).up).name(deref the*mut aClient, never.read()anaClientby 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 ahunt_serverforward (INFO always; LINKS whenparc>2) → L2‑S2S. No L1 — like P5ii/m_list/m_names, pure wire handlers with no socket‑free data op.check_linkdoes mutateircstpcounters, but it is file‑static→ the‑‑prefix‑symbols=cref_rename keeps it local (nocref_check_linkoracle), 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, two371 RPL_INFOlines 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 tocanonicalize()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‑server364 RPL_LINKS, the TRYAGAIN/ENDOF replies) goes straight to the C variadicsendto_one;myctime()returns its own static buffer.m_links's only integer arg isacptr->hopcount(%d, alreadyc_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 drivesINFO→a run of371(masked birth/online) ending in374 RPL_ENDOFINFO, then bareLINKS→364/365 RPL_ENDOFLINKS— ref‑C == Rust byte‑identical;check_linkreturns 0 (localMyConnect) so no263 RPL_TRYAGAIN.golden_s2s_s_serv_info_links(L2‑S2S): thehunt_serverforwards (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 exercisem_links's remote‑serverup->name/sid/infofield formatting.cargo build0 warnings; fullircd-golden+ircd-testkitsuites green.
- First
-
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 helpersend_users(s_serv.c:2202) ported intoircd-common/src/s_serv.rs, extending the existings_serv_link.opartial port (‑DPORT_SERV_P5). Thirds_serv.csub‑phase. Plan:docs/superpowers/plans/2026-06-04-p5kk-s_serv-users-summon-help.md.- Config‑resolved bodies.
USERS_RFC1459off (config.h:85 #undef) →m_userstakes thesend_usersarm: forward viahunt_server(":%s USERS :%s", 1)only whenparc>1, thensend_usersemits265 RPL_LOCALUSERS(is_myclnt/is_m_myclnt×2) +266 RPL_GLOBALUSERS(is_user[0]+is_user[1]/is_m_users×2).ENABLE_SUMMONoff (config.h:77 #undef) →m_summon, after theparc<2/empty411 ERR_NORECIPIENTguard, rewritesparv[1..=4](user/host=ME‑or‑parv[2]/chname=*/NULL) and forwards viahunt_server(":%s SUMMON %s %s %s", 2); the ISME path emits445 ERR_SUMMONDISABLED.m_helpwalksmsgtab[](the incomplete‑array extern,addr_of!+cmd.is_null()terminator) emitting:%s NOTICE %s :%sper command. send_usersextern‑prototype switch (P5u/P5x).send_usersisstaticin C (s_serv.c:52 forward decl) and was GCC‑inlined into both callers as.isra.0(no global symbol in the oracles_serv.o→ nocref_send_users). But it has a remaining C caller — the still‑Cm_lusers(s_serv.c:2386,if (all) send_users(...)) — so guarding out its definition lefts_serv_link.owith an undefined localsend_users(confirmed at RED: undefinedm_users/m_summon/m_help/send_users). Fix: the forward decl flipsstatic→externunder#ifdef PORT_SERV_P5and the Rust def is#[no_mangle] pub extern "C", som_lusersresolves to the Rustsend_usersat 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[](P3reply()),msgtab[](parse.rs,static mutresolves at link). Globals —istat(istat_t:is_myclnt/is_m_myclnt/is_user[2]/is_m_users,u_longcounters passed verbatim to the variadic sender),me. Inlined macros —ME/BadTo/BadPtr. Theparvrewrite inm_summonis 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_summoneach have ahunt_serverforward → L2‑S2S;m_helphas no forward/IsServerpath → no S2S. No L1 — like P5ii/P5jj/m_list, pure wire handlers with no socket‑free data op;send_usershas nocref_oracle (inlined) so it too is L2‑covered, not L1. format!per user request — N/A. Every wire line (the265/266counts,411/445, the HELP NOTICEs) goes straight to the C variadicsendto_one; nothing is assembled into a Rust string.- L2 + L2‑S2S are the gates.
golden_s_serv_stubs(L2): a lone registered client drivesUSERS→265+266,SUMMON nobody→445,HELP→the NOTICE list anchored on the table's final:ETRACEentry — ref‑C == Rust byte‑identical.golden_s2s_s_serv_stubs(L2‑S2S):USERS peer.test/SUMMON nobody peer.testforward viahunt_serverand the linked peer receives byte‑identical forwarded commands (m_help, having no forward, is excluded with a note).cargo build0 warnings; both touched golden binaries green.
- Config‑resolved bodies.
-
2026‑06‑04 — P5ll (s_serv.c LUSERS handler —
m_lusers) merged. The LUSERS network‑stats handler (s_serv.c:2272) ported intoircd-common/src/s_serv.rs, extending the existings_serv_link.opartial port (‑DPORT_SERV_P5). Fourths_serv.csub‑phase. This closes the P5kksend_usersloop:m_lusers'sall‑path tailif (all) send_users(...)already resolves to the Rustsend_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_P5guard around the C def (s_serv.c:2272‑2392);m_lusersis not a file‑static and not anyone's static callee. RED:cargo build -p ircd-rs→ exactly one undefined symbolm_lusers. GREEN:nm target/debug/ircd | grep ' T m_lusers$'→ defined from Rust;cargo build0 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
LUSERSorLUSERS *→ theallpath readsistat(is_serv/is_user[0..1]/is_unknown/is_oper/is_service/is_myclnt/is_myserv/is_myservice) then tails intosend_users(265/266); (2)LUSERS <server>→ a remotefind_client→IsServer→serv->usercnt[0..2]; (3)LUSERS <mask>→ walkssvrtopsumming eachasptr->usercnt[](theIsMearm grabsis_myclnt/…/is_unknown), then walkssvctopcounting matching services.parc>2forwards viahunt_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(theaServer.usercnt/nexts/bcptr+aService.servp/nextschains, raw‑pointer walked, never anaServer/aServiceread by value),replies(P3reply()),me. New inline helpersis_server=status==STAT_SERVER,is_me=status==STAT_ME(struct_def.h:136/138). - Faithfulness notes. The
intlocals (s_count/c_count/i_count/u_count/o_count/v_count/m_clients/m_servers/m_services) stayc_int, assigned fromu_longistat fields withas c_int(C's implicitu_long→inttruncation).istat.is_chanis passed straight to the%dRPL_LUSERCHANNELS variadic asu_long— byte‑identical on x86‑64 SysV (the low 32 bits of the arg register match a small positiveint), exactly as the already‑Rustsend_usersdoes. Quirk ported literally: RPL_LUSERUNKNOWN (253) is emitted withparv0, notBadTo(parv0)(s_serv.c:2380) — every other LUSERS reply usesBadTo. - 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); thehunt_serverforward (parc>2) and the remote‑server field formatting (svrtop/find_clientusercnt[]+ server names) → L2‑S2S. No L1 — pure wire handler with no socket‑free data op (the lookups are read‑only counting terminating insendto_one), cf. P5ii/P5jj/P5kk/m_list. format!per user request — N/A. Every wire line (the five LUSER numerics + thesend_users265/266) goes straight to the C variadicsendto_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 bareLUSERS→ 251/254/255 then thesend_users265/266 (theallpath), read through the always‑last266anchor — ref‑C == Rust byte‑identical.golden_s2s_s_serv_lusers(L2‑S2S): two server‑reachable facets after a peer links — (1)LUSERS *.testexercises thesvrtopmask walk (the*.testmatches bothirc.test+peer.test, so the 251 server count reflects the link and theIsMe253 arm fires), diffed from the client side through the 255 tail; (2)LUSERS * peer.test(parc>2) forwards viahunt_serverand the peer receives the byte‑identical forwarded command.cargo build0 warnings; both touched golden binaries green.
- Guard + RED→GREEN. A single plain
-
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 renderersdump_map(plain names, s_serv.c:3666) anddump_map_sid(names + usercount + SID + version, s_serv.c:3599) ported intoircd-common/src/s_serv.rs, extending the existings_serv_link.opartial port (‑DPORT_SERV_P5). Fifths_serv.csub‑phase.- Guard + RED→GREEN. A single plain
#ifndef PORT_SERV_P5guard around the contiguous C region (s_serv.c:3599‑3781, all three fns):dump_map/dump_map_sidare file‑staticwith no remaining C caller (onlym_mapcalls them, and it ports here too) → private Rustunsafe fns (nocref_oracle, likem_list/can_join);m_map's decl resolves via the parse.rsmsgtabimports.nm target/debug/ircd | grep ' T m_map$'→ defined from Rust;cargo build0 warnings. - Module‑private
BUF(the sharedbuf[BUFSIZE]).dump_map/dump_map_sidaccumulate the output line in the s_serv.c TU‑widestatic char buf[BUFSIZE]via raw pointer arithmetic —pbufindexes the current nesting offset (pbuf + 4per level), each leaf flushes the wholebufwith015 RPL_MAP, and the tree‑char cleanup walks backwards (pbuf[-2]/pbuf[-3]).m_mapfills+flushesbufentirely in‑call (no cross‑call aliasing with the still‑C users ofbuf— m_stats/report_*/send_server_burst), so a module‑privatestatic mut BUF: [c_char; BUFSIZE]+ abuf_base()helper is faithful (cf. P5aa/P5cc). Pointer ops use.add()/.sub()/.offset_from(); the snprintfsizearg keeps C'sint→size_tconversion viasize as usize(negative→huge, faithful UB). - Callees, all already resolved. C externs —
sendto_one(variadic P8 trampoline, local block fmt*mut c_char), plussnprintf/strcat/strlen(libc, added to the local extern block, faithful to the C). Rust —match_(P1),replies[](P3reply()). Globals —me(addr_of_mut!(me)for&me, the tree root),timeofday. New inline helpersis_bursting=!(flags & FLAGS_EOB)(struct_def.h:222) andmap_name=IsMasked(root) ? maskedby->name : root->name; reusedis_masked/my_connect/bad_to/me_name(P5ii/P5jj). - Faithfulness notes.
meisSetEOB'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'sprevservercarries the last unmasked server across masked‑subtree boundaries via a*mut *mut aClientout‑param + a recursion‑localprev(addr_of_mut!(prev)), byte‑for‑byte the CaClient **. The%dsliteral (snprintf) is%dfollowed by a literals. - 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_maphas nohunt_serverforward (it always dumps from&me), butdump_map/dump_map_sidformat 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 insendto_one(cf. P5ii/P5jj/m_list/m_names). format!per user request — N/A. Every wire line goes straight to the C variadicsendto_one;dump_map_sidbuilds its per‑server token intobufvia libcsnprintf(faithful, not a Rust string),dump_mapvia libcstrcat. Nothing assembled withformat!.- L2 + L2‑S2S are the gates.
golden_s_serv_map(L2): a lone registered client drivesMAP→018 RPL_MAPSTART+ a single015(irc.test) +017 RPL_MAPEND, thenMAP s→018+ a single015with 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 — bareMAPbefore 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), thenMAP safter the link adds: `- peer.test 0 001B 0211030000(the only way to exercisedump_map_sid's remoteserv->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.
- Guard + RED→GREEN. A single plain
-
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 handlerm_etrace(s_serv.c:2862) + the per‑client renderertrace_one(s_serv.c:2656) + the count helpercount_servers_users(s_serv.c:1653) ported intoircd-common/src/s_serv.rs, extending the existings_serv_link.opartial port (‑DPORT_SERV_P5). Sixths_serv.csub‑phase. Plan:docs/superpowers/plans/2026-06-04-p5nn-s_serv-trace.md.- Guard + RED→GREEN. One contiguous
#ifndef PORT_SERV_P5guard aroundtrace_one/m_trace/m_etrace(s_serv.c:2664‑2923, comment blocks included);m_sidtrace(the next fn,#ifdef ENABLE_SIDTRACE) left untouched.trace_oneis file‑staticwith no remaining C caller (onlym_trace×2 call it, both port here) → a private Rustunsafe fn(nocref_oracle, cf.m_list/can_join/dump_map).m_trace/m_etrace's decls resolve via the parse.rsmsgtabimports. 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 build0 warnings. count_servers_usersextern‑prototype switch. Thestatic void count_servers_users(...)forward decl (s_serv.c:47) gives the function internal linkage despite its non‑staticdefinition, so GCC inlined it into both call sites → no emitted symbol (absent fromnm). It is still called by the Creport_myservers(s_serv.c:1632, stays C) and by the now‑Rusttrace_one, so it gets the P5u/P5x extern‑prototype switch: the forward decl flipsstatic→externunder the guard, its body (1653‑1681) is guarded out, and the Rust#[no_mangle] pub extern "C" fn count_servers_usersresolves the C remnant's call at link. HUB off → only the#elseistat branch is ported:*servers = is_serv‑1,*users = is_user[0]+is_user[1]‑is_myclnt(thecptrarg unused); theu_longfields arewrapping_*‑combined thenas c_int(faithful to C'su_long→int, 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 variadicsendto_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 thelocal_base()send.rs helper),me,timeofday,istat,replies(P3reply()),IRC_VERSION. New inline macro helpersis_client/is_person/is_unknown/is_an_oper/send_wallops/my_client; reusedis_server/is_me/my_connect/bad_to/me_name/dbuf_length. - Faithfulness notes.
trace_one's statusswitchported as amatch (*acptr).status as c_int(status isc_short) over the nineSTAT_*arms incl. the STAT_ME no‑op and thedefaultRPL_TRACENEWTYPE; the STAT_SERVER case keeps bothserv->user/!serv->userbranches verbatim (the*serv->by ? by : "*"first‑char test, the masked‑vs‑real server fields).m_trace's passthru emits the volatile200 RPL_TRACELINK(link‑uptimetimeofday‑from->firsttime, two sendQDBufLengths) ported faithfully but only diffed peer‑side in S2S (the deterministic signal is the forwarded:%s TRACE :%s).m_etraceis gated to my opers with ACL_TRACE; XLINE off → the user2/user3 columns are the"-","-"literals; theserv->user->{username,host}/acptr->user->{username,host}are[c_char;N]arrays passed via.as_ptr(). Never reads anaClient/aServerby value (raw‑pointer invariant). ENABLE_SIDTRACE off →m_sidtracenot 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, reachesm_etrace; a plain client hitsm_nopriv/481).m_trace's passthru +trace_one's STAT_SERVER case format remote‑server fields → L2‑S2S;m_etracewalks only local persons (no S2S path of its own). No L1 — pure wire handlers terminating insendto_one;count_servers_usersis a data op but has nocref_oracle (internal linkage in the oracle archive too) → L2‑S2S‑covered via the206servers/users it feeds (cf. P5jjcheck_link). format!per user request — N/A. Every wire line goes straight to the C variadicsendto_one(replies[…], …); no ASCII/numeric reply string is assembled in Rust.get_client_name/get_client_ipreturn their own C buffers.- L2 + L2‑S2S are the gates.
golden_s_serv_trace(L2): bareTRACEfrom a lone plain client →205 RPL_TRACEUSER(self) +262 RPL_TRACEEND;OPER alice secret+ETRACEvia the newtrace_oper.conf(O‑linet/ACL_TRACE flag) →381+709 RPL_ETRACEFULL(self) +759 RPL_ETRACEEND— byte‑identical ref‑C == Rust. (SymbolicRPL_ETRACEFULL=708 but the dumpedreplies[708]carries the wire numeric 709; both builds share the P3 table → bytes match.)golden_s2s_s_serv_trace(L2‑S2S): bareTRACEafterPeer::link()→206 RPL_TRACESERVERfor the peer (thetrace_oneSTAT_SERVER +count_servers_userspath with the remoteserv->version/by/userfields), diffed client‑side;TRACE peer.test→ theIsServerpassthru forwards the command, diffed peer‑side.cargo build0 warnings; the two touched golden binaries green; no new canonicalizer masks (the 206/205/262/709/759 numerics carry no volatile token).
- Guard + RED→GREEN. One contiguous
-
2026‑06‑04 — P5oo (s_serv.c STATS / reporting cluster —
m_stats+ the fivereport_*reporters +report_array) merged. The STATS handler (s_serv.c:1882) + its file‑static reportersreport_myservers(1619),report_configured_links(1756),report_ping(1833),report_fd(1854),report_listeners(3794) + thereport_array[18][3]table (1713) ported intoircd-common/src/s_serv.rs, extending the existings_serv_link.opartial port (‑DPORT_SERV_P5). Sevenths_serv.csub‑phase — completes the cluster‑map "Stats / reporting" component (its siblingstrace_one/check_link/count_servers_usersalready ported P5jj/P5nn). Plan:docs/superpowers/plans/2026-06-04-p5oo-s_serv-m_stats.md.- Guard + RED→GREEN. Three
#ifndef PORT_SERV_P5guards: thereport_listenersforward decl (s_serv.c:48); the contiguousreport_array→report_configured_links→report_ping→report_fd→m_statsspan (withreport_myserversguarded separately, since the already‑guardedcount_servers_userssits between it andreport_array); and thereport_listenersbody (3794). RED left exactlym_statsundefined ins_serv_link.o(the five reporters arestatic→ no external reference;report_arrayis data) →nm target/debug/ircd | grep ' T m_stats$'defined from Rust after GREEN;cargo build0 warnings. - All five reporters are private Rust
unsafe fns. Each is file‑staticin C withm_statsas its only caller (verified per‑symbol withgrep) → sincem_statsports here too, no C caller remains → no extern‑prototype switch, nocref_oracle (cf. P5mmdump_map, P5ddcan_join).report_listenersadditionally had astaticforward decl → both decl + body guarded out. report_arrayreproduced resolved to TKLINE‑on. A module‑privatestatic 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 Cfor (p = &report_array[0][0]; *p; p += 3)walk is ported as "iterate rows untilrow[0]==0"; theif (!*p) continueno‑match path preserved. XLINE off →report_x_lines(#ifdef XLINE) not compiled, not ported (STATS Xis 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 variadicsendto_one(local block, fmt*mut c_char) + libcsprintf/strcpy/strchr/memcpy/isupper(new local extern decls;sprintffmt*const c_charto avoid theclashing_extern_declarationswith 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[](vialocal_base()). New inline helpersis_illegal/is_listener_inactive/is_conf_delayed/is_split; reusedis_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
switchported as amatch stat as u8over the locked config (DISABLE_STATSTKLINE/TXT_NOSTATSK off → no oper gate onSTATS k/K; DEBUGMODE off →STATS xno‑op andSTATS Z/zboth →count_memory(...,0); theZ→zfallthrough collapsed into one arm). TheIsServer(cptr)rate‑limit head (i/I/c/C/k/K/m/M/t/T/z/Z→check_link→RPL_TRYAGAIN) + bothhunt_serverparc==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; TKLINEhold‑timeofday; CLIENTiline_flags; OPERATORoline_flags; else) ported with the exact C arg types (charc→c_int,port→c_int,tmp->hold‑timeofday→c_long,iline/oline_flags_to_string((*tmp).flags: c_long)). TheSTATS mloop walksmsgtabby raw pointer untilcmdis null, summing the fivehandlers[n].countand emitting212 RPL_STATSCOMMANDS(counters byte‑identical — the P4 dispatch invariant).report_ping/report_fduse the moduleBUF/ipv6stringas in‑call scratch (faithful, cf. dump_map P5mm);report_myservers's%X serv->versionisc_int,report_fd's idle ternary isc_long(the Ctime_t/-1common type). Never reads anaClient/aConfItemby 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). TheIsServer(cptr)head +hunt_serverforward +report_myservers's remote‑server fields are server‑reachable → L2‑S2S. No L1 — every symbol is a pure wire reporter terminating insendto_one(no socket‑free data op), andreport_arrayis config‑gated data with nocref_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 variadicsendto_one(replies[…], …)(or the inline":%s …"literals for report_myservers/report_fd/report_listeners);report_pingbuilds itsname[host]token intoBUFvia libcsprintf/strcpy. Nothing assembled withformat!.- L2 + L2‑S2S are the gates.
golden_s_serv_stats(L2): a lone registered client underoper.confdrivesSTATS i→215 RPL_STATSILINEandSTATS o→243 RPL_STATSOLINE(thereport_configured_linkswalk over the I/O lines +iline/oline_flags),STATS y→218 RPL_STATSYLINE(report_classes),STATS m→212 RPL_STATSCOMMANDS(the msgtab loop) — each closing219 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‑firsttimetokens (or are canonicalizer‑masked) → review/mask‑covered, not asserted here.golden_s2s_s_serv_stats(L2‑S2S): two server‑reachable facets afterPeer::link()— (1)report_myserversviaSTATS ?→249 RPL_STATSDEBUGwith the peer name /1S 0C(count_servers_users) /0kB sent 0kB recv 0kB sq/BURST/%Xversion, diffed in full bar the masked(timeconnected)real‑clock token; (2) thehunt_serverforwardSTATS m peer.test(parc==3) → the peer receives the byte‑identical forwarded command.cargo build0 warnings; both touched golden binaries + the s_serv golden regression suite green.
- Guard + RED→GREEN. Three
-
2026‑06‑04 — P5pp (s_serv.c
m_motdhandler) merged. The MOTD command handler (s_serv.c:2975) ported intoircd-common/src/s_serv.rs, extending the existings_serv_link.opartial port (‑DPORT_SERV_P5). Eighths_serv.csub‑phase. Plan:docs/superpowers/plans/2026-06-04-p5pp-s_serv-m_motd.md.- Guard + RED→GREEN. One
#ifndef PORT_SERV_P5guard around them_motdbody (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.rsmsgtabimports. RED left exactlym_motdundefined (rust-lld: error: undefined symbol: m_motd); after GREENnm target/debug/ircd | grep ' T m_motd$'→ defined from Rust;cargo build0 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 variadicsendto_one(local block, fmt*mut c_char). Globals —motd(*mut aMotd)/motd_mtime(time_t) bindgen statics;replies(P3reply()). libc —localtime/tmfor the date line. - Faithfulness notes. Exact control flow: the
!IsUnknown(sptr)head guards both thecheck_link→RPL_TRYAGAIN(263) rate‑limit return (5) and thehunt_server(":%s MOTD :%s", 1, …)forward return (5); thenlocaltime(&motd_mtime)(always called, before the null check, as in C), themotd==NULL→ERR_NOMOTD(422) early return (1), theRPL_MOTDSTART(375) + the inline:%s %d %s :- %d/%d/%d %d:%02ddate line (RPL_MOTD=372 passed as the%darg, not areplies[]index — verbatim s_serv.c:2997) + thefor (temp=motd; …; temp=temp->next)listing loop emittingRPL_MOTD(372) pertemp->line+RPL_ENDOFMOTD(376), and theIsUnknown(sptr) ? 5 : 2tail. Never reads anaClient/aMotdby 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 insendto_one(no socket‑free data op, nocref_oracle — cf. m_version/m_time/m_admin P5ii, m_list, m_names) → no L1. Thehunt_serverforward is server‑reachable → L2‑S2S required. format!per user request — N/A. Every wire line goes straight to the C variadicsendto_one(the numeric replies viareply(), the date line via the inline:%s …literal). Nothing assembled withformat!.- L2 + L2‑S2S are the gates.
golden_s_serv_motd(L2): a lone registered client drivesMOTD→422 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==NULLflow), ref‑C == Rust byte‑identical. The full‑listing path (375/372 + thelocaltimedate 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.testafterPeer::link()→ the peer receives the byte‑identical forwarded… MOTD …command (thehunt_servershort‑circuit, the only server‑reachable path inm_motd), diffed peer‑side.cargo build0 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).
- Guard + RED→GREEN. One
-
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. Theserver_name[]string‑interning pair (s_serv.c:3411/3428), theaServer‑tree pointer rewiring (s_serv.c:3574/3592), and the server‑name validatorcheck_servername(s_serv.c:3519) ported intoircd-common/src/s_serv.rs, extending the existings_serv_link.opartial port (‑DPORT_SERV_P5). Ninths_serv.csub‑phase — the first s_serv.c utility/callee cluster (pure data ops, nom_*/msgtabrow). Plan:docs/superpowers/plans/2026-06-04-p5qq-s_serv-server-tree.md.- Guard + RED→GREEN. Three
#ifndef PORT_SERV_P5guards: theserver_name/server_max/server_numstatics +find_server_string+find_server_num(s_serv.c:3402‑3451); and the contiguouscheck_servername→add_server_to_tree→remove_server_from_treespan (s_serv.c:3509‑3619). RED left exactlyfind_server_string/find_server_num/check_servername/add_server_to_tree/remove_server_from_treeundefined (rust-lld: undefined reference to …), matching thenmchecklist; after GREENnm target/debug/ircd | grep ' T (…)$'→ all five defined from Rust;cargo build0 warnings. check_servername_errors[3][2]stays C (faithfulness gotcha). The exported error‑message table is declaredEXTERN const char *check_servername_errors[3][2];ins_serv_ext.h, and withEXTERNexpanding empty insides_serv.cthat decl is a C tentative definition → guarding only the initialized def (s_serv.c:56) leaves a zeroed BSS symbol (nm s_serv_link.o→B check_servername_errors) that would collide with a Rust#[no_mangle]strong def (‑fno‑common, two strong defs = multiple‑definition link error). Since the portedcheck_servernamenever reads the table (it only returns the 1‑based rc index; the callersm_server/ircd.c boot index it), the table is left in C with a comment, to be ported whens_serv.ois dropped outright. The L1 still assertsc_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‑
staticserver_name[]).SERVER_NAME: *mut *mut c_char/SERVER_MAX/SERVER_NUM: c_int— not ABI (cf.BUF/ASTERIX/REPORT_ARRAY).find_server_numgrows the array by 50 (SERVER_MAX += 50thenMyRealloc(…, sizeof(*mut)*SERVER_MAX), or the first‑timeMyMalloc(…SERVER_MAX=50)) and interns viamystrdup, byte‑for‑byte the C;find_server_string's bogus‑index path emits thesendto_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(theremove_server_from_treenon‑leafserv->down != NULLguard, ported verbatim). C variadic —sendto_flag(bindgen decl, error path only).ServerChannels_SCH_ERROR(=0)/HOSTLEN(=63) bindgen consts. - Faithfulness notes.
check_servernamemirrors C's signed‑charloop exactly: the char is read asc_char(signed i8 on x86‑64; unsigned u8 on this aarch64 dev box —c_char = u8), the a‑z/A‑Z/-/*range tests +isdigit(cc as c_int)preserve C's sign‑extension, thedots==0→rc 3 /chars==0→rc 2 post‑checks keep their order.add_server_to_treecachesup = serv->upand head‑inserts (down→new, new.right=olddown, olddown.left=new,servers++);remove_server_from_treedecrementsservers,abort()s on a non‑leaf, then relinksright.left/left.right/up.down. Never reads anaClient/aServerby value (raw‑pointer invariant —(*(*acptr).serv).upchains). - Classification: utility/callee TU, L1 is the gate. No
msgtabrow for any of the five — they are unconditional callees of the still‑C server‑link/registration machinery (m_server/m_server_estabs_serv.c:568/656/804/821/1227/1235,exit_servers_misc.c:755, boot ircd.c:728/1053,register_users_user.c:1412/2452). So L1 differential is the primary gate. No new L2/L2‑S2S — none has anIsServer(cptr)dispatch branch of its own (step‑7 "skip + note regression"): every symbol already runs on the existing 13golden_s2s_*scenarios — peer link (find_server_num+add_server_to_tree+check_servernamevalidating the incomingSERVERname), remote users (find_server_stringforuser->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/mystrdupcopies the name;find_server_string's only output is the bogus‑indexsendto_flagnotice, a C trampoline).- L1 + S2S‑regression are the gates.
s_serv_servtree_diff(L1, zero‑diff vscref_): (1)server_name[]interning round‑trip — repeat returns the same index (a,b,a,c→0,1,0,2),find_server_string(i)round‑trips each, growth pastserver_max=50(intern 60 names → sequential0..59,MyReallocfires), 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 vscref_and against the analytic structure (so a faithful insert + botched removal is caught); (3)check_servernamerc corpus (ok/too‑long/invalid‑char/no‑dot/dots‑only/digits/-/*/empty) + the error‑table bytes.cargo build0 warnings; the fullircd-goldengolden_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) hardcodei8forc_charand fail to compile on this aarch64 box (wherec_char = u8) — confirmed broken on cleanHEAD, unrelated to this port; the news_serv_servtree_diff.rsusesstd::os::raw::c_charand is portable.
- Guard + RED→GREEN. Three
-
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 intoircd-common/src/s_serv.rs, extending the existings_serv_link.opartial port (‑DPORT_SERV_P5). Tenths_serv.csub‑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_rehashare 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_P5guards — aroundm_rehash(s_serv.c:2619‑2631) andm_close(s_serv.c:3019‑3049). Neither is a file‑staticor anyone's static callee → no extern‑prototype switch; theirmsgtabdecls (parse.rs imports) resolve to the Rust defs at link. RED left exactlym_close/m_rehashundefined (undefined reference to 'm_close'/'m_rehash'from.data.msgtab), matching thenmchecklist; after GREENnm target/debug/ircd | grep ' T (m_close|m_rehash)$'→ both defined from Rust;cargo build0 warnings. - Config‑resolved bodies. DELAY_CLOSE on (config.h:440=15) →
m_close's#ifdef DELAY_CLOSEtail (closed = istat.is_delayclosewait; delay_close(-2); sendto_flag(SCH_NOTICE,…)) is compiled and ported. USE_SYSLOG off →m_rehash's#ifdef USE_SYSLOGsyslog(LOG_INFO,…)line is not compiled/ported. OPER_REHASH defined (config.h:130) →ACL_REHASHis grantable (not stripped at s_conf.c:344); oper flag lettersl→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 helpersreply/me_name/bad_to/is_unknown(+ newis_connecting/is_handshakeinline helpers). C externs (bindgen) —rehash(s_conf.c),get_client_name,delay_close(s_bsd.c); the variadicsendto_one/sendto_flag(the*mut c_charfmt /u_intchannel decls). Globals —configfile(*mut c_charstatic, read via*addr_of!),me(addr_of_mut!(me)for theexit_clientfromarg),highest_fd/local[](via the existinglocal_base()helper),istat,replies(P3reply()). Consts —ACL_CLOSE(0x80)/ACL_REHASH(0x400) asc_long,ServerChannels_SCH_NOTICE(=1) asc_uint,RPL_CLOSING(362)/RPL_CLOSEEND(363)/RPL_REHASHING(382). - Faithfulness notes.
m_close'sfor (i = highest_fd; i; i--)walk descends to 1 and never toucheslocal[0](ported aslet mut i = highest_fd; while i != 0 { let acptr = local[i]; i -= 1; … });replies[RPL_CLOSING]carriesget_client_name(acptr,TRUE)(string) +acptr->statuspassed asc_int(C default‑arg promotion of thec_short); each pending fd closed viaexit_client(acptr, acptr, &me, "Oper Closing").m_rehashreturnsrehash(cptr, sptr, (parc > 1) ? parv[1][0] : 0)— the third arg is the first char ofparv[1](**parv.add(1) as c_int),0when absent;RPL_REHASHINGcarriesmybasename(configfile)and is emitted before the SCH_NOTICE (and beforerehash()runs). Never reads anaClientby 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‑1m_nopriv/481 at the parse table (never enters the handler). No L1 — both are pure wire handlers terminating insendto_one/sendto_flag(no socket‑free data op, nocref_oracle — cf. m_motd P5pp, m_map P5mm, m_list, m_names). No S2S (skip + note per the skill's Classify step): neither has anIsServer(cptr)dispatch branch nor formats remote‑user fields —m_closeacts only on locallocal[]fds,m_rehashreloads local config; the existing 13golden_s2s_*scenarios stay byte‑identical as the regression gate. format!per user request — N/A. Every wire line goes straight to the C variadicsendto_one(reply(…),…)(the numerics) orsendto_flag(SCH_NOTICE, "…%s…", …)(the oper notices). Nothing assembled withformat!.- L2 is the gate.
golden_s_serv_maint(4 scenarios, ref‑C == Rust byte‑identical aftercanonicalize()): the newmaint_oper.conf(O‑line flagslr= ACL_CLOSE|ACL_REHASH) drivesOPER alice secretthen — 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 viaoper.conf(O‑line with no flags) drives REHASH/CLOSE →481 ERR_NOPRIVILEGES(the handler‑internalis_allowed→m_noprivbranch, distinct from the col‑1 parse‑table reject).cargo build0 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).
- Cluster choice. The remaining s_serv.c oper commands (
-
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 intoircd-common/src/s_serv.rs, extending the existings_serv_link.opartial port (‑DPORT_SERV_P5). Elevenths_serv.csub‑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_eobackare the thematic end‑of‑burst pair: server‑only, already exercised in C bygolden_s2s_s_serv_map(which sends a peerEOB), with a clean directly‑observable peer‑socket output (theEOBACKreply). Porting them advances s_serv.c without touching the burst recursion. - Guard + RED→GREEN. One contiguous
#ifndef PORT_SERV_P5guard spanning both bodies (s_serv.c:3059‑3214). Neither is a file‑staticor anyone's static callee → no extern‑prototype switch; theirmsgtabdecls (parse.rs imports) resolve to the Rust defs at link. RED left exactlym_eob/m_eobackundefined (undefined reference to 'm_eob'/'m_eoback'from.data.msgtab), matching thenmchecklist; after GREENnm target/debug/ircd | grep ' T (m_eob|m_eoback)$'→ both defined from Rust;cargo build0 warnings. - Config‑resolved bodies. No
#ifdefinside either body under the locked config (USE_SERVICESoff — the nearbycheck_services_butonebelongs tom_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 helperset_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_DEBUGbindgen 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. ThenSetEOB(sptr),istat.is_eobservers += 1, and (hopcount == 1, directly linked)sendto_one(sptr, ":%s EOBACK", me.serv->sid).parc<2→sendto_serv_v(sptr, SV_UID, ":%s EOB", sptr->serv->sid)+ return 1; the!IsBursting && parc<2arm → the"Received another EOB"SCH_ERROR + return 1. Theparc≥2mass‑SID loop:eobmaxlen = BUFSIZE - 1 - SIDLEN - 6 - 2 - (SIDLEN+1); thefor (; (sid = strtoken(&p, parv[1], ",")); parv[1] = NULL)C idiom ported as aloop { … s = null }withpstate;find_sid(sid, NULL)→ unknown‑SID / wrong‑direction (acptr->from != cptr) bothreturn exit_client(cptr, cptr, &me, …)(link drop), already‑EOB'dacptr→ SCH_ERROR +continue, else SCH_DEBUG + (overflow flush:%s EOB :%sviaeobbuf+1) +SetEOB(acptr)+is_eobservers+++ append,<sid>(*e++ = ','; memcpy(e, acptr->serv->sid, SIDLEN); e += SIDLEN).eobbufis a function‑local stack array (not a file‑static) → a Rustlet mut eobbuf = [0 as c_char; BUFSIZE]withe/baseraw‑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, thencheck_split().m_eoback:cptr->flags &= ~FLAGS_CBURST; if (cptr->serv && cptr->serv->byuid[0]) cptr->serv->byuid[0] = '\0';. Never read anaClient/aServerby value (raw‑pointer invariant —(*(*acptr).serv).sid,(*(*acptr).from).namechains). - 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'sIsServer(sptr)guard is redundant‑but‑faithful). No L1 — pure server‑protocol handlers terminating insendto_*+aClientflag mutation, no socket‑free data op with acref_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 theeobbufcomma‑SID list, built withmemcpybyte‑for‑byte as the C (aformat!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 aftercanonicalize()): a freshly‑linked bursting peer's bareEOB→:000A EOBACKreply on the peer socket (000A= local SID,fixtures/s2s.confM‑line); the inverse — a second bareEOBnow finds!IsBursting→ no secondEOBACK(provingSetEOBactually changed state, not just the happy path);m_eoback(peer sendsEOBACK) → the link survives (a follow‑up remote‑user introduce +WHOIS bob→ 311 still resolves).golden_s2s_s_serv_map(which drives a peerEOBfor its non‑burstingMAP sbranch) stays byte‑identical as the regression gate, as dogolden_s2s_link/golden_s2s_squit. Theparc≥2mass‑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.
- Cluster choice. The remaining s_serv.c handlers are independent leaves (
-
2026‑06‑04 — P5tt (s_serv.c
m_sethandler — 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 intoircd-common/src/s_serv.rs, extending the existings_serv_link.opartial port (‑DPORT_SERV_P5). Twelfths_serv.csub‑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_setis the one remaining oper handler with a fully L2‑testable, non‑destructive success path (m_die/m_restart/m_sdieterminate the process → their success is unreachable in a golden;m_smaskis server‑only and tied to the burst cluster'sget_version/introduce_server/register_server). Porting it advances s_serv.c without touching the burst recursion. - Guard + RED→GREEN. One plain
#ifndef PORT_SERV_P5guard aroundm_set(s_serv.c:3252‑3403). Not a file‑staticor anyone's static callee → no extern‑prototype switch; itsmsgtabdecl (parse.rs imports) resolves to the Rust def at link. RED left exactlym_setundefined (undefined reference to 'm_set'from.data.msgtab), matching thenmchecklist; after GREENnm target/debug/ircd | grep ' T m_set$'→ defined from Rust;cargo build0 warnings. - Config‑resolved bodies. No
#ifdefinside 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 asconst TSET_SHOWALL: c_int = -1(all bits set → the no‑arg form's threeacmd & 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.rsme_name()helper (ME). C externs (bindgen) —activate_delayed_listeners(s_bsd.c), the variadicsendto_one/sendto_flag(the*mut c_charfmt /u_intchannel decls); libcatoi(added to the s_serv.rs extern block). Globals —iconf(iconf_tstatic,.aconnect/.cacceptread/write),poolsize(u_intstatic),firstrejoindone(c_intstatic). Consts —ACL_SET(8192) asc_long,ServerChannels_SCH_NOTICE(=1) asc_uint. - Faithfulness notes. The C
switch(acmd){…break;…}followed by the trailingif(acmd){ echo POOLSIZE/ACONNECT/CACCEPT }block is refactored into aset_finish(sptr, parv0, acmd)helper: the two "Illegal value for ACONNECT/CACCEPT" armsbreakout of the switch straight to the echo block → ported as an earlyreturn set_finish(...)(acmd is unchanged, so the echo for the recognized option still fires — exactly the C).poolsize > tmppreserves the C usual‑arithmetic signed→unsigned promotion (poolsize > tmp as c_uint);poolsize/tmppassed to%dare width‑matched (poolsize as c_int,tmpisc_int). The invalid‑option path (acmd == 0) emits":%s NOTICE %s :Invalid option %s"withparv[0]directly (noBadTo, matching the C). The ternary%svalue strings ("ND"/"ON"/"OFF"/"SPLIT") passed asc"…".as_ptr() as *mut c_char. Never reads anaClientby 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‑1m_nopriv/481 at the parse table (never enters the handler), and the body re‑gates onis_allowed(sptr, ACL_SET). No L1 — a pure wire handler terminating insendto_one/sendto_flag(no socket‑free data op, nocref_oracle — cf. m_close/m_rehash P5rr, m_motd P5pp). No S2S (skip + note per the skill's Classify step): noIsServer(cptr)dispatch branch, no remote‑user fields — m_set reads/writes only local tunables (iconf/poolsize); the existing 13golden_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 withformat!.- L2 is the gate.
golden_s_serv_set(4 scenarios, ref‑C == Rust byte‑identical aftercanonicalize()): the newset_oper.conf(O‑line flage= ACL_SET, s_conf.c:321) drivesOPER alice secretthen — bareSET→ the showall path (acmd = TSET_SHOWALL) echoing:POOLSIZE = N/:ACONNECT = OFF/:CACCEPT = ON;SET ACONNECT OFF→ apply + the:ACONNECT = OFFecho;SET BOGUS→:… :Invalid option BOGUSwith no trailing echo (acmd stays 0 — the inverse of a recognized option); the inverse viaoper.conf(O‑line with no flags) drivesSET→481 ERR_NOPRIVILEGES(the handler‑internalis_allowed(ACL_SET)→m_noprivbranch, distinct from the col‑1 parse‑table reject).cargo build0 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_statsdiverges on the volatile…kB sq(peer sendQ) field ofreport_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.
- Cluster choice. The remaining s_serv.c handlers are independent leaves (
-
2026‑06‑04 — P5uu (s_serv.c
m_wallopshandler — the WALLOPS broadcast) merged. The WALLOPS handler (s_serv.c:2561 — relay an operwall to every other server and emit it to local&WALLOPSwatchers) ported intoircd-common/src/s_serv.rs, extending the existings_serv_link.opartial port (‑DPORT_SERV_P5). Thirteenths_serv.csub‑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_wallopsis the smallest remaining leaf with a fully observable, non‑destructive S2S path (a singlesendto_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_P5guard aroundm_wallops(s_serv.c:2561‑2569). Not a file‑staticor anyone's static callee → no extern‑prototype switch; itsmsgtabdecl (parse.rs imports) resolves to the Rust def at link. RED left exactlym_wallopsundefined (undefined reference to 'm_wallops'from.data.msgtab), matching thenmchecklist; after GREENnm 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/parcare then unused (_sptr/_parc). No other#ifdefin the body. - Callees, all already resolved. The entire body is
sendto_ops_butone(IsServer(cptr) ? cptr : NULL, parv[0], "%s", parv[1]).sendto_ops_butoneis a C variadic (the P8 trampoline, common/send.c:987 — itsendto_serv_butones:%s WALLOPS :%sto every other server andsendto_flag(SCH_WALLOP)s the!from! %sframing to local&WALLOPSmembers); 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 : NULL→if is_server(cptr) { cptr } else { core::ptr::null_mut() }, ported verbatim (under the col‑0‑only dispatchcptris always a server, so it always yieldscptr— the origin‑link exclusion that keeps the WALLOPS from looping back). The"%s"/parv[1]pass straight through; the!from!framing + thevsprintfhappen inside the C trampoline, so no bytes are assembled in Rust. Never reads anaClientby value (raw‑pointer invariant — onlyis_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 hitm_nop. No L1 — a pure wire handler terminating in the variadic, no socket‑free data op with acref_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 variadicsendto_ops_butone; nothing is assembled withformat!(a Rustformat!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 aftercanonicalize()on all three transcripts): a freshly‑linked peer's:001B WALLOPS :…→ a local&WALLOPSmember (alice) receives:irc.test NOTICE &WALLOPS :!peer.test! the building is on fire(thesendto_flag(SCH_WALLOP)path;from = parv[0]resolves to the source server name, cf. the:peer.testresolution ingolden_s2s_kill). Inverse 1 (scoping): a non‑member (carol, fenced by a PING/PONG round‑trip) receives nothing. Inverse 2 (theone=cptrexclusion — the very reason this is the server path): the originating peer does not get its own WALLOPS echoed back, fenced by aPRIVMSGto a remote user that routes out the peer link (a wrongly‑NULLonewould loop the WALLOPS back to the peer).cargo build(workspace) 0 warnings; the s_serv golden +golden_s2s_linkregression stay green.
- Cluster choice. The remaining s_serv.c handlers are independent leaves (
-
2026‑06‑04 — P5vv (s_serv.c
m_errorhandler — the server‑protocol ERROR message) merged. The ERROR handler (s_serv.c:2245 — pass an error received over a server link on to local&ERRORSwatchers) ported intoircd-common/src/s_serv.rs, extending the existings_serv_link.opartial port (‑DPORT_SERV_P5). Fourteenths_serv.csub‑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 P5uum_wallops, so the L2‑S2S harness pattern is proven. - Guard + RED→GREEN. One plain
#ifndef PORT_SERV_P5guard aroundm_error(s_serv.c:2245‑2268). Not a file‑staticor anyone's static callee → no extern‑prototype switch; itsmsgtabdecl resolves to the Rust def at link. RED left exactlym_errorundefined (undefined reference to 'm_error'from.data.msgtab); after GREENnm 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#ifdefin the body. - Callees, all already resolved.
para = (parc>1 && *parv[1]!='\0') ? parv[1] : "<>". The reachable server branch issendto_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_flagis the C variadic P8 trampoline (itvsprintfsnbufthensendto_channel_butservs:%s NOTICE %s :%s=:irc.test NOTICE &ERRORS :<nbuf>to local&ERRORSmembers) — imported from bindings, not redeclared;get_client_namealready resolves (P5 import);SCH_ERROR=ServerChannels_SCH_ERROR. - Faithfulness notes.
nameis a*mut c_charself‑pointer →(*sptr).namepassed straight to%s(no.as_mut_ptr()). Returns 2 (notFLUSH_BUFFER= ‑2) → parse keeps the link. TheIsPerson(cptr)||IsUnknown(cptr)||IsService(cptr) → return 2ignore guard is ported verbatim for faithfulness but is unreachable via the col‑0‑only dispatch (those client types hitm_nop/m_unreg, neverm_error). New privateis_servicehelper (IsServicemacro, struct_def.h:141 —status==STAT_SERVICE && service). Never reads anaClientby 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 acref_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 variadicsendto_flag; nothing is assembled withformat!(a Rustformat!here would diverge from the byte‑exact trampoline framing).- L2‑S2S is the gate.
golden_s2s_error(ref‑C == Rust byte‑identical aftercanonicalize()on both transcripts): a freshly‑linked peer's:001B ERROR :the disk is full→ a local oper&ERRORSmember (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&ERRORSserver channel is+i(invite‑only) —can_join(channel.c:2091) admits it only for an oper — so the news2s_oper.conffixture =s2s.conf(same M/C/N/H lines thePeeridentity needs) plus anO|*@*|secret|alice|0|10|line, and alice OPERs before subscribing. Thecptr != sptr"via" form (needs a server introduced behind the peer — the samesendto_flag, a different format string) is review‑covered.cargo build(workspace) 0 warnings;golden_s2s_wallopsregression stays green.
- Cluster choice. A leaf server‑only handler with a fully observable, non‑destructive S2S path (a single
-
2026‑06‑04 — P5ww (s_serv.c server network‑registration cluster —
register_server+unregister_server) merged. Thesvrtopdoubly‑linked‑list (aServernexts/prevs/bcptr) manipulators (s_serv.c:3922/3940 — head‑insert / unlink a server'saServerwhenever it (re)associates or loses its client struct) ported intoircd-common/src/s_serv.rs, extending the existings_serv_link.opartial port (‑DPORT_SERV_P5). Fifteenths_serv.csub‑phase.- Cluster choice. The first pure utility/callee s_serv.c cluster since P5qq — two tiny intrusive‑list ops with no
msgtabrow, 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'sadd_server_to_tree/remove_server_from_tree(a different list —svrtopnetwork registration vs theserv->down/righttree), so the L1‑differential harness pattern is proven. - Guard + RED→GREEN. One plain
#ifndef PORT_SERV_P5guard aroundregister_server+unregister_server(s_serv.c:3922‑3956). Both areEXTERN(s_serv_ext.h:79‑80), not file‑staticand not anyone's static callee → no extern‑prototype switch; their callers (bootircd.c:814/1095, link estabs_serv.c:568/816/1238,s_misc.c:757exit_one_client) resolve to the Rust defs at link. RED left exactlyregister_server+unregister_serverundefined; after GREENnm target/debug/ircd | grep -E ' T (register_server|unregister_server)$'→ both defined from Rust;cargo build(workspace) 0 warnings. - Config‑resolved body. No
#ifdefin either body (DEBUGMODE‑gatedservs.inuseaccounting lives inmake_server/free_server, not here). Faithful pointer rewiring:register_serverhead‑inserts (svrtop->prevs = cptr->serv; cptr->serv->nexts = svrtop; svrtop = cptr->serv);unregister_serversplices out (nexts->prevs/prevs->nextsrelink,svrtopadvance when removing the head) then clearsprevs/nexts/bcptr. Operates only through thecptr->servraw pointer (raw‑pointer invariant — never anaServerby value). - Classification: utility/callee TU → L1 is the gate. No
msgtabrow (not anm_*handler), noIsServer(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 thecref_*oracle on parallelsvrtoplists built viamake_client/make_server): register A/B/C head‑inserts → chain[C,B,A]with the fullnexts/prevscross‑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'snexts/prevs/bcptrall cleared —bcptrstamped by hand first sincemake_serverzeroes it), the head C (svrtopadvances to A, A.prevs=∅), then the sole node A (svrtopempties); 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 runsregister_server(golden_s2s_link— 2 tests green), a SQUIT/exit_serverrunsunregister_server(golden_s2s_squit— green), both still byte‑identical (ref‑C == Rust) after the port.cargo build(workspace) 0 warnings.
- Cluster choice. The first pure utility/callee s_serv.c cluster since P5qq — two tiny intrusive‑list ops with no
-
2026‑06‑04 — P5xx (s_serv.c operator
CONNECThandler —m_connect) merged. The CONNECT handler (s_serv.c:2429 — an oper forces an outbound server link) ported intoircd-common/src/s_serv.rs, extending the existings_serv_link.opartial port (‑DPORT_SERV_P5). Sixteenths_serv.csub‑phase.- Cluster choice. A self‑contained
msgtabhandler leaf (only a localport/tmpport/aconf— no file‑staticshared 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_P5guard aroundm_connect(s_serv.c:2429‑2557). Not a file‑staticor anyone's static callee → no extern‑prototype switch; itsmsgtabdecl (parse.rs imports) resolves to the Rust def at link. RED left exactlym_connectundefined (undefined reference to 'm_connect'from.data.msgtab), matching thenmchecklist; after GREENnm 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#ifdefin the body. - Callees, all already resolved.
is_allowed/m_nopriv/hunt_server/find_name/find_mask/match_/get_client_name/connect_serverfrom bindings (the still‑C definitions);confglobal walked directly;sendto_one/sendto_flag/sendto_ops_butoneare the C variadic P8 trampolines (sendto_onevia the module's localextern "C"block,sendto_flag/sendto_ops_butoneimported — stable Rust can call a variadic extern). New libc externsabs+strerror(faithful to the Cabs(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_noprivgate;BOOT_STANDALONEshort‑circuit (unset in the-t -sL2 boots but ported);hunt_server(…,3,…) != HUNTED_ISME → return 1; thefind_name || find_mask"already exists fromacptr->from->name" notice; the two‑passconfwalk (CONF_CONNECT_SERVER/CONF_ZCONNECT_SERVER by name, then by host) where the Cindex(aconf->host,'@')+1is replicated asstrchr((*aconf).host, b'@').wrapping_add(1)—wrapping_addmirrors C'sNULL+1 = (char*)1when the host has no@(the short‑circuited||keeps it unreached for@‑less names that match the firstmatch_); port resolution (atoi(parv[2])/abs(tmpport)/ theport<=0"Illegal port" guard); the!IsAnOper(cptr)"Remote CONNECT %s %d from %s" ops notice; and theconnect_serverretvalswitch(0 Connecting / ‑1 Couldn't / ‑2 unknown host / defaultstrerror), restoringaconf->port = tmpport. The localme_name()binding is namedmyname(alet mewould shadow the imported staticme). Never reads anaClient/aConfItemby 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 hitsm_nopriv(481) at the parse table. No L1 — every reachable branch needs a live server/socket; there is no socket‑free data op with acref_oracle. format!per user request — N/A. Every wire line is a C variadic (sendto_one/sendto_flag/sendto_ops_butone); a Rustformat!would diverge from the byte‑exact trampoline framing the reference produces.- L2 is a gate.
golden_s_serv_connect(ref‑C == Rust byte‑identical aftercanonicalize()): newconnect_oper.conffixture (O‑line flagsC= ACL_CONNECT) → operedalicerunsCONNECT nosuch.invalid→NOTICE alice :Connect: Host nosuch.invalid not listed in ircd.conf(the conf walk misses → returns beforeconnect_server, so no live socket / nondeterminism). Inverse via plainoper.conf(no O‑line flags) → the handler‑internalis_allowed(ACL_CONNECTLOCAL)reject → 481 ERR_NOPRIVILEGES (thereturn m_nopriv(...)branch, distinct from the col‑1 parse‑tablem_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 viaSetClient) behind the peer issues a remote‑formCONNECT nosuch.invalid 0 irc.test— arriving over the link the prefix resolves to the remote oper (STAT_OPER, col 2) whilecptris the server link, som_connectruns withIsServer(cptr)true (the only non‑MyConnectentry);is_allowedshort‑circuits to 1 for a non‑local client, the conf walk misses, and the "not listed" notice routes back out the peer tooper1. Inverse: a plain remote client (+, col 1 →m_noprivat 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 + theconnect_serverretval 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_maintregression stay byte‑identical.
- Cluster choice. A self‑contained
-
2026‑06‑04 — P5yy (
s_serv.cm_squit) merged. Ported the SQUIT server-removal handler toircd-common/src/s_serv.rsvia thes_serv_link.opartial port.- Cluster choice —
m_squitis a connected component of one: its sole module-local state is the function-localstatic char comment2[TOPICLEN+1](the rewritten"<comment> (by <nick>)"buffer that must survive pastparv[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_P5wraps s_serv.c:149–311 (the whole handler, incl. its function-localcomment2); the C def drops out ofs_serv_link.o, the Rust#[no_mangle] m_squitresolves the twomsgtabreferences (SQUIT col 0 = server, col 2 = oper).nmconfirmsm_squitisTin the binary post-port. - Config‑resolved body —
USE_SYSLOGis#undef(config.h:244) → the#if defined(USE_SYSLOG) && defined(SYSLOG_SQUIT)syslog line is not compiled and not ported. A local oper reachesm_squit(parse col 2 = STAT_OPER) only becauseOPER_SQUIT+OPER_SQUIT_REMOTEare#defined (config.h:133‑134) and the O-line carries flag letterS(s_conf.c:311 → ACL_SQUIT). The*-mask expansion (my_name_for_link(ME, nline->port)) and theMyPerson(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_noprivat 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. - L2 —
golden_s_serv_squit.rs: a local oper undersquit_oper.conf(new fixture, O-line flagS) drivesSQUIT irc.test→:irc.test NOTICE alice :You can QUIT, but you cannot SQUIT me.andSQUIT nosuch.invalid→402 ... :No such server; the inverse underoper.conf(no flags) hits the handler-internalis_allowed(ACL_SQUIT)reject →481with no402. Reference-C == Rust byte-identical aftercanonicalize. - S2S —
golden_s2s_s_serv_squit.rs(mirrorsgolden_s2s_s_serv_connect.rs): a remote oper (+o, STAT_OPER) behindPeer::link()issuesSQUIT nosuch.invalid;is_allowedshort-circuits to 1 for the non-local prefix → no server found →402 oper1 nosuch.invalid :No such serverrouted back out the peer link (positive). A plain remote client issuing the same SQUIT (col 1 →m_nopriv) produces no402(inverse), proving col-2 prefix-is-oper gating. Theexit_clientteardown tail (real-link SQUIT) is a faithful passthrough exercised by the existinggolden_s2s_squit.rspeer-EOF netsplit cascade — not re-driven here (SQUITting a live link is nondeterministic to diff). - Pre-existing flake noted —
golden_s2s_s_serv_stats.rsfails on clean HEAD too (reference-C reads uninitialized peer sendq during BURST:…3544950785030750208kB sq…); unrelated to P5yy, confirmed by stash-and-rerun.
- Cluster choice —
-
2026‑06‑04 — P5zz (
s_serv.coper-shutdown cluster:m_die+m_restart) merged. The leaf oper shutdown/restart pair — the direct analogue of P5rr'sm_close/m_rehashmaintenance 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-staticbuf+ the static helpers) plus the propagation leavesm_sdie/m_encap.m_die/m_restartare the two cleanly-independent oper-shutdown leaves, ported here;m_sdie/m_encap/m_smaskgo with the server-link finale (the cluster that dropss_serv.o). - Guard / mechanism — both already sit under the
s_serv_link.opartial port (-DPORT_SERV_P5); each gets a plain#ifndef PORT_SERV_P5guard. No extern-prototype switch (neither is a file-static or anyone's static callee), noparse.rs/build.rsedit — theirmsgtabdecls resolve to the#[no_mangle]Rust defs at link. - Config‑resolved body —
DIE { 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 hitsm_nopriv/481 at the parse table. Faithful: thefor (i=0; i<=highest_fd; i++)local[]walk, theIsClient||IsService:%s NOTICE %s :Server {Terminating,Restarting}. %snotice +exitc=EXITC_DIE('d') + (clients only)exit_client, theIsServer:%s ERROR :{Terminated,Restarted} by %sline,flush_connections(me.fd), thens_die(0)/restart(buf).killer[HOSTLEN*2+USERLEN+5]=141 → a Rust stack array; the defensivestrcpy(killer, get_client_name(sptr,TRUE))replicated.m_restart's Csprintf(buf, "RESTART by %s", …)used the file-staticbufas in-call scratch (passed straight torestart()); the still-Ccheck_version/m_server_estabkeep that static, so the Rust port writes a module-privateRESTARTBUF— 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 ofsendto_oneterminating in the process-killings_die/restart). - L2 —
golden_s_serv_shutdown(the non-destructiveis_allowedreject): a flag-less oper (oper.conf) issuesDIE/RESTART→ the handler-internalis_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_setstay byte-identical. - S2S — none. Neither handler has an
IsServer(cptr)dispatch branch nor formats remote-user fields; theIsServer(acptr)arm in the local fd-loop sends anERRORover 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.
- Cluster choice — the remaining C of
-
2026-06-04 — P5aaa (
s_serv.cENCAP/SDIE server-broadcast stub cluster:m_encap+m_sdie) merged. Two server-only leaf handlers ported toircd-common/src/s_serv.rsvia the existings_serv_link.opartial port (-DPORT_SERV_P5);s_serv.ostays 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_P5guard (no extern-prototype switch — neither is anyone's static callee; theirmsgtabdecls resolve to the Rust defs at link). - Config-resolved bodies —
m_encap(s_serv.c:3884) rebuilds:<src-sid> ENCAP <mask> <cmd> [args…]into a function-localchar buf[BUFSIZE](→ a Rust stack array, not a module static),i>=3 && i==parc-1selecting the:-prefixed trailing param, thensendto_serv_v(cptr, SV_UID, "%s", buf); the over-length guard emitsENCAP 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: thelen + strlen(parv[i]) >= BUFSIZE-2size-promotion compare ported asusize;sptr->serv->sid(a[c_char;5]array) passed to the%svariadics 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 hitsm_nop). No L1 (pure wire/broadcast handlers, no socket-freecref_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 argthen: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-upPRIVMSG bobrouting 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 andm_encap's SCH_ERROR over-length path are review-covered — the single-peer harness has no second direct server link to receive thebutonebroadcast (SID001Bis hardcoded tofixtures/s2s.conf), and the 512-capped reader can't deterministically overflowBUFSIZE-2in the rebuilt buffer. Regression:golden_s2s_link/golden_s2s_squit/golden_s2s_eobstay byte-identical; workspace builds 0 warnings.
- Cluster choice — the next small independent remaining s_serv.c component: both handlers' sole action is a
-
2026‑06‑04 — P5bbb (s_serv.c server‑introduction / link‑establishment / burst — FINAL s_serv.c cluster) merged.
s_serv.cis now fully Rust;s_serv.odropped 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 emitterssend_server/introduce_server/send_server_burst— plus thecheck_servername_errors[3][2]string table that P5qq deliberately kept C (a#[no_mangle]strong def would have collided with the C BSS symbol whiles_serv.owas still linked). - Drop mechanism — every symbol
s_serv.cdefines now lives inircd-common/src/s_serv.rs, sos_serv.ois added toPORTEDand dropped from the link set outright (mirroring P5hh's channel.o drop): the P5ii…P5aaas_serv_link.osecond‑compile (‑DPORT_SERV_P5, which also carried‑DIRCDMOTD_PATHfor the then‑still‑Cm_motd) and thes_serv.o→s_serv_link.olink‑set mapping are removed; the cref oracle keeps the unguardeds_serv.ofor differential testing. - Config‑resolved bodies — USE_SERVICES off (the
check_services_butonearms inm_server/m_server_estabnot compiled); ZIP_LINKS off (thezip_init/FLAGS_ZIP/connect‑burst‑stats arms gone;encr = cptr->passwddirectly); JAPANESE off (noFLAGS_JP); CRYPT_LINK_PASSWORD off (nocrypt()link‑password);#ifndef HUBIS compiled (a leaf refuses a second server link); USE_SYSLOG off.check_servername_errorsis reproduced as a#[no_mangle] pub static mut [[*const c_char; 2]; 3](astaticof raw pointers isn'tSync→ thereplies[]‑in‑s_err.rs pattern) becauseircd.c:1056reads[i‑1][1]for its fatal‑error message. - Faithfulness notes — the in‑place
cptr->infosplit incheck_version(*id++ = '\0'chains) ported via raw‑pointer writes; theget_version0210/021/else classification kept exact (idarg unused, retained for signature fidelity);set_server/strncpyzt/str_eq(StrEq)/TIMESEC=60helpers added; them_server_estabEOB‑list builder (raw‑pointereobbufwalk +memcpyof eachaServer.sid) and the client/service UNICK/SERVICE burst loop (for acptr=&me; acptr; acptr=acptr->prev) ported faithfully; the deadSV_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 acref_oracle:check_version+ transitivelyget_version); the five statics + the three handlers terminate in sockets/exit cascade → no L1. - L1 —
s_serv_version_diff.rs:check_versionvscref_check_versionon parallelmake_client'd clients with an empty conf (sofind_two_masks/find_conf_flagsreturn 0 → the happy path, noexit_client). Corpus covers the0210‑beta991199(old) vs991200(ok) boundary, the021xfast‑path, else→SV_OLD, the in‑placeinfotruncation, and theZ→FLAGS_ZIPRQflag (with aPflag so the faithfulBOOT_STRICTPROT"Unsafe mode" exit —bootoptdefaults toBOOT_PROT|BOOT_STRICTPROT— isn't taken, keeping it a clean data op) + theDefInfopointer‑identity early‑return. Zero diff. This is the only gate on the version branches: the live S2S harness only links with the fixedPEER_VERSION0211030000(oneget_versionbranch). - L2‑S2S — the headline gate is the whole existing 37‑test
golden_s2s_*suite: every test'sPeer::link()PASS/SERVER handshake drivesm_server(local IsUnknown→handshake)→check_version→m_server_estab(N/C conf + password validation,det_confs_butmask, hash/tree/svrtopregistration,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_rejectdrivesm_server's unauthorized‑link reject. All byte‑identical (the lonegolden_s2s_s_serv_statsfailure is the documented pre‑existing reference‑C uninitialized peer‑burst‑sendq flake —…3544950458613235712kB sqleft vs Rust's correct0kB sq— not a regression). Newgolden_s2s_server.rscloses the gap the user‑only UNICK introduce can't:m_server'sIsServer(cptr)remote‑server‑introduction branch — a peer:001B SERVER sub.test 1 002C 0211030000 :Sub Server(theH|*||peer.test|1|hub line admits it at hop 1) →make_client/make_server, the L/H/Q‑line gating, the remoteaServercreation,add_server_to_tree+ SID/client‑hash registration,introduce_server— observed viaLINKS(the new server'sup/sid/infoformatted bym_links), byte‑identical ref‑C vs Rust. - S2S inverse / no‑path note — the server‑removal inverse,
m_smask, and the multi‑serversend_server/introduce_serverpaths 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 — soremove_server_from_treestays covered byte‑for‑byte bygolden_s2s_squit.rs(a peer‑EOF netsplit), andsend_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).
- Cluster choice — the connected component left in
-
2026-06-04 — P5ccc (
s_user.chunt_server/next_client, the final s_user.c cluster) merged.s_user.cis now FULLY ported →s_user.odropped outright; onlys_auth.cremains C among the P5 TUs.- Cluster choice — the two routing callees left in
s_user_link.o(nmshowed onlyhunt_server+next_clientstill C):next_client(s_user.c:119) finds the nextclient-list entry matching a wild mask (exact-hash shortcut viafind_client+ wildcardnext->nextwalk);hunt_server(s_user.c:155) resolvesparv[server](find_client→find_sid→find_server→find_service, each nulling a self-loop hit, thencollapse+next_clientwildcard walk), rewritesparv[server]to the matched server's real name, andsendto_one-forwardscommand(or 402s). Pure callees — not inmsgtab; 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). Addedis_registered(status >= STAT_SERVER || == STAT_ME) +my_service(MyConnect && IsService) inline helpers; importedfind_sid/find_service/client. Faithful: the 9-slotsendto_one(acptr, command, parv[0..8])forward, theIsService(sptr)dist-vs-acptr->serv->sidreject arm, and the for-loopcontinue→increment semantics ported byte-for-byte. - Build —
s_user.oadded toPORTED; removed thes_user.o → s_user_link.osubstitution inlink_objs()and thes_user_link.osecond-compile recipe (-DPORT_USER_P5+ FNAME_USERLOG/CONNLOG/OPERLOG). The cref oracle keeps the full unguardeds_user.o. - Classification — utility/callee TUs (no
msgtabrow). L1 is the socket-free gate; L2-S2S covers the liveIsServer/remote-link routing. - L1 (
s_user_hunt_diff.rs, zero-diff vscref_) —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-freeHUNTED_ISMEfast-paths (parc≤server, BadPtr, match-ME),HUNTED_PASS(route to a directly-linkedpeer.test→ return + parv[server] rewrite + the:alice VERSION :peer.test\r\nforwarded line buffered into the target sendQ, send_diff pattern), andHUNTED_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.testobserved at the peer;VERSION peer.*→ thenext_clientwildcard walk → same forward;VERSION nosuch.server→ NOSUCH 402; inverse re-drive stays 402. The whole existinggolden_s2s_*suite routes viahunt_server(info_links/lusers/motd/trace/who re-verified green), so it is the de-facto regression gate. - Known flake —
golden_s2s_s_serv_statsstill fails on the documented reference-C uninitialized peer-burst sendq garbage (…kB sq); pre-existing, not a regression.s_user_umode_difffails to compile on clean HEAD too (bindgenc_charsignedness rot) — unrelated to this cluster.
- Cluster choice — the two routing callees left in
-
2026‑06‑04 — P5whowas (
whowas.cm_whowas, whowas.o dropped) merged. The P2‑deferred WHOWAS command handler ported →whowas.cfully Rust.- Cluster choice —
m_whowaswas the only C remnant ofwhowas.o, kept behindwhowas_link.o(-DPORT_WHOWAS_P2) since the P2 ring port because its reply path needed the variadicsendto_one(P3) and routing calleescanonize(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 symbolwhowas.odefines is now Rust →whowas.oadded toPORTEDand dropped outright (the rename match arm"whowas.o" => "whowas_link.o"and the P2c second‑compile step removed fromircd-sys/build.rs). The cref oracle still compiles the full unguardedwhowas.o(inCREF_OBJS, unaffected byPORTED), socref_add_history/cref_get_history/… survive for the L1whowas_diff. - Config‑resolved body —
parc<2→431;parc>2→atoi(parv[2])max;parc>3→hunt_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, matchingmycmp(nick,ww_nick)→ 314RPL_WHOWASUSER(up->username/host,ww_info) + 312RPL_WHOISSERVER(up->server,myctime(ww_logout));j>=maxearly break; not‑found → NICKLEN‑truncate + 406; trailing 369. - Faithfulness notes —
upis declared once and reset to NULL only via theelse up=NULLarm (so a found token leaves it set across theif up.is_null()check, then clears it for the next token) — ported literally.p[-1]=','token restore via*p.sub(1). The helpersme_name/reply/bad_to/my_connectmirror s_user.rs. - Classification — handler cluster (msgtab WHOWAS row), server‑reachable: it formats
up->server(a remote‑user field) and forwards viahunt_server→ L2 + L2‑S2S required; no L1 (pure wire handler; the ring it reads is already L1‑covered bywhowas_diff). - L1 — none new;
whowas_diff(P2) stays zero‑diff vscref_(ring add/get/find/off/count) and now links against the dropped‑whowas.obuild. - L2 —
golden_whowas: alice registers + QUITs (→add_history), bobWHOWAS alice→ 314/312/369 found path,WHOWAS alice 1exercises thej>=maxbreak,WHOWAS ghost→ 406 inverse; byte‑identical ref‑C vs Rust. 312'smyctime(ww_logout)is wall‑clock volatile → masked locally (the sharedcanonicalizeleaves 312 untouched for the stable‑server‑info WHOIS goldens).golden_s_user_canonize(406/369 dedup) is the regression. - S2S —
golden_s2s_whowas: (1) a peer introduces+QUITsrmuser→ localWHOWAS rmuserrenders 312 withpeer.test(the remoteup->server, notirc.test); (2)WHOWAS ghost 1 peer.test→hunt_servermatches parv[3] and forwards the command out the link, observed peer‑side. byte‑identical. The remote‑sendermax>20cap is review‑covered (the single‑peer harness reply‑routing doesn't reach a remote sender passing max>20).
- Cluster choice —
-
2026-06-04 — P5hash (
hash.cm_hash/ HAZH) merged. The last C remnant of hash.c —m_hashwas kept in C via the P2dhash_link.opartial-compile (-DPORT_HASH_P2) while the 6-table hash machinery moved toircd-common/hash.rsin P2. Porting it letshash.obe dropped outright.- Cluster choice —
nm hash_link.odefines exactly one symbol,m_hash; every symbol it references (the 6 tables, thecl/uid/ch/sid/cn/ip × hits/miss/sizecounters, the_*SIZEglobals, 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"toPORTED, removed the"hash.o" => "hash_link.o"link-set remap, and deleted the P2dhash_link.osecond-compile step inircd-sys/build.rs. The cref oracle keeps the unguardedhash.o(socref_*hash symbols survive the L1hash_difftest). - Config-resolved body — DEBUGMODE/HASHDEBUG OFF → the
show/listsubcommands andshow_hash_bucketare 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%fratio /%%Full/Av Hitslines reproduce the C(float)(double_expr)cast + varargs float→double promotion via(expr as f32) as f64(incl. the inner(float)tothits/(float)usedcasts). - Classification — handler cluster (msgtab
HAZH 0 MPAR { m_nop, m_nopriv, m_hash, m_nop, m_nop }); columns index by clientstatus, so col 2 =STAT_OPERis the ONLY path tom_hash(a local oper). Re-gated internally onis_allowed(sptr, ACL_HAZH). - L1 — none new;
m_hashis a read-only pure-wire handler (no add/remove/alloc data op). The hash data structures' round-trips are covered by the P2hash_diff.rs(re-run green after the import changes). - L2 —
ircd-golden/tests/golden_s_hash.rs+fixtures/hazh_oper.conf(O-line flagh= ACL_HAZH).oper_hazh: registeralice,OPER alice secret(→ STAT_OPER + ACL_HAZH),HAZH(theparc<2usage/table-list NOTICEs) +HAZH c(the client-table statistics block) byte-identical.nonoper_hazh_denied: a registered non-operbobrunningHAZH cis routed tom_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_hashhas noIsServer(cptr)branch and formats no remote-user fields (no->user->server/hopcount/SID trailers); genuinely unreachable over a server link under the locked config.
- Cluster choice —