P8 — Delete the C + retire the oracle harness#
P8 removes the last C from the workspace. By the P7 exit, all C logic was already Rust;
only data globals + the ~25 variadic sender trampolines (sendto_one/sendto_flag/…/
sendto_iauth, which stable Rust can't define as extern "C" variadics) remained. P8
replaces each trampoline with a non-variadic Rust sender API and converts its call
sites (sendto_foo(c"fmt %s", …) → sendto_foo(format!("fmt {}", …).as_bytes())), then —
once the last C symbol is gone — drops all cc::Build compilation from ircd-sys and
mothballs the L1 (cref_*) / L2 (ircd-golden) oracle, migrating the load-bearing tests
into ircd-common. leveva typed-message adoption rides along with the IRC senders; the
faithful byte-output is preserved first (format!), per the user decision.
One-line summaries: PLAN-P8-progress.md.
- 2026-06-07 — P8a (
sendto_iauth— the first trampoline) merged. Ripped out the C variadic iauth-pipe writer; the iauth control protocol now flows entirely through Rust.- Cluster choice.
sendto_iauth/vsendto_iauth(s_auth.c:120-170) is the cleanest first trampoline:nmconfirms zero undefined references to either symbol in any link-set object (s_bsd_link.o/ircd_link.o/s_auth_link.o/send_link.o/support_link.o) — every remaining C caller sits in an already-#ifdef'd-out body — so the only live callers are the 24 Rust sites. It is also self-contained (a single pipewrite, no broadcast/target iteration like the IRC senders). - leveva note. This trampoline carries the iauth pipe protocol (
<id> <cat> <info>), not IRC messages, so leveva'sMessage/identifier newtypes don't apply. Per the user decision, formatting isformat!now (faithful bytes first); leveva typed messages arrive with the IRC sender clusters. - Port. New
pub unsafe fn sendto_iauth(line: &[u8]) -> c_int+pub unsafe fn ia_str(*const c_char) -> String(lossy C-string read forformat!) inircd-common/src/s_auth.rs. Faithful tovsendto_iauth:adfd < 0→ -1; `abuf = line- b"\n"
; thewrite(adfd, abuf, len)loop re-issues from the buffer **base** each iteration (the Cpcursor is advanced but never passed towrite— replicated, harmless since the pipe takes each small line in one write); EAGAIN/EWOULDBLOCK → 0-byte partial; hard error →sendto_flag(SCH_AUTH, "Aiiie! lost slave…")+close+adfd=-1+start_iauth(0)→ -1.len == abuf.len()matches the Cstrlen(p)because aformat!-built line (or a NUL-terminated C buffer read viaCStr::to_bytes`) carries no embedded NUL.
- b"\n"
- Call sites (24). s_auth.rs (9), s_user.rs (3), s_bsd.rs (8, incl. the former
sendto_iauth_p7fflink_namealias — nowuse … as sendto_iauth_p7ff), ircd.rs (3), res.rs (1).%s-of-C-string args go throughia_str; whole-buffer passthroughs (start_auth<fd> C …, setup-auth<fd> Opacking) passCStr::from_ptr(buf).to_bytes(). The 7 variadicextern "C" { fn sendto_iauth/_p7ff }decls deleted. - C drop.
#ifndef PORT_S_AUTH_SENDTO_IAUTH_P8aaround both C functions;-Dadded to thes_auth_link.ocompile inircd-sys/build.rs.nmconfirmss_auth_link.ono longer defines or references the symbols, the binary carries only the mangled Rustircd_common::s_auth::sendto_iauth(no exported Csendto_iauth), and the cref oracles_auth.okeepscref_sendto_iauth/cref_vsendto_iauthfor the L1 diff. Only s_auth.c's data globals (iauth_conf/iauth_stats/adfd/iauth_options/…) remain C. - L1. New
ircd-testkit/tests/sendto_iauth_diff.rs: on two AF_UNIX socketpairs, build the same iauth line through the oraclecref_sendto_iauth(fmt, …)and the Rust core (pre-formatted bytes), read both pipe ends, assert byte-identical. Covers every%s/%d-of-fd format family at the call sites, the built-buffer passthrough, and theadfd < 0inverse (both return -1, write nothing). Not byte-diffed (faithful by inspection, noted): the debug-1 E last=%u start=%x …line (formats raw heap pointer addresses → nondeterministic) and the lost-slave hard-error branch (no deterministic way to force a fatalwrite). - No regression / no S2S. The iauth L1 suite (
start_auth_diff— whoseadfd>=0handoff now drives the Rust core —,read_iauth_diff,authports_diff,iauth_reporters_diff,start_iauth_diff) +golden_registrationstay green;cargo build0 warnings. No S2S — formats no remote-user wire fields. - Plan:
docs/superpowers/plans/2026-06-07-p8a-sendto_iauth.md.
- Cluster choice.
-
2026-06-07 — P8b (
sendto_serv_butone+sendto_ops_butone— the coupled server-broadcast pair) merged. Ripped out the second and third variadic sender trampolines in one step, because they form a C→C edge.- Cluster choice / the coupling.
sendto_serv_butone(send.c:517-535) is a clean self-contained broadcast — looplocal[fdas.fd[i]]over directly-linked servers, skipone's uplink +me,vsendprep(format→trunc 510→CRLF) once +send_messageeach. But it is not a leaf of the C sender call-graph: the still-Csendto_ops_butone(send.c:989, the WALLOPS/oper-notice sender) calls it. Withserv_butoneported (mangled Rust), that C caller broke the link (undefined reference to sendto_serv_butone).sendto_ops_butoneis itself only Rust-called (a graph leaf) and only callsserv_butone(now Rust) +sendto_flag(stays C), so porting it too closes the edge cleanly — no transitional C shim. - leveva note. These carry IRC server-protocol messages. Per the user decision the
bytes are built faithfully first via a new
wire!/WirePartraw-byte line builder (NOT lossyString/format!) — quit/kill comments and other arbitrary remote content pass through byte-for-byte. leveva typedMessages adopt alongside later. - Port (
ircd-common/src/send.rs).cstr_bytes(*const c_char) -> &[u8](raw, NULL→empty);trait WirePartimpls for&[u8;N]/&[u8]/*const c_char/*mut c_char/c_int(the lone%d, faithful decimal);wire!(b":", uid, b" QUIT :", comment)macro (#[macro_export]);prep_line=vsendprepfaithful (IRCII_KLUDGE OFF: ≤510 + CRLF);pub unsafe fn sendto_serv_butone(one, body: &[u8])(the C loop verbatim, build line on first recipient);pub unsafe fn sendto_ops_butone(one, from, body: &[u8])=serv_butone(:from WALLOPS :body)+sendto_flag(SCH_WALLOP, "!%s! %s", from, body)(the still-C variadic flag sender gets a NUL-terminatedbody_cstrfor its%s). - Call sites (18). serv_butone (11): parse(1 KILL), s_misc(1 QUIT), s_user(6:
MODE ±a / KILL ×3 / NICK), channel(2 PART). ops_butone (7): s_user(3: MODE-notice /
Bad UID / UID collision), s_serv(4: Remote CONNECT[+%d] / SQUIT / ERROR-relay /
brought-in). Each per-file
extern "C" { fn sendto_serv_butone/ops_butone }decl deleted (s_misc/s_user/channel) and thebindings::imports (parse/s_serv) repointed tocrate::send. - C drop.
#ifndef PORT_SEND_SERV_BUTONE_P8b/#ifndef PORT_SEND_OPS_BUTONE_P8baround the two C bodies in common/send.c; both-Ds added to thesend_link.ocompile in ircd-sys/build.rs (alongside the P3-DPORT_SEND_P3).nmconfirmssend_link.ono longer defines or references either symbol (still defines the other trampolines — sendto_one/sendto_flag/sendto_prefix_one), the binary carries only the mangled Rustircd_common::send::sendto_{serv,ops}_butone(no exported C symbol), and the cref oracle keepscref_sendto_{serv,ops}_butonefor the L1 diff. - L1. New
ircd-testkit/tests/sendto_serv_butone_diff.rs(mirrorssend_diff.rs+ thecheck_pings_diff.rsGLOBALS_LOCKshared-global pattern): parallel Rustlocal/fdasand oraclecref_local/cref_fdasworlds of fake server clients (fd −1 → buffer to sendQ), drive the Rust core vs the variadiccref_oracle, compare each recipient's sendQ bytes. 4 cases: QUIT broadcast (high-byte comment passthrough),510 truncation, one-uplink exclusion (the inverse — excluded server's sendQ stays empty), and ops WALLOPS broadcast (sendto_flag half a no-op with no oper watchers).
- L2-S2S. The existing broadcast goldens boot reference-C + Rust and diff the wire —
a direct C-vs-Rust comparison of these senders.
golden_s2s_{away,quit,nick,kill, wallops,membership,join,save,s_serv_connect,s_serv_squit}all byte-identical. - Plan:
docs/superpowers/plans/2026-06-07-p8b-sendto_serv_butone.md.
- Cluster choice / the coupling.
-
2026-06-07 — P8c (
sendto_serv_v) merged. Third variadic server-broadcast trampoline ripped out, following P8a (sendto_iauth) and P8b (sendto_serv_butone+sendto_ops_butone).- Cluster choice —
sendto_serv_v(send.c:541-572) is the near-identical sibling of the already-portedsendto_serv_butone: identicalfdas.highest→0 loop overlocal[fdas.fd[i]], skipone's uplink (cptr != one->from) andme, build ONEvsendprep'd line on the first recipient,send_messageeach. Picked as the cleanest next target precisely because the delivery logic was already validated in P8b. - Config-resolved body — the
verparameter gates delivery oncptr->serv->version & ver, but that block is#if 0'd out in the C source ("We're not using it for now … --B."), soveris dead code: no recipient is ever skipped on it andrcis never set to 1. The compiled behavior issendto_serv_butonethat additionally takes (and ignores)verand always returns 0. The Rust port keeps the_verparameter (call sites still passSV_UID, faithful) and returns 0. - Call-graph check —
grep/nmconfirmed NO live C caller: every caller (ircd.c, s_serv.c, s_user.c, channel.c) is already ported and its.odropped, and send.c itself never callssendto_serv_v. No C→C edge → ripped out alone (unlike P8b's coupled pair). - Mechanism —
pub unsafe fn sendto_serv_v(one: *mut aClient, _ver: c_int, body: &[u8]) -> c_intinircd-common/src/send.rs, cloningsendto_serv_butone's body (sharedprep_line≤510+CRLF helper) and returning 0. 10 Rust call sites converted to&wire!(…): ircds_die(:%s SDIE); channel NJOIN tail (:%s NJOIN %s :%s%s); s_user KILL propagation (:%s KILL %s :%s!%s) + SAVE (:%s SAVE %s :%s%c%s, the%cseparator → ab"!"/b" "byte slice); s_serv 4× EOB (:%s EOB/:%s EOB :%s) + SDIE relay ("%s"over the prebuiltbuf) +m_sdie(:%s SDIE). Bindgen import ofsendto_serv_vdropped from ircd/s_user/s_serv; the local variadicexterndecl removed from channel.rs; all four nowuse crate::send::sendto_serv_v. C body guarded#ifndef PORT_SEND_SERV_V_P8c;-DPORT_SEND_SERV_V_P8cadded to thesend_link.orecipe inircd-sys/build.rs. The cref oracle keeps the unguarded copy → the symbol is no longer a C-ABI export in the Rust binary (it is now a plain Rust fn), butcref_sendto_serv_vsurvives for the L1 byte-diff. - L1 — new
ircd-testkit/tests/sendto_serv_v_diff.rs(cloned fromsendto_serv_butone_diff.rs): drives the Rustsendto_serv_v(one, SV_UID, body)andcref_sendto_serv_v(one, SV_UID, "…%s…", …)on parallellocal/fdasvscref_local/cref_fdasworlds (fake servers, fd −1), diffs every recipient's sendQ. Cases: EOB broadcast (one=NULL) + asserts both return 0; >510 truncation (512-byte clipped line); one-uplink exclusion (fd 5 =one->fromempty, fd 7 receives). Zero diff. - L2-S2S — no new test needed; the wire paths are already covered byte-identical by
golden_s2s_save(SAVE),golden_s2s_join/golden_s2s_membership(NJOIN),golden_s2s_kill(KILL),golden_s2s_s_serv_connect/golden_s2s_s_serv_squit(EOB). All re-run green.cargo build0 warnings.
- Cluster choice —
-
2026-06-07 — P8d (
sendto_match_servs+sendto_match_servs_v) merged. The channel-mask server-broadcast pair (send.c:790-876) — the next clean trampoline after the serv_butone/serv_v family.- Cluster choice — both are clean leaves:
nm/grep of send.c showed no intra-C caller (their only callers, all 12, are Rust in channel.rs already).sendto_match_servs_vis the near-identical sibling (same loop + filtering, extra deadverparam) so the two go out together, mirroring P8b (serv_butone+ops_butone) and P8c (serv_v). - Config-resolved body — JAPANESE OFF →
get_channelmask(x)isrindex(x,':')(libc::strrchr); thejp_validcontinue and the#if 0version gate are not compiled._vtherefore always returns rc = 0 (faithful to the dead gate). - Faithfulness — exclusion is
cptr == fromdirectly (NOTone->fromlike serv_butone);&-leading channel returns immediately; mask filter is!BadPtr(mask) && match(mask, cptr->name)→ skip on no-match. Line prepped once viaprep_line(≤510 + CRLF) on the first surviving recipient. Cores reuse the existingwire!/prep_linemachinery in ircd-common/src/send.rs. - Call sites — 12 in channel.rs converted to
wire!bodies: TOPIC, PART, JOIN 0 (×2), MODE +o (the 5sendto_match_servs); KICK (×3), NJOIN (×3, one:%s%stail), MODE (the 7sendto_match_servs_v). The two crate-wideextern "C"variadic decls removed; import switched tocrate::send::{sendto_match_servs, sendto_match_servs_v}. - Guard —
#ifndef PORT_SEND_MATCH_SERVS_P8daround both C bodies; the define added to thesend_link.orecipe in ircd-sys/build.rs. The cref oracle keeps the unguarded .o socref_sendto_match_servs/_vsurvive for the L1 byte-diff. - L1 — new
ircd-testkit/tests/sendto_match_servs_diff.rs: parallellocal/fdasvscref_local/cref_fdasworlds, sendQ byte-diff. 5 cases: broadcast (no channel), leading-&early return (nothing delivered),chan:server-maskfiltering (matching server receives, non-matching stays empty — the inverse invariant),from-exclusion, and_v>510 truncation + rc == 0. All green. - L2-S2S — covered by the existing
golden_s2s_{topic,kick,membership,mode,njoin,join}(the propagation paths through these senders), all byte-identical vs reference-C. No new S2S file needed — every converted path already has peer-server golden coverage.
- Cluster choice — both are clean leaves:
-
2026-06-07 — P8e (
sendto_prefix_onerip-out) merged. The first of thevsendpreprep-based prefix senders, ported alone as a clean leaf.- Cluster choice —
sendto_prefix_one(send.c:1017-1027) has no in-send.c caller: internal channel/match paths call thestatic vsendto_prefix_one, only external callers (s_numeric/s_user/channel — all now Rust) reach the public variadic. So ripping it out closes no C→C edge. It is the simplest consumer ofvsendpreprep, so it is the natural place to introduce theprep_preprepRust core that the later channel/match prefix senders (sendto_channel_butone/_butserv/sendto_match_butone/sendto_common_channels) will reuse. prep_preprepcore — faithful port ofvsendpreprep(send.c:388-419), IRCII_KLUDGE OFF. Special path (to && from && MyClient(to) && IsPerson(from)): collapse the:%sprefix arg to:from->name!from->user->username@from->user->hostwhenmycmp(par, from->name) == 0, else keep:par; non-special: emit:par+ rest verbatim. Builds the body into aVec<u8>and hands it to the sharedprep_line(≤510 + CRLF), thensend_message. Addedis_client/is_person/my_clientinline macros + importedmycmpto send.rs.- Faithfulness notes — (1) the C
!strncmp(pattern, ":%s", 3)guard: every call site leads its pattern with:%s, so in the non-variadic port (no pattern) the guard reduces to the to/from eligibility test, with the caller supplyingpar+ the pre-formattedrest. (2) the Cfrom == &anondisjunct is unreachable from the Rust callers — only the still-C channel senders substitutelfrm = &anon, and they keep using the Cvsendpreprep— soprep_preprepencodes only themycmparm; the anon arm lands when those senders are ported. The staticvsendto_prefix_one+vsendpreprepstay C for them. - Call sites (10) — s_numeric
:%s %d %s%snumeric relay; channel INVITE ×2 (:%s INVITE %s :%s); s_user PRIVMSG/NOTICE ×4 (:%s %s %s :%s, thefmtvar stays for the siblingsendto_oneserver/userhost relays) + KILL (:%s KILL %s :%s!%s). Each passes the original first-arg asparand builds the post-:%sremainder withwire!. Imports switched fromircd_sys::bindings::sendto_prefix_one(channel had a local variadic extern decl) tocrate::send::sendto_prefix_one. - Seam —
#ifndef PORT_SEND_PREFIX_ONE_P8earound send.c:1017-1027;-DPORT_SEND_PREFIX_ONE_P8eadded to thesend_link.orecipe in build.rs.nm:sendto_prefix_oneis now a mangled Rust symbol in the binary (no C def in send_link.o). - L1 —
ircd-testkit/tests/sendto_prefix_one_diff.rs: drive Rustsendto_prefix_one(to, from, par, rest)vs the variadiccref_sendto_prefix_oneon parallel make_client'd clients (fd 0 → MyConnect; STAT_CLIENT → MyClient), compareto->sendQbytes. 4 cases: special-path collapse (par == from->name), special-path no-collapse (par != from->name), non-special (serverto, not MyClient → verbatim), collapse >510 truncation. (Gotcha: fixtures must NUL-terminate before the C-string copy — an un-terminatedb"alice"intostrcpyoverrannamebufand corrupted the heap; switched to a boundedset_cstr.) - L2 —
golden_channel_invite(5),golden_s_user_message(7),golden_s2s_message,golden_s_user_kill(2),golden_s2s_killall byte-identical vs reference-C. - S2S — covered by
golden_s2s_message/golden_s2s_kill(the relay paths format remote-user prefixes); no new S2S file needed (the special-path collapse is a local-recipient behavior, exercised by the local-client message/kill goldens).
- Cluster choice —
-
2026-06-07 — P8f (
sendto_match_butonerip-out) merged. The$$/$#-mask broadcast prefix sender, ported alone as a clean leaf.- Cluster choice —
sendto_match_butone(send.c:935-987) has no in-send.c caller (grep/nmof send.c shows no intra-C reference; the only live caller ism_messagein s_user.rs). So ripping it out closes no C→C edge. It is the next clean leaf after P8e because it reuses the P8eprep_preprep/sendto_prefix_onecore as-is: its delivery isvsendto_prefix_one(cptr, from, pattern, va)with the realfrom(never&anon— only the still-C channel senders substitute&anon), so no new anon machinery is needed. - Config-resolved body — DEBUGMODE off, JAPANESE off (verified). The loop is
for i = 0; i <= highest_fd; i++overlocal[i]: skip NULL, skipcptr == one(the origin). ForIsServer(cptr): walkcptr->prev(the ordered dependent-client list) for the firstIsRegisteredUser(srch) && srch->from == cptr && match_it(srch, mask, what); if none, skip. Else (my client): requireIsRegisteredUser(cptr) && match_it(cptr, mask, what). Each survivor →vsendto_prefix_one. match_it(send.c:772-782) —MATCH_HOST(2) matchesmaskagainstuser->host(an array →.as_mut_ptr()),MATCH_SERVER(1) / default againstuser->server(a*mut c_char);matchreturns 0 on a match.IsRegisteredUser(x)=(status == STAT_CLIENT || STAT_OPER) && x->user, identical to the existingis_personhelper under the locked config (reused; added a namedis_registered_useralias for the faithful C mapping).- Faithfulness — delivery is per-recipient (the prefix collapse in
prep_preprepdepends on eachto'sMyClient-ness), so the line is re-prepped per recipient, exactly as the Cvsendto_prefix_onedoes. A server recipient is not MyClient → the line is the verbatim:par …; a local person withpar == from->name→ the collapsed:nick!user@host …. The P8e:%s-lead reduction holds: the call site suppliespar(the prefix nick) + the pre-formattedrest. - Call site (1) — s_user.rs
m_message:sendto_match_butone(one, sptr, nick.add(2), syntax, fmt=":%s %s %s :%s", parv0, cmd, nick, parv2)→(one, sptr, nick.add(2), syntax, par=parv0, rest=wire!(b" ", cmd, b" ", nick, b" :", parv2)). Import moved fromircd_sys::bindingstocrate::send. - Seam —
#ifndef PORT_SEND_MATCH_BUTONE_P8faround send.c:935-987;-DPORT_SEND_MATCH_BUTONE_P8fadded to thesend_link.orecipe in build.rs. The cref oracle keeps the unguarded.osocref_sendto_match_butonesurvives for the L1 byte-diff. The staticvsendto_prefix_one+vsendpreprepstay C (channel senders). - L1 —
ircd-testkit/tests/sendto_match_butone_diff.rs: parallellocal/highest_fdvscref_local/cref_highest_fdworlds, sendQ byte-diff. 4 cases: (1) my-client MATCH_HOST — matching client receives the collapsed line, non-matching stays empty (inverse); (2) serverprev-walk MATCH_SERVER — server with a matching dependent receives the verbatim line, server with a non-matching dependent skipped (inverse); (3) originoneexcluded while another match receives; (4) an UNREGISTERED my-client with a matching host is skipped (theIsRegisteredUsergate). All green. (Gotcha: themask/parfixtures are read as C strings — must be NUL-terminated before the Rust call; the bareb"*.ok"/b"alice"literals overran into garbage andmatch_missed. Added acstr()helper;cref_callalready terminated its copies.) - L2 — existing
golden_s_user_message(7) +golden_s2s_messagebyte-identical (them_messagerouting around the$$/$#path is unperturbed). The oper-mask delivery itself (needs an oper + a matching registered target) is the L1 gate's job; no new S2S file needed.
- Cluster choice —
-
2026-06-07 — P8g (
sendto_channel_butonerip-out +anonmachinery) merged. The channel broadcaster, ported alongside the anonymous-identity placeholder it needs.- Cluster choice —
sendto_channel_butone(send.c:459-510) has no in-send.c caller (its callers are the now-Rust channel/numeric/message handlers). It is the first of the three vsendpreprep-based channel senders and the first to needanon. The other two are deferred:sendto_channel_butservis called variadically by the still-Csendto_flag(send.c:1147) — ripping it out needssendto_flag(247 Rust call sites) ported first;sendto_common_channelsis a clean leaf (no C caller, no anon) for its own step. - The anon call-graph constraint —
anon(a file-static aClient) is referenced byinitanonymous(def),vsendpreprep(line 403),channel_butone(468), andchannel_butserv(746). Portingchannel_butone+anonto Rust whilechannel_butserv/vsendpreprepstay C means the C side must still referenceanon. Solution: Rust providesanon/ausras#[no_mangle] pub static mutexternal symbols; the guarded-out Cstaticdefinitions (-DPORT_SEND_ANON_P8g) becomeexterndeclarations so the still-Cvsendpreprep/channel_butservresolve to the Rust globals.initanonymous(called from the Rust boot spine) moves to Rust too. (The RED build confirmed this exactly: undefinedanonat send.c:409 (vsendpreprep) + 754 (channel_butserv), plusinitanonymous+sendto_channel_butone.) - anon statics —
anon: aClient/ausr: anUserviaMaybeUninit::zeroed().assume_init()(theircst/cainfoprecedent);initanonymouszeroes them withwrite_bytesthen fills the identity (name="anonymous",user->{username,host}={"anonymous","anonymous."}), with theanon.from = &anon/anon.name = namebufinterior self-pointers stable because a static has a fixed address (the no-move invariant). - prep_preprep — gains the
from == anon_ptr()disjunct alongsidepar == from->name, so an anonymous-channel member sees:anonymous!anonymous@anonymous.while the sender's own self-send (realfrom, never anon) shows their real nick. - Faithful mapping / equivalence — the C sends the raw
vsendprepline (no prefix collapse) to non-eligible (server) recipients and the prefixedvsendpreprepline to MyClient recipients. Routing both through the P8esendto_prefix_one/prep_preprepcore is byte-identical becauseprep_preprepalready branches onmy_client(to): a server recipient (not MyClient) → else-path:par + rest(==vsendprep); a MyClient recipient → the same collapse. (A localSTAT_CLIENTalways hasuser⇒MyConnect && IsClient ⇒ IsRegisteredUser, so the "MyConnect server/unknown in the else branch" case is never MyClient.) Theacptr->from == one/IsMeskips and theacptr != from(don't re-send to the self-sentfrom) guard are preserved exactly. - Call sites (7) — s_numeric
do_numeric(channel-numeric relay,:%s %d %s%s); channelcan_join(invite-overriding-ban + invite-overriding-limit NOTICEs),reop_channel(the+%c (%d)enforce NOTICE — the%cmode char passed as a 1-byte&[ch]WirePart),set_mode(the two anonymous-flag warning NOTICEs); s_userm_message(channel PRIVMSG/NOTICE,:%s %s %s :%s). Each builds the post-:%srestwithwire!. - Seam —
#ifndef PORT_SEND_CHANNEL_BUTONE_P8garound send.c:459-510 and#ifndef PORT_SEND_ANON_P8garound the anon statics/initanonymous(with the#elseextern decls); both-Ds added to thesend_link.orecipe in build.rs. The cref oracle keeps the unguarded.o(itsanonstays an internal static;cref_initanonymoussurvives for L1). - L1 —
ircd-testkit/tests/sendto_channel_butone_diff.rs: parallel Rust-world / cref-world channels (each its ownaChannel+ memberLinkchain + clients, sincechannel_butonetakes the channel by arg), sendQ byte-diff. 4 cases: (1) broadcast — a local registered member collapses, a server member gets the verbatim:par, anIsMemember is skipped; (2)oneexclusion — a member whosefrom == oneis empty while another receives (inverse); (3) self-send — a local registeredfrom(one==null) gets exactly ONE line and the loop skipsacptr == from(not doubled); (4) anonymous channel — a clientfrommakes the member see:anonymous!anonymous@anonymous.whilefrom's own self-send shows the real nick. All four call their resp.initanonymous. (Gotchas: the cref oracle formats into process-globalsendbuf/psendbuf+anon/ausrare globals → aGLOBALS_LOCKmutex serializes the parallel#[test]s; andSTAT_ME == -1, not -2, which isSTAT_UNKNOWN.) - L2 — channel goldens join (5) / part (4) / kick (2) / topic (2) / mode_set (1) / invite (5) + s_user_message (7) all byte-identical vs reference-C.
- S2S — golden_s2s message (1) / join (2) / kick (2) / topic (1) / mode (2)
byte-identical (the server-recipient verbatim-
:parpath + remote-user prefix formatting).
- Cluster choice —
-
2026-06-07 — P8h (
sendto_common_channels) merged. Ripped out the local-channel-peers broadcaster variadic trampoline (send.c:620-729), the clean leaf after P8g.- Cluster choice —
sendto_common_channelshas no in-send.c caller; its live callers are the now-Rustexit_client(s_misc, QUIT) + the two NICK-change paths in s_user. It sends a line to every person on this local server sharing a non-Quiet, non-Anonymous channel withuser, plususeritself when local — so peers see a NICK/QUIT once. - No anon — unlike P8g's
sendto_channel_butone, this sender explicitly skips anonymous channels (if IsAnonymous(chptr) continue), so the per-recipient prefix source is always the realuser, never&anon. No new anon machinery. - The
len-caching subtlety (why NOTsendto_prefix_oneper recipient) — C computesvsendprepreponce (theif (!len)guard, DEBUGMODE off) into the globalpsendbufand reuses that buffer for everysend_message. The prepped content depends on the FIRST recipient's MyClient-ness. In branch 1 (highest_fd<50) all recipients are local (MyClient) so the cache is moot, but in branch 2 achptr->clistmember can be remote (not MyClient) and still receives the cached collapsed line. So the faithful Rust port holds a local[u8;520]buf +lenand callsprep_preprep(to_or_user, user, par, rest, &mut buf)only whenlen==0, thensend_message(cptr, buf, len)— re-prepping per recipient via the publicsendto_prefix_one(which re-runsprep_preprepagainst eachto) would diverge in branch 2. sentalong[cptr->fd](branch 2) — remote fd = -1 — remote clients havefd==-1(make_client), so C'ssentalong[cptr->fd]does*(sentalong-1), a pre-existing OOB read C tolerates as pointer arithmetic. Ported as a per-callvec![0 as c_int; MAXCONNECTIONS]indexed via raw.offset((*cptr).fd as isize)into its base — matches C's arithmetic exactly (no panic, same bytes). Branch 2 is only entered athighest_fd>=50(untested at L1).- Faithful mapping —
from == userfor every call and the pattern leads:%swithpar == user->name, so MyClient recipients collapse to:nick!user@host(==prep_preprep) and non-MyClient (server) recipients get:par. Both Comstud size-gated branches ported verbatim (the HUBIsMember-over-user->user->channelwalk + the big-clientclist/sentalongwalk). New privateis_member/is_quiethelpers mirror channel.rs (via the P2-portedfind_channel_link). - Call sites (3) — s_misc
exit_client(:%s QUIT :%s→ par=(*sptr).name, rest=wire!(b" QUIT :", comment)); s_user nick-save (:%s NICK :%s→ par=(*sptr).name, rest=wire!(b" NICK :", uid)); s_userm_nick(par=parv0, rest=wire!(b" NICK :", nick)). Removedsendto_common_channelsfrom the localextern "C"blocks in both files; importedcrate::send::sendto_common_channels. - Seam —
#ifndef PORT_SEND_COMMON_CHANNELS_P8haround send.c:620-729 and around the now-unusedsentalongstatic (only that function referenced it);-Dadded to thesend_link.orecipe in build.rs. The cref oracle keeps the unguarded.ofor L1. - L1 —
ircd-testkit/tests/sendto_common_channels_diff.rs: parallel rust / creflocal[]highest_fdworlds with per-clientuser->channelLink lists, per-recipient sendQ byte-diff. 4 cases: (1) shared open channel — user self-send (MyConnect) + a co-member both collapse, a client in a different channel + a server in a local slot are skipped (inverse); (2) quiet shared channel — co-member skipped, user still self-sends; (3) anonymous shared channel — co-member skipped (no&anonsubstitution); (4) remoteuser(fd=-1) — no self-send, a local co-member still receives (line prepped from the first recipient sincelenstarts 0). AGLOBALS_LOCKserializes the parallel#[test]s (process-globallocal/highest_fd+ the oracle'spsendbuf).
- L2 —
golden_s_user_nick(5) +golden_s_user_quit(4) byte-identical (NICK change + QUIT broadcast to common channels). - S2S —
golden_s2s_nick+golden_s2s_quit(2) byte-identical (the broadcast only touches local members, so S2S exercises the surrounding propagation routing, not a new branch in this sender — no dedicated S2S path).
- Cluster choice —
-
2026-06-07 — P8i (
sendto_floglogfile cluster) merged. Ripped out thesendto_floglogger — the first non-variadic P8 leaf (the memory flagged it "port plainly"). It writes one formatted line per client exit to a logfile fd.- Cluster choice —
sendto_flogreads the file-privateuserlog/connlogfd statics (send.c:1202-1203), set bylogfiles_open(1205) and cleared bylogfiles_close(1262). Those three functions + the two statics form the connected component over the shared fds, so they port together.setup_svchans/svchans(1060-1139) stay C —svchansis a separate component shared with the still-C variadicsendto_flag(send.c:1141, 247 blocked call sites). Under the locked config (LOG_SERVER_CHANNELSOFF) the svchans open/close loops inlogfiles_open/logfiles_closeare not compiled, so the {userlog, connlog} component is cleanly separable. - Config-resolved body —
LOG_OLDFORMATOFF → the new"%c %d %d %s %s %s %s %d %s %lu %llu %lu %llu "format (the#else).USE_SYSLOG/USE_SERVICESOFF → the syslog +check_services_butoneUSERLOG/CONNLOG relay blocks gone; the!USE_SERVICES && !(USE_SYSLOG&&…)early-return (if logfile==-1 return) IS compiled.LOGFILES_ALWAYS_CREATEOFF →open()has noO_CREAT(file must pre-exist).FNAME_USERLOG/FNAME_CONNLOGset (per-object-Ds on send.o's recipe). - Formatting = byte-identical by construction — the Rust
sendto_flogcallslibc::sprintfwith the exact same C format string + args cast to match C's varargs promotions (%c/%d→c_int,%s→*c_char,%lu→c_ulong,%llu→c_ulonglong;(u_int)firsttime/(u_int)timeofdayviaas u_int as c_int). Same libc engine + same format ⇒ no formatting logic to drift. Independently checked the rendered line against a standalone Csprintfof the same values. - FNAME paths into Rust —
FNAME_USERLOG/FNAME_CONNLOGare recursively-expanded Makefile vars (per-object-Ds on send.o, absent from bindgen). build.rs expands both via make and exportsIRCD_USERLOG_PATH/IRCD_CONNLOG_PATHrustc-envs (thePID_PATHtrick);ircd-sysre-exposes them asUSERLOG_PATH/CONNLOG_PATHconsts; the Rustlogfiles_openopens those exact paths so it hits the identical files the reference-C build does. - Seam —
#ifndef PORT_SEND_FLOG_P8iwraps send.c:1202-1440 (the two statics + the three fns);-DPORT_SEND_FLOG_P8iadded to thesend_link.orecipe in build.rs. The cref oracle keeps the unguardedsend.osocref_logfiles_open/cref_sendto_flogsurvive for L1. Symbols confirmed Rust-owned:nm target/debug/ircd | grep -E ' T (sendto_flog| logfiles_open|logfiles_close)$'. - L1 —
ircd-testkit/tests/sendto_flog_diff.rs, the write_pidfile pattern (s_bsd_leaves_diff.rs): both worlds open the same hardcodedUSERLOG_PATH/CONNLOG_PATH, so drivelogfiles_open → sendto_flog → logfiles_closein the rust (c_*) and cref (cref_*) worlds, read the file back, assert identical bytes.O_CREAToff → pre-create the file empty; the pre-create failing (sandbox/usr/local/var/logabsent) mirrors theopen()failing → bothuserlog/connlogstay -1 →sendto_flogearly-returns → both files absent (None == None). On a configured box the exact bytes are pinned differentially. 2 cases:EXITC_REG→userlog, a non-REG code→connlog.timeofdayset on bothircd_sys::bindings::timeofday(rust) andcref_timeofday(oracle); aGLOBALS_LOCKserializes the parallel#[test]s (shared logfile paths + globals). Both pass. - No L2 / no S2S —
sendto_flogwrites a file, never the wire, andUSE_SERVICESis OFF (the USERLOG/CONNLOG service relay is not compiled) → nothing reaches a golden client; not amsgtabhandler. Boot smoke:logfiles_openruns at every ircd boot, sogolden_s_user_quit(4) staying byte-identical confirms the boot/rehash path is intact. - Caller — one live Rust caller of
sendto_flog:s_bsd.rs add_connection(clone reject,EXITC_CLONE) via the bindgen decl, which now resolves to the Rust symbol.logfiles_open/logfiles_closeresolve their ircd.rs/s_conf.rs bindgen callers likewise.
- Cluster choice —
-
2026-06-07 — P8j (the keystone
sendto_flag+dead_link) merged. Ripped out the server-notice broadcastersendto_flag(send.c:1141), the largest remaining trampoline (~220 call sites across 16 ircd-common files), plusdead_link(its last C caller).- Cluster choice / connected component —
svchans[SCH_MAX]is a file-static in send.c shared bysetup_svchans(populates eachsvc_ptrviafind_channel) andsendto_flag(readssvc_ptr), so porting sendto_flag pulls insvchans+setup_svchansas one unit (the P8b/P8i precedent).sendto_channel_butservstays C (still variadic) — the Rust core calls it via the bindgen variadic decl; it is channel_butserv's only C caller, so channel_butserv is unblocked as the next step.dead_link(send.c:50, variadic, exposed for the Rust send core) was the last C caller of sendto_flag, so it ports here to close that edge — its only callers are the already-Rust send_message/send_queued. - Config-resolved bodies —
sendto_flag: USE_SERVICES off → the check_services_butone NOTICE/ERROR relay gone; LOG_SERVER_CHANNELS off → the(svchans+chan)->fdlogfile write gone; reduces to clamp → svchans lookup →sendto_channel_butserv(chptr,&me,":%s NOTICE %s :%s",ME,chname,nbuf).setup_svchans: LOG_SERVER_CHANNELS off → only the find_channel loop compiled.svchansinitializer: CLIENTS_CHANNEL off → 14 entries (ERROR..WALLOP, OPER), SCH_MAX == 14.dead_link: DEBUGMODE off → the trailingDebug()is a no-op. - svchans as a Rust static —
#[no_mangle] pub static mut svchans: [SChan; SCH_MAX], names viab"&...\0"byte statics so thesvc_chnamepointers fill in const context (aconst fn sch()helper).setup_svchansis#[no_mangle] extern "C"so the still-bindgen caller (channel.rs rehash) resolves to it. - wire! / WirePart growth — the call sites carry more than
%s/%d: tally was 298%s, 75%d, 8%08x, 6%u, 6%02d, 4%x, 3%p, 3%2d, 2%X, 1%c(+ 4%#xthe tally regex missed). Addedimpl WirePart for c_uint(%u) and pub newtypesHex08/Hex/HexUp/HexAlt(%#x)/Dec02/Dec2/Chr/Ptr. Integer specifiers render via Rustformat!(byte-identical to C for these specs — verified{:08x}/{:02}/{:2}etc.);%#xand%pdefer tolibc::snprintfso they match glibc exactly (e.g.%#xof 0 →0, not0x0;%pof NULL →(nil)). - Faithfulness of the ~220 conversions — a string-aware checker reconstructed each new
wire!skeleton (literal byte-runs + specifier markers) and compared it to the original C format string: 203 call sites byte-faithful, 0 real mismatches. Edge cases handled: s_bsdreport_errortakes a runtime format param (not a literal) → rendered vialibc::sprintfwith the runtime format then handed the bytes (exactly what the old variadic did); the one NULL%s(s_service, server-always-NULL path) renders the literal(null)glibc's vsprintf emits; the%#xdebug sentinels (list/whowas refcnt loops) useHexAlt. - Seam —
#ifndef PORT_SEND_FLAG_P8jwraps send.c:1057-1200 (svchans + setup_svchans + sendto_flag);#ifndef PORT_SEND_DEAD_LINK_P8jwraps send.c:50-76 (dead_link); both-Ds added to thesend_link.orecipe in build.rs. The cref oracle keeps the unguarded send.o socref_sendto_flag/cref_setup_svchans/cref_dead_linksurvive for L1. Symbols confirmed:nm target/debug/ircdshowssetup_svchansT +svchansD Rust-owned, and send_link.o no longer defines sendto_flag/setup_svchans/dead_link. - L1 —
ircd-testkit/tests/sendto_flag_diff.rsdrives the Rustsendto_flagvscref_sendto_flagthrough each world'ssetup_svchans+ a&NOTICESchannel inserted into its channel hash (add_to_channel_hash_table) with one local member, diffing the membersendQ. 4 cases: basic:me NOTICE &NOTICES :textwrap, mixed%d/%s/%ucall-site rendering,chan >= SCH_MAXclamp to SCH_NOTICE, and the NULL-svc_ptr no-op inverse. AGLOBALS_LOCKserializes (svchans/me/channel-hash are process-global). All 4 pass; the P8b– P8i send-core L1 suite still green. - L2 — server-notice channels (
&NOTICESetc.) are never created in the golden scenarios, sosvc_ptris NULL andsendto_flagis a no-op there; the value of the golden run is confirming the ~220 call-site conversions did not change any non-notice wire output. 16 golden binaries byte-identical: registration, s_user_nick (5), s_user_quit (4), channel join/part/kick/topic, s2s nick/quit/kill/join/eob/server/squit/njoin/s_serv_connect. No dedicated L2 path for the notice text itself (no&-channel membership in the harness).
- Cluster choice / connected component —
-
2026-06-07 — P8k (
sendto_channel_butserv) merged. Ripped out the channel local-members broadcastersendto_channel_butserv(send.c:746-780) — the relay that pushes TOPIC/PART/KICK/JOIN/MODE (and the anonymous PART, andsendto_flag's server notices) to the members of a channel connected to this server.- Unblock / cluster choice — P8j made
sendto_flagRust; that was channel_butserv's only in-send.ccaller, so it became the next clean leaf. Unlike P8gsendto_channel_butone, butserv never reaches a server recipient (the loop keeps onlyMyClient(acptr)— that's the "butSERV"), so every recipient is MyClient with a constantlfrm. That makes the per-recipientsendto_prefix_one(the P8eprep_preprepcore) byte-identical to the Cpsendbuf/if (!len)single-prep cache — no need to replicate the cache. - Config-resolved body — JAPANESE off (no
jp_valid), DEBUGMODE off (noDebug()):MyClient(from)self-send first via the real (un-anonymized) prefix, thenif IsQuiet(chptr) return;IsAnonymous(chptr) && IsClient(from)→lfrm = &anon(the P8g anon globals, now Rust); loopchptr->clistdelivering one line to eachMyClient(acptr) && acptr != from. - Rust core —
pub unsafe fn sendto_channel_butserv(chptr, from, par: *const c_char, rest: &[u8])inircd-common/src/send.rs, right aftersendto_channel_butone. Reusessendto_prefix_one/prep_preprep/is_quiet/is_anonymous/anon_ptr/my_client. - Seam —
#ifndef PORT_SEND_CHANNEL_BUTSERV_P8kover channel_butserv (746-780) AND its now-orphaned static helpersvsendpreprep(405-444) +vsendto_prefix_one(fwd decl 34 + def 1057-1065) + thepsendbuf[2048]buffer (35): channel_butserv was their last unguarded caller (the other callers — channel_butone/common_channels/prefix_one/match_butone — were already guarded out in P8e-h).vsendprep/sendbufstay live (vsendto_one); theanon/ausrexterns stay (harmless unused externs in send_link.o).-Dadded to the send_link.o recipe in build.rs. cref oracle keeps the unguarded send.o socref_sendto_channel_butserv/cref_initanonymoussurvive for L1. - Call sites (14) —
par= the:%sprefix arg,rest= everything after:%srebuilt withwire!. channel.rs: TOPIC, PART×3, KICK (%s %s :%s), JOIN (:%s), JOIN-burst (%s%s), MODE×5 (incl. the+%s%c %s %sop-grant viacrate::send::Chr); s_misc.rs: the anonymous PART (:%s PART %s :None); send.rs: thesendto_flagwrapper now calls the Rust core directly (body_cstrstill truncates at NUL to match the old%s). The local variadicextern fn sendto_channel_butservdecls in channel.rs + s_misc.rs replaced withuse crate::send::sendto_channel_butserv; the stale bindgen import dropped from send.rs. - L1 —
ircd-testkit/tests/sendto_channel_butserv_diff.rsbuilds a channel with aclistof members (a localfrom, a local co-member, a remote client, a server) in two parallel worlds and diffs each member'ssendQagainstcref_sendto_channel_butserv. 4 cases: (1) basic —fromself-send + co-member receive collapsed, remote+server skipped (the "butserv" inverse); (2) quiet —fromself-sends then the early return suppresses the loop; (3) anonymous —lfrm=&anonso the co-member gets:anonymous!anonymous@anonymous.while the self-send keeps the real prefix (both worlds'anonpopulated viainitanonymous/cref_initanonymous— theiranonstatics are separate, and the path needsIsPerson(&anon)); (4) remotefrom— no self-send, co-member still receives. All 4 pass;sendto_flag_diff(4) still green. - L2 — 14 golden binaries byte-identical: channel join/part/kick/topic/mode_set/modes +
s2s join/topic/kick/mode/njoin — confirming the 14 call-site conversions moved no wire
output. (No dedicated notice-channel L2:
&-server-channels aren't created in the harness.)
- Unblock / cluster choice — P8j made
-
2026-06-07 — P8l (
sendto_onekeystone) merged. The last broadcast/notice variadic sender.sendto_one(to, pattern, ...)was the single-client sender behind ~every numeric reply; ~437 textual hits → 435 real call sites across 15 ircd-common files.- Core. Non-variadic
pub unsafe fn send::sendto_one(to: *mut aClient, body: &[u8]) -> c_int=vsendprep(the libc-vsprintf≤510 truncation + CRLF into a local[u8;520])send_message, returning the prepped length the fewrlen +=callers read. The Rust fn is a plain (mangled) module fn, so it coexisted with the still-defined Csendto_onesymbol throughout the per-file conversion — the tree stayed green after every file, and only the final#ifndef PORT_SEND_ONE_P8lguard-flip removed the C trampoline.
- Call-site mechanism. Two builders. (1)
wire!forc"..."literal patterns whose specifiers are all in the P8j WirePart vocab (%s %d %u %c %x %X %08x %#x %p %02d %2d) — 58 sites. (2) A newreply_one!(to, fmt, args...)macro (send.rs) forreply(...)and runtime-char*patterns — 377 sites: it rendersfmt+args through libcsnprintfinto a 2048-byte scratch buffer (oversized so its own cap never bites before the core's 510), then hands the C string to the core. Byte-identical by construction —vsendprepused libcvsprintf, the same engine, so any printf specifier (incl.%lu/%ld/%lld/width/ precision) and NULL%s((null)) match exactly. The 2rlen += sendto_one(...)accumulator sites (channel.rs ~1556/1583) call the core directly via an inline snprintf-into-__rbufblock (the macro returns()). - Faithfulness finding. PLAN/[[p8-started]] marked sendto_one "blocked on leveva
typed-numerics (P10)" on the premise "most sites use a runtime
reply(i)fmt." That conflated two cases: only ~6 sites use a genuinely-runtimereply(var)(reply(num)channel.rs:4752,reply(cj as u32):2754,reply(rpl)/reply(tmp_rpl)/reply(tmp_rpl2),reply(REPORT_ARRAY[idx][1])s_serv.rs:2580). The other ~430reply(ERR_CONST)/literal sites have a compile-time-known format. And because the delivery path formats via libcvsprintf(NOT the customirc_vsprintf), thereply_one!/snprintf bridge is faithful for ALL of them — so sendto_one was rippable now without the P10 numeric layer (the idiomatic leveva-Numericconversion of the reply sites stays P11). - Conversion. A 15-agent ultracode workflow (one agent per ircd-common file, edit-only
to avoid the shared-build-dir race; central verify after) converted all sites + removed
each file's
extern "C" { fn sendto_one(...,...); }decl (and thesendto_oneentry from class.rs's bindgenuse).ircd-commoncompiled clean first try. - Guard.
sendbuf(send.c:31),vsendprep(385),vsendto_one+sendto_one(450-468) wrapped in#ifndef PORT_SEND_ONE_P8l;-DPORT_SEND_ONE_P8ladded to the send_link.o recipe in build.rs. The othersendbuf/vsendprepuses (send.c:501-946) are inside already-P8b…P8k-guarded senders, so the cut is clean. cref oracle keepssendto_oneunguarded for the L1 diff. - L1. New
sendto_one_diff.rs: Rust core (+wire!/reply_one!) vscref_sendto_oneon parallel fresh clients, sendQ byte-diff; 5 cases (wire! literal, reply_one!%stemplate,%d/%02dnumerics, NULL%s, >510 trunc). A GLOBALS_LOCK serializes — the oracle'scref_sendbuf+ the shared dbuf free-list are process globals (the first run failed with cref content bleeding between parallel #[test]s; the [[p7-l1-shared-global-race]] pattern). - L2. Full 111-test golden suite byte-identical; the only 2 failures are the documented
[[p5-s2s-stats-flake]] pair (
golden_s2s_s_serv_statssq,golden_s_miscSq/Yg/Fl) — uninitialized per-process counter garbage, identical code path to before, not sendto_one output (every other numeric line in those very tests matches byte-for-byte).
- Core. Non-variadic
-
2026-06-08 — P8m (the
esendto_*cluster, ircd/s_send.c) merged. The last variadic SENDER trampolines — the UID-aware "extended" service-routing senders — ripped out into a faithful non-variadic Rust cluster.- Cluster choice. s_send.c was the only TU still defining variadic sender trampolines
(
esendto_one/esendto_serv_butone/esendto_channel_butone/esendto_match_servs) after P8a–P8l drained send.c. Picked as the next (and last) sender rip-out per the user's "keep ripping out the variadic trampolines" directive. - Classification / config. USE_SERVICES is OFF →
nm cbuild/s_send.oshows the 4 public senders + the file-statics (esend_message/build_old_prefix/build_new_prefix/build_suffix), andgrepconfirms ZERO callers in both the remaining C and ircd-common/src. So: no call-site conversion, no L2/L2-S2S path (services never run on the wire) — L1 is the gate. build_prefix (s_send.c:147-200) is#if 0'd → not compiled, not ported. - Port mechanism. New ircd-common/src/s_send.rs (
pub mod s_send;+ S_SEND_LINK_ANCHOR in link_anchor()). The four buffers (oldprefixbuf/newprefixbuf/prefixbuf/suffixbuf[2048]) + six length ints (oldplen/newplen/plen/slen/maxplen/lastmax) are module-privatestatic mut(not ABI; addr_of_mut! access); CLEAR_LENGTHS is a helper run per public call. The variadicfmt, ...collapses tosuffix: &[u8]= the bytes vsprintf produced; build_suffix stores suffix + CRLF + the trailing'0'sentinel (faithful; length-bounded so never on the wire). Each public esendto_X is a plainpub unsafe fn(NOT extern "C": the&[u8]body param is not FFI-safe and there are zero C callers — matches the P8b..P8l body-taking-sender convention). Delivery via crate::send::send_message. - Faithfulness calls. (1) The
:%s %s %sprefix builders (build_old_prefix/ build_new_prefix) and the hardcoded:anonymous!anonymous@anonymous. %s %slocal-member prefix defer to libc::sprintf with the exact C format strings rather thanwire!— strictly more faithful: it reproduces glibc's(null)rendering for the NULLonamebuild_new_prefix can pass whendname != NULL, the same libc-for-format-semantics precedent as P8j (%#x/%p) and P8l (reply_one!/snprintf). (2) The loop-index decrement is placed to preserve the Cfor(...; i--)semantics undercontinue: esendto_match_servs decrements right after fetchingcptr(its body hascontinues), esendto_serv_butone at loop end (its body is a singleif). The maxplen/lastmax +maxplen+slen>512 → slen=510-maxplensuffix truncation, the UID selection +newplen=-1bail, and the&-channel return + get_channelmask + match() mask filter + BadPtr guard are all replicated exactly. - build.rs. "s_send.o" added to PORTED (dropped outright — no s_send_link.o, no -DPORT_*
guard); it stays in CREF_OBJS so the cref oracle keeps cref_esendto_* for L1. Verified
ar t libircd_c.ano longer lists s_send.o while libcref.a still exports the cref_ symbols. - L1. New ircd-testkit/tests/s_send_diff.rs (template = sendto_serv_butone_diff.rs + sendto_channel_butone_diff.rs): each cref_esendto_X(..., c"%s", payload) variadic oracle vs the Rust core on parallel local[]/fdas + channel worlds, byte-comparing every recipient sendQ via dbuf_map; GLOBALS_LOCK serializes the cref's process-global statics. 9 cases cover the keystone branches AND inverses (new vs old prefix, the newplen=-1 bail, one-exclusion + IsMe skip, the anonymous-vs-server prefix split + orig/one skips, the &-channel return + mask match()/non-match, and the >512 suffix truncation). 9/9 zero-diff.
- Gate.
cargo build -p ircd-rs0 warnings; s_send_diff 9/9; golden_registration 2/2 byte-identical (confirms dropping s_send.o didn't disturb the link/wire). No L2/S2S (services unreachable under the locked config — documented above). Verified by a 5-agent ultracode workflow: 1 implementer + 3 adversarial faithfulness auditors (all returned "faithful", zero findings) + 1 golden smoke. - Status. With the esendto_* cluster gone, every variadic sender trampoline in the tree
is now Rust. The remaining C is non-sender: data globals (ircd.c
me/timers, s_bsd.clocal[]/timeofday/highest_fd, s_auth.ciauth_*), the support remnants (snprintf_append/dgets/make_isupport/ipv6string/minus_one), and send_link.o's lonercsid. Next P8 work is porting those data globals + support remnants (then dropping all cc::Build, mothballing the L1/L2/cref oracle, migrating tests into ircd-common).
- Cluster choice. s_send.c was the only TU still defining variadic sender trampolines
(
-
2026-06-08 — P8n (s_auth.c iauth data globals) merged. The first P8 data-global drop — all variadic sender trampolines were already Rust (P8a–P8m), so P8 now turns to the residual C data globals.
s_auth.ois the cleanest:nm --defined-only s_auth_link.oshowed it defines ONLY the five iauth globals (+ module-privatercsid), every function already-DPORT_S_AUTH_*-guarded out.- Cluster / scope — the five s_auth.c file-scope globals:
iauth_options(u_char),iauth_spawn(u_int),iauth_version(char*),iauth_conf/iauth_stats(aExtCf*/aExtData*= bindgenLineItem*). All written/read by the now-Rustread_iauth+ the tworeport_iauth_*reporters (this file);iauth_options/iauth_spawnadditionally read by the iauth-timeout checks in ircd.rs/s_user.rs/s_bsd.rs. - Mechanism — defined them as
#[no_mangle] pub static mutinircd-common/src/s_auth.rs, zero/NULL-initialized exactly as the C BSS defs. Removed the two now-redundant localextern "C"decls (theiauth_conf/iauth_statsblock + theiauth_versionblock) and droppediauth_optionsfrom theuse ircd_sys::bindings::{…}import (it would otherwise collide with the new module def); repointed the loneircd_sys::bindings::iauth_spawnref to the local def. The OTHER modules keep theirircd_sys::bindings::iauth_options/iauth_spawnimports untouched — those are bindgenextern "C" { static mut … }declarations that resolve to the Rust definitions at link, the same data-symbol seam P8g used foranon/ausrand P8j forsvchans. - build.rs —
s_auth.oadded toPORTED(solink_objs()filters it out before the… => "s_auth_link.o"map, which was removed as dead); the wholes_auth_link.osecond-compile step (the-DPORT_S_AUTH_REPORT_P7u … -DPORT_S_AUTH_SENDTO_IAUTH_P8achain) deleted.s_auth.ostays inCREF_OBJS(the full unguarded compile) so thecref_*oracle symbols survive for L1. - No new test / no L2 path beyond boot — pure data globals have no behavioral L1 diff of their
own; the gate is the existing iauth differentials that exercise the globals end-to-end.
read_iauth_diffis load-bearing here: it drives the Rustread_iauth(which mutates all five) againstcref_read_iauthand byte-diffs the resulting conf/stats lists + client state, so a mis-wired global would fail it. - Gate —
nm target/debug/ircdshows all five as Rust BSS defs, none undefined. L1:read_iauth_diff(10) +iauth_reporters_diff(6) +sendto_iauth_diff(2) +start_iauth_diff(3) all zero-diff. L2:golden_registration(2) byte-identical.cargo build --workspace0 warnings. - Remaining P8 C —
ircd_link.o(me+ the dorehash/timers/CLI data globals;mehas the interior self-pointer hazard),s_bsd_link.o(local[]/highest_fd/timeofday/fd globals),support_link.o(ipv6string/minus_onedata +dgets/make_isupportfns + the variadicsnprintf_append, the last entangled with the P11irc_sprintfdeletion), andsend_link.o's lone module-privatercsid(trivially droppable). Then: drop allcc::Build, mothball the oracle, migrate tests.
- Cluster / scope — the five s_auth.c file-scope globals:
-
2026-06-08 — P8o (
send.odropped outright) merged. The second P8 data-global drop, and the trivial one flagged at P8n's close.- Scope —
send.cis now FULLY ported: over P3d (delivery core) + P8b..P8l every sender —sendto_serv_butone/sendto_ops_butone/sendto_serv_v/sendto_match_servs[_v]/sendto_prefix_one/sendto_match_butone/sendto_channel_butone/sendto_common_channels/sendto_channel_butserv/sendto_flag/sendto_one— plusdead_link, thesendto_floglogfile cluster, thesvchans/setup_svchanscomponent, and theanon/ausr/initanonymousmachinery were#ifdef'd out ofsend_link.oand reimplemented inircd-common/src/send.rs. - Why droppable now —
nm --defined-only cbuild/send_link.oshowed exactly one symbol:rcsid(lowercaser= local, internal-linkagestatic const char[]version string, referenced by nothing). Every livesend.csymbol comes from Rust. Sosend_link.ocontributed nothing to the link except a dead static. - Mechanism — added
"send.o"toPORTEDinircd-sys/build.rs(solink_objs()filters it out); deleted the"send.o" => "send_link.o"arm from the link-mapmatch; deleted the entiresend_link.osecond-compilerun(make … --eval=send_link.o: …)step plus its long-DPORT_SEND_P3 … -DPORT_SEND_ONE_P8lcomment block, replacing it with a short P8o note. cref keeps the unguardedsend.oinCREF_OBJS(native Makefile recipe, FNAME_* macros expanded) so thecref_*send oracle survives for L1. - FNAME paths — the Rust
logfiles_open(ported P8i) readsFNAME_USERLOG/FNAME_CONNLOGvia theIRCD_USERLOG_PATH/IRCD_CONNLOG_PATHrustc-envs (set elsewhere in build.rs), so dropping the send_link.o compile (which previously also carried the FNAME_*-Ds) changes nothing. - Classification — pure drop, no new Rust and no behavioral L1 diff of its own (send_link.o was already inert at link). Gate = the existing send differentials + a golden smoke.
- L1 — all 14 send differentials zero-diff (57 tests):
send_diff(9),sendto_one_diff(5),sendto_flag_diff(4),sendto_channel_butone_diff(4),sendto_channel_butserv_diff(4),sendto_serv_butone_diff(4),sendto_serv_v_diff(3),sendto_match_servs_diff(5),sendto_match_butone_diff(4),sendto_prefix_one_diff(4),sendto_common_channels_diff(4),sendto_flog_diff(2),sendto_iauth_diff(2),s_send_diff(9). - L2 —
golden_registration(2) byte-identical (exercises the full boot send path). - Verify —
nm target/debug/ircdshowslogfiles_open/svchansas Rust defs and no undefined send symbols (sendto_one/sendto_flagare now plain Rustfns, not C-ABI symbols).cargo build(full workspace) 0 warnings. - Remaining P8 C —
ircd_link.o(me+ the dorehash/timers/CLI data globals;mehas the interior self-pointer hazard),s_bsd_link.o(local[]/highest_fd/timeofday/fd globals),support_link.o(ipv6string/minus_onedata +dgets/make_isupportfns + the variadicsnprintf_append, the last entangled with the P11irc_sprintfdeletion). Then: drop allcc::Build, mothball the oracle, migrate tests.
- Scope —
-
2026-06-08 — P8p (
s_bsd.odropped outright) merged. The third P8 data-global drop, thes_bsd.cevent-loop TU.- Cluster choice / scope —
s_bsd.chad all its logic ported over P7d..P7ff (the select/poll event loop, listeners, add_connection/connect_server, start_iauth/daemonize, the socket/file leaves).nm --defined-only cbuild/s_bsd_link.othen showed it defined ONLY data globals:local(theaClient *local[MAXCONNECTIONS]fd→client slab),fdas/fdall(FdAry poll/select sets),highest_fd,readcalls,udpfd/resfd/adfd(-1-init fds),timeofday(the cached wall clock),mysk(the outgoing-bind source addr, de-static'd P7s) + the module-privatercsid. So the TU is a pure data-global drop, the same shape as P8n (s_auth.o) and P8o (send.o). - The defs — moved all ten to
ircd-common/src/s_bsd.rsas#[no_mangle] pub static mutwith byte-exact C initializers (s_bsd.c:51-55):local = [null; MAXCONNECTIONS=50];fdas/fdall = FdAry{fd:[0;50],highest:0};highest_fd/readcalls = 0;udpfd/resfd/adfd = -1;timeofday = 0;mysk = MaybeUninit::<sockaddr_in6>::zeroed().nmconfirmsadfd/resfd/udpfdland in.data(D, =-1) and the rest in BSS (B) — matching the C object. - Data-symbol seam — the other modules (send/parse/s_user/s_auth/s_service/s_send) keep their
ircd_sys::bindings::{local,highest_fd,fdas,fdall,timeofday,…}bindgenextern static mutrefs; those decls resolve to these Rust defs at link (the data case of the function-symbol seam, proven foranon/ausrin P8g,svchansin P8j, the iauth globals in P8n). s_bsd.rs's own full-pathircd_sys::bindings::X/b::Xreads resolve the same way — no in-module name clash because they are distinct paths from the new module statics. - Two in-module clears — (1) dropped the old
extern "C" { static mut mysk: libc::sockaddr_in6 }decl inget_my_name's block (now the module static); (2) renamedread_message's local varlet local = local_base()→local_p(E0530: aletcannot shadow the new static); also prunedadfd/highest_fdfromstart_iauth's function-localuse ircd_sys::bindings::{…}so it reads the module statics directly. - build.rs —
s_bsd.oadded toPORTED; deleted the wholes_bsd_link.osecond-compile (themake --evalrule carrying the long-DPORT_S_BSD_LEAF_P7d .. -DPORT_S_BSD_READ_MESSAGE_P7ffchain + the IRCDPID/IAUTH path defines) and the"s_bsd.o" => "s_bsd_link.o"arm inlink_objs(). The Rustwrite_pidfile/start_iauthstill read IRCDPID_PATH/IAUTH_PATH/IAUTH via the existingIRCD_PID_PATH/IRCD_IAUTH_PATH/IRCD_IAUTHrustc-envs. cref keeps the unguardeds_bsd.o(still in CREF_OBJS) socref_local/cref_highest_fd/cref_mysk/… survive for L1. - Gate (pure-data drop) — no behavioral L1 diff of the globals themselves; leaned on the
existing differentials that drive them end-to-end:
read_message_diff(event loop readslocal[]/fdas/highest_fd/timeofday),add_connection_diff(writeslocal[]/highest_fd/fdas/fdall),close_connection_diff/close_listeners_diff,list_diff(add_fd/del_fd mutatefdas/fdall),s_bsd_leaves_diff,connect_server_diff,check_client_diff,setup_ping_diff,send_ping_diff,check_pings_diff,get_my_name_diff(writesmyskvscref_mysk),init_sys_diff,daemonize_diff,start_iauth_diff,delayed_kills_diff,calculate_preference_diff— all zero-diff. Cross-module wire seam checked viasendto_one_diff/sendto_flag_diff/sendto_common_channels_diff(they readlocalthrough the seam). Boot path viagolden_registration(full event loop over the globals) byte-identical.nm target/debug/ircd: all 10 globals resolve as Rust B/D defs, noneU.cargo build0 warnings.
- Cluster choice / scope —
-
2026-06-08 — P8q (
ircd.odropped outright — theircd.cdata globals) merged. The fourth P8 pure-data-global drop, completing theircd.cmigration begun at P7c.- Cluster choice / why now. Over P7c..P7oo every function in
ircd.cwas ported toircd-common/src/ircd.rsbehind the-DPORT_IRCD_*guard chain.nm --defined-only cbuild/ircd_link.othen showed noTsymbols — only data globals + the module-privatercsid— proving the logic is fully drained. This is the same terminal state that triggered P8n (s_auth.o), P8o (send.o), P8p (s_bsd.o): a*_link.othat defines only data, ready to be dropped outright by moving those data globals to Rust statics. - The globals (ircd.c:31-96).
me(aClient),client(= &me),istat/iconf,myargv/sbrk0/ListenerLL(NULL),rehashed/firstrejoindone(0),serverbooting(1),portnum/debuglevel(-1),bootopt(BOOT_PROT|BOOT_STRICTPROT = 768),configfile/tunefile(IRCDCONF_PATH/IRCDTUNE_PATH),dorehash/dorestart/restart_iauth(volatile int = 0), and the event timersnextconnect/nextgarbage/nextping/nextexpire/nextiarestart/nextpreference(= 1) +nextdnscheck/nexttkexpire/nextdelayclose(= 0). - The
meself-pointer hazard — a non-issue at definition.aClient me;is a BSS all-zero def;client = &me. The interior self-pointers (me.name → me.serv->namebuf) are set at runtime by the already-portedmake_server/setup_me, NOT at definition. So the Rust static is justMaybeUninit::<aClient>::zeroed().assume_init()(the same const-zeroed pattern P8p used formysk), andclientinitializes tocore::ptr::addr_of_mut!(me)in const. The "never move/copymeby value" invariant is already honored tree-wide (addr_of_mut!(me)/ircd_sys::bindings::meeverywhere), so nothing else changes. - DATA-symbol seam. The bindgen-surfaced globals (
me/client/istat/iconf/myargv/rehashed/portnum/configfile/debuglevel/bootopt/serverbooting/firstrejoindone/sbrk0/tunefile/ListenerLL+ thenext*timers) are referenced by the other modules (s_bsd/s_user/…) through theirircd_sys::bindings::*extern static mutdecls, which resolve to these#[no_mangle]defs at link — the proven seam (local/anon/svchans/the iauth globals). No source changes outside ircd.rs for those. - The non-bindgen five.
dorehash/dorestart/restart_iauth(signal flags, de-static'd P7kk) andnextpreference/nextiarestart(io_loop timers) are absent from any*_ext.h, so they were reached via two localextern "C"blocks in ircd.rs (one at the signal-handler cluster, one insideio_loop). Both blocks removed; the symbols are now module statics and the bare-name references inside ircd.rs resolve to them directly. - Import clash. ircd.rs both imported these globals from bindgen (for its own use) and
now defines them, so the three
use ircd_sys::bindings::{…}lists were trimmed of the now-defined names:bootopt/iconf/istat/me/myargv/rehashed/sbrk0/tunefile(block @20), the sixnext*timers (block @34), andconfigfile/serverbooting(the innerc_ircd_mainblock). - Path defaults.
configfile = IRCDCONF_PATH/tunefile = IRCDTUNE_PATHare recursively-expanded Makefile path vars (absent from bindgen). build.rs now expands them viamake --eval(theIRCD_MOTD_PATH/IRCD_PID_PATH/IRCD_SERVER_PATHpattern) and emitsIRCD_CONF_PATH/IRCD_TUNE_PATHrustc-envs; ircd-sys re-exports NUL-terminatedCONF_PATH_Z/TUNE_PATH_Zconsts (theconcat!+env!must run in ircd-sys), and the Rust*mut c_charstatics init from.as_ptr(). The C never writes through these pointers (only reassigns on-f/-T), so pointing at a'staticbyte string is faithful. Verified:strings target/debug/ircdshows/usr/local/etc/ircd.conf+/usr/local/var/run/ircd.tune. - build.rs.
ircd.oadded toPORTED; theircd_link.omakesecond-compile (the long-DPORT_IRCD_TUNE_P7c .. -DPORT_IRCD_MAIN_P7oochain + the-DIRCD*_PATHmachine paths) and the"ircd.o" => "ircd_link.o"link-map arm deleted. The cref oracle keeps the unguardedircd.o(still in CREF_OBJS, native Makefile recipe) socref_me/cref_try_connections/cref_dorehash/… survive for the L1 differentials. - Gate (pure-data drop, per P8n/o/p — no new test; the existing differentials are the
gate). The ircd L1 differentials that exercise these globals end-to-end —
try_connections_diff(8),check_pings_diff(5),calculate_preference_diff(5),delayed_kills_diff(6),ircd_signals_diff(5, writesdorehash/dorestart/restart_iauth),ircd_tune_diff(9),ircd_cli_helpers_diff(3),setup_signals_diff(1),activate_delayed_listeners_diff(2) — all zero-diff vscref_;golden_registration(2) byte-identical (boots the Rust ircd, which now ownsme/the timers/configfile/tunefile).nm target/debug/ircdshows all ~26 globals resolving as Rust defs (B/D, noneU) with the correct.data(non-zero inits) /.bss(zero inits) split; the crefircd.ostill carries 49 symbols (oracle intact).cargo build --workspace0 warnings. - Remaining C. Only
support_link.okeeps C logic — the format remnantsdgets/make_isupport/snprintf_append+ the dataipv6string/minus_one(and the P1-deferredirc_sprintfengine, to be deleted not ported in P11). Every other TU is now Rust or pure-data-dropped.
- Cluster choice / why now. Over P7c..P7oo every function in
-
2026-06-08 — P8r (
support.odropped outright — the LAST C-logic TU) merged. The final P8 TU drop. After P8n–q drained the data-global.os,support_link.owas the only remaining C translation unit defining logic; this drops it.- Cluster choice / why now.
nm --defined-only support_link.oshowed exactly:dgets/make_isupport(T),snprintf_append(T, variadic),ipv6string(B),minus_one(D), + the module-privatercsid/dgbuf.0. The pure subset (mystrdup/strtoken/myctime/inetntop/… + theMyMalloctrio) was already Rust from P1c/P2e behind-DPORT_SUPPORT_P1 -DPORT_SUPPORT_P2. So three real ports + two data globals remained. snprintf_append— dead in Rust, not ported. Its only ircd caller wass_user.c's WHOX builder, which the Rust port (s_user.rs:387) already replaced with a field-by-fieldVec<u8>builder.grepconfirms the sole Rust reference is a comment → no live caller → it simply died with the.o(no trampoline needed). Stable Rust can't define a variadicextern "C"anyway; this sidesteps that entirely. (irc_sprintf, the deferred varargs engine, is likewise deleted-not-ported, in P11.)dgetsport. Faithful translation of the\r/\n-line reader with\-continued- newline splicing (support.c:702-808). C usesstatic char dgbuf[8192]+head/tailpointers; ported with head/tail as byte offsets into a module-privatestatic mut DGBUF(behaviour-identical — the statics are private todgets), pulled into locals for the body and written back before every return.index()→a scan-to-NUL helper,bcopy()→ptr::copy,read()→libc::read. The subtle bit: the odd-backslash de-escape loop advancess/tthrough the*s++ = *t++copy and then doest = s, so the scan resumes from the post- splice position (end) exactly as C does — replicated verbatim (separate copy cursors would have diverged on multi-continuation buffers).make_isupportport. TwoMyMalloc(BUFSIZE)token strings + a 3-slotMyMallocpointer array (support.c:810-840,#ifndef CLIENT_COMPILEalways active in the daemon). Thesprintfformat args are config macros resolved to the locked config (common/*.h, not surfaced by bindgen):MAXMODEPARAMS=3 MAXCHANNELSPERUSER=21 LOCALNICKLEN=15 TOPICLEN=255 MAXBANS=64 CHANNELLEN=50 CHIDLEN=5,BUFSIZE=512— baked as named consts.tis[1]is built byte-faithfully (rawcopy_nonoverlappingof the base + optionalNETWORK=+ the bindgennetworknamebytes) to avoid any UTF-8 reinterpretation ofnetworkname.- Data globals via the seam.
ipv6string: [c_char;46] = [0;46](BSS) andminus_one: [c_uchar;17] = [255;16]+[0](.data) defined#[no_mangle] pub static mut. The cross-module reads (res/s_bsd/s_conf/s_serv/s_auth/send/s_misc/s_user) reach them through their bindgenextern static mutdecls — the proven DATA-symbol seam (local/me/svchans/iauth). bindgen surfacesminus_oneas[c_uchar;0](incomplete array) — the size mismatch is irrelevant (callers take the address + copy 16 bytes); the linker resolves by name. No source changes outsidesupport.rs. - build.rs.
support.oadded toPORTED; thesupport_link.osecond-compile and the"support.o" => "support_link.o"arm inlink_objs()deleted —link_objs()is now a plainCREF_OBJS \ PORTEDset difference (no*_link.ovariants remain anywhere). cref keeps the unguardedsupport.oinCREF_OBJSsocref_dgets/cref_make_isupport/cref_mystrdup/… survive for the L1 differentials. - Milestone — every ircd C TU is now Rust. With
support.odropped,CREF_OBJS \ PORTEDis empty: the product link set (libircd_c.a) holds only the generatedversion.o(a version-string constant) + the L1 harness'sctruth.o. All ircd logic and data is Rust. Remaining P8 work: drop allcc::Build(including regeneratingversion.cas Rust), mothball the L1/L2 oracle after a final green run, and migrate the load-bearing tests intoircd-common. - Gate. New L1 differentials in
support_diff.rs:dgets_matches_referencedrives bothdgets/cref_dgetsover independent pipes carrying plain\nlines, an odd-backslash continuation (spliced), an even-backslash run (newline kept), a\rbyte, an EOF tail with no trailing newline, and empty input — asserting the identical (ret, bytes) stream incl. thedgets(_,_,0)reset;make_isupport_matches_referencewalks thechar**to NULL and asserts identical token strings (networkname NULL → no NETWORK= token). Both zero-diff; fullsupport_diff(7 tests) zero-diff.golden_registration(2) byte-identical — the 005 RPL_ISUPPORT numeric flows throughmake_isupport. Seam reads checked vias_conf_match_ipmask_diff(5) +res_gethost_diff(8) zero-diff.nm target/debug/ircd:dgets/make_isupportresolveT,ipv6stringB,minus_oneD(noneU),snprintf_appendabsent.cargo build --workspace0 warnings.
- Cluster choice / why now.
-
2026-06-08 — P8s (L1 test migration to ircd-common) merged. Migrated all 115 of ircd-testkit's
cref_-oracle L1 differential tests into self-containedircd-common/tests/<name>_snap.rsinsta-snapshot characterization tests — the PLAN's "migrate the load-bearing tests into ircd-common" step that unblocks mothballing the oracle.- Mechanism. Each new test drives ONLY the Rust port: original-name
extern "C"decls resolve to ircd-common's#[no_mangle]defs, pulled onto the test binary's link line byircd_common::link_anchor()(ircd-sys's whole-archiveversion.o/ctruth.ocome transitively, so bindgen types still resolve). Allcref_*externs andircd_testkit::link_reference_archives()deleted.insta = "1"added as a dev-dependency. - Soundness (capture chain). Step 0 of every conversion confirms the matching
ircd-testkit --test <name>_diffis green at capture time (Rust == cref). Combined with the freshly-capturedsnapshot == Rust, that givessnapshot == cref golden— so the frozen.snapcarries the reference-C correctness forward with no C oracle in the loop. These are now regression/characterization gates for the P9–P12 idiomatic passes. - Dual-World → single-world. The complex tests (list/hash/res_/s_conf_/channel_/ the sendto_ senders) built two parallel worlds (cref + Rust) and asserted field-equality. Collapsed to a single Rust world snapshotting the observable state over the SAME corpus + branches + inverses (no coverage shrink).
- Determinism. Never snapshot raw pointers/addresses/timestamps/fd-numbers/ephemeral
ports/uninitialized memory/HashMap order. Self/interior-pointer assertions
(
name==namebuf,from==self, list linkage) became derived invariants (bools, offsets, name/id walk-sequences, buffer bytes). Clock interposition (gettimeofdayshims) and process-globalMutexserialization were preserved from the originals. Verified by two clean re-runs per file + three full-suite re-runs (stably green). - Process. Hand-proved the pipeline on
s_err(table snapshot) +s_id(pure-fn tuples), then a 6-file pilot spanning every tier (s_user_canonize/dbuf/patricia/list/res_hash/channel_modes), then a multi-agent Workflow over the remaining 107. The first 107-wide fan-out tripped the org token-rate limit (64 agents 429'd); the 65 affected were redone in throttled sequential batches of 8 with zero failures. Conversion guide:docs/superpowers/plans/2026-06-08-p8-test-migration-guide.md. - Cleanup. Removed run-1 misnamed
*_diff_snap.rsduplicates + their orphan snapshots; silenceddead_codeon the snapshot-shape structs (fields read only via#[derive(Debug)]) with a file-level#![allow(dead_code)]in the 34 affected files; dropped one unused import. - Gate. 115
_snap.rstest files / 707 committed.snapfixtures, exact 1:1 coverage (no missing/extra/orphan),ircd-commonsuite green and stable across repeated runs,cargo build --workspace0 warnings.ircd-testkit+ thecref_/ircd-goldenoracle are left intact for now (still the differential proof); dropping allcc::Build(incl. regeneratingversion.cas Rust) and mothballing the oracle is the remaining P8 work.
- Mechanism. Each new test drives ONLY the Rust port: original-name
-
2026-06-08 — P8t (version.c → Rust) merged. Ported
ircd/version.c— the last generated C translation unit — toircd-common/src/version.rs, then deleted its generation + compile fromircd-sys/build.rs.- Scope / classification.
version.cis pure data (nm version.o= onlyD/B/r, noT):char *generation,char *creation,char *pass_version,char *infotext[],char **isupport, + the module-privatercsidstatic. It is generated byircd/version.c.SH(bumpsgeneration, embeds adatecreation, expandsPATCHLEVEL- source-file checksums). Not in
CREF_OBJS—version.owas archived straight into the productlibircd_c.a, so there is nocref_*oracle for it and no L1 differential.
- source-file checksums). Not in
- Mechanism (data-symbol seam, as P8n–P8q). Defined the five globals as
#[no_mangle] pub static mutinversion.rs+ aVERSION_LINK_ANCHOR(added tolib.rs::link_anchor()). The consuming modules (s_servm_infowalksinfotextand printscreation/generation;s_userRPL_CREATED;s_debug/s_bsdreadisupport/pass_version) reach them through their existingircd_sys::bindings::*extern static mutdecls — which come froms_externs.h, independent of which.ocompiles — so they resolve to the Rust defs at link.infotext's bindgen decl is an incomplete array[*mut c_char; 0]; the real length (46, NULL-terminated) lives only in the Rust def, walked viaaddr_of!(infotext)(thelocal[]/tolowertab[]pattern). - Version sourcing (user-directed): crate metadata at build time.
generation=concat!(env!("CARGO_PKG_VERSION"), "\0");creation=env!("IRCD_BUILD_CREATION"), emitted by a newircd-common/build.rsthat formatsSystemTime::now()into the C-likeWkd Mon D YYYY at HH:MM:SS UTCshape (dependency-free Hinnant civil-from-days; UTC to avoid host-TZ nondeterminism; pinned via a lonererun-if-changed=build.rsso it only re-stamps on clean/build.rs-edited builds).pass_version= the bindgenPATCHLEVELconst (b"0211030000\0");infotext[]copied byte-for-byte from the generated C (including the trailing C source-checksum lines).isupport= NULL (runtime-filled bymake_isupport). - build.rs drop. Removed the
version.c.SHgeneration block, theversion.ocompile block, andar.arg("version.o")fromircd-sys/build.rs; updated theCREF_OBJSdoc comment.link_objs()was already empty (every hand-written TU ported), solibircd_c.anow contains only the L1 harnessctruth.o. - Canonicalizer.
creationis now genuinely build-time volatile (the reference-Cversion.ostamp vs the Rust crate-metadata stamp legitimately differ), so added a 003 RPL_CREATED masking rule toircd-golden'scanonicalize()(:This server was created <MASKED>), the exact analogue of the existing 371 Birth-Date rule that masks the samecreation/generationtokens in/INFO. No snapshot captures the value — the only.snapmentioning it (s_errreplies table) holds the static format template":%s 003 %s :This server was created %s", unaffected. - Gate.
cargo build --workspace0 warnings;nm target/debug/ircdshowsgeneration/creation/pass_version/infotextasDandisupportasB(Rust defs, noneU),version.oabsent from the archive (ar t=ctruth.oonly); string values confirmed (build stamp /0211030000/IRC --).ircd-commonsnapshot suite green (117 test-result groups). Golden suite 58 pass + the one known pre-existinggolden_s2s_s_serv_statsflake (reference-C uninitialized-sendq garbage:…3544950802210619392kB sqvs Rust0kB sq— STATS, not version; documented in the p5-s2s-stats-flake memory). No version-touching golden diverged. - No S2S path concern. Pure data globals;
m_info/RPL_CREATED already covered bygolden_s_serv_info_links/golden_registration(both green post-mask).
- Scope / classification.
-
2026-06-08 — P8u (FINAL: drop all C compilation + mothball the L1/L2 oracle) merged. P8 COMPLETE. With every ircd C translation unit already Rust (P8r dropped the last C-logic TU; P8t the last generated one), the only remaining P8 work was retiring the differential oracle that had proven each port. This step ran it one last time, then deleted the C build.
- Final differential run (the gate).
cargo test -p ircd-testkit(L1cref_*unit diffs) — exit 0, all pass.cargo test -p ircd-golden(L2 wire-output golden vs the reference-C binary) — 86 pass, 1 fail:golden_s2s_s_serv_stats::s2s_stats_match_reference, which is the documented pre-existing flake ([[p5-s2s-stats-flake]]): reference-C emits uninitialized-sendq garbage (…3544950540217614336kB sq) where the Rust port correctly emits0kB sq— i.e. Rust is the correct side, reference-C is wrong. Not a regression. This is the last time the reference-C oracle runs; its verdict is captured in git history. - Dropped all C compilation from
ircd-sys/build.rs. Removed: themake CREF_OBJSobject build; theRES_CACHE_EXPOSEcrefres.orecompile; thectruth.olayout-truth compile; thelibircd_c.aarchive (ar); the whole-archive link directive (static:+whole-archive=ircd_c) and-lz/-lm/-lcrypt; and thecargo:objs/cargo:cbuildmetadata. TheCREF_OBJS/PORTED/link_objs/EXTRA_CFLAGSconstants + themakefile_varhelper are gone. Link-lib finding: none ofz/m/cryptare needed by the Rust workspace — no Rust code callscrypt/zlib, andpowresolves from libc (pow@GLIBC_2.29; glibc merged libm in 2.29), so the pure-Rust binary links clean without them. - What
build.rsstill does (no C compiled). Two things still read the C source tree: (1) bindgen overwrapper.h—ircd-commonstill uses the bindgen struct/type view of the ABI (aClient/dbuf/Message/…), replaced with native Rust types only in P9–P12; this parses the unmodified*_def.h/*_ext.hheaders, soconfigureis still run purely to emitsetup.h/config.h(read via-I cbuild). (2) The install-path exports — a newmake_var()helper expands the recursively-evaluated Makefile path vars (IRCDCONF_PATH/IRCDMOTD_PATH/FNAME_USERLOG/IAUTH_PATH/…) that embed machine-specific install locations the Rust runtime still defaults to.make --evaldoes var expansion only — nothing is built. So the C source headers stay in-tree as bindgen input; the.cfiles are no longer compiled (the reference-C tree is "mothballed" in the build-the-oracle sense). - Mothballed the oracle crates.
ircd-testkit(L1) andircd-golden(L2) can no longer build (no reference C to compile / link), so both areexcluded from the workspacemembersin the rootCargo.toml— left on disk (git-recoverable), not deleted, with a note. Theircd-sys-local drift-net —ircd-sys/tests/layout.rs(asserted bindgen vs the real compile viactruth.o'scref_sizeof_*) and its C sourceircd-sys/ctruth.c— is deleted: with no C compiled there is nothing for bindgen's view to drift against. The load-bearing L1 characterization the oracle anchored lives on as the self-containedinstasnapshots inircd-common/tests/*_snap.rs(P8s), which carry the reference-C correctness forward with no C in the loop. - Doc updates.
ircd-sys/src/lib.rsmodule doc rewritten (no longer "compiles the C tree"); thec_ircd_mainextern decl's doc notes it now resolves toircd-common's#[no_mangle] c_ircd_main(P7oo) rather than the-Dmain=c_ircd_mainCmain. - Gate.
cargo metadatashowsircd-testkit/ircd-goldengone (exclude works);cargo build --workspace0 warnings, theircdbinary links pure Rust (boots to the usage banner;nmshowsc_ircd_mainasT, zerocref_/ctruthsymbols, noircd_clink directive in the current build-script output);cargo test --workspace698 pass / 0 fail across 125 test binaries. The workspace is now 100% Rust — zero C TUs compiled. P8 is COMPLETE; the migration's faithful mechanical port is done end-to-end. Next: P9 (std-library cleanup), verified against theircd-commonsnapshot suite.
- Final differential run (the gate).
-
2026-06-08 — P8v (C source tree deleted;
ircd-sysretired) merged. The literal end of C in the repo. P8u dropped all C compilation but leftircd-sysalive to runconfigure+ bindgen over the C headers (the struct viewircd-commonstill builds against). P8v removes that last dependency on C files and deletesircd-sysitself.- Froze the bindgen view. Captured the generated
bindings.rs(4953 lines, rust-bindgen 0.72.1 overos.h+s_defines.h+s_externs.h+irc_sprintf_ext.hunder the locked configure —USE_IAUTH/TKLINE/ENABLE_CIDR_LIMITSon,AFINET=AF_INET6,MAXCONNECTIONS=50) and committed it verbatim asircd-common/src/bindings.rswith a provenance header + the original#![allow(non_camel_case_types, …, clippy::all)]. It is self-contained (noinclude!/env!/ external-crate refs; theextern "C"blocks resolve at link to this crate's own#[no_mangle]defs — the seam is unchanged, just no longer regenerated). The idiomatic-Rust passes (P9–P12) replace these bindgen types with native types field by field; until then this is the frozen ABI. - Self-alias seam (zero src churn). Rather than rewrite
ircd_sys::bindings::*across all 28 ported modules,ircd-common/src/lib.rsgainedpub mod bindings;+extern crate self as ircd_sys;, so every existingircd_sys::bindings::X/ircd_sys::MOTD_PATHpath now resolves to the in-crate module / the frozen install-path consts. The 11 install-path consts (MOTD_PATH/PID_PATH/USERLOG_PATH/CONNLOG_PATH/IAUTH_PATH/IAUTH/SERVER_PATH/CONF_PATH/TUNE_PATH/CONF_PATH_Z/TUNE_PATH_Z) — formerlyenv!-injected byircd-sys'sbuild.rsviamake --eval— are frozen as literals at the locked./configuredefaults (/usr/local/...), since the Makefile they were expanded from is now deleted. - Deletions.
ircd-syscrate (build.rs/wrapper.h/src/lib.rs); the mothballed oracle cratesircd-testkit(L1) andircd-golden(L2);iauth-rs/tests/differential_c.rs(the C-iauth handshake differential — it only ever diffed against C-iauth, now gone; iauth-rs's per-module#[cfg(test)]units remain the gate); and the entire C source tree:common/,ircd/,iauth/,support/(Makefile.in + configure + config.h.dist),contrib/,cbuild/, the rootconfigurewrapper,.clang-format/.clang-format-ignore, and the now-purposeless.github/workflows/clangformat.yml(the repo's only CI, a C formatter). Dropped/cbuildfrom.gitignore.doc/is kept (reference docs, not C). - Rewiring.
ircd-rsdropped itsircd-sysdep and now callsircd_common::ircd::c_ircd_maindirectly (the P7oo Rust boot spine; the stale "still entirely C core" main.rs doc was corrected). Workspacememberslostircd-sys; theexcludelist (the two oracle crates) is gone with them. - Clash fixes. Putting the bindgen decls and the modules' local cross-module forward-decls in
one crate exposed 4
clashing_extern_declarations:unregister_server/del_from_hostname_hash_table/del_from_ip_hash_tablewere forward-declared-> ()in s_misc.rs but really returnc_int(s_serv.rs/hash.rs), andmystrdupwas forward-declared*const c_charin s_misc.rs + res.rs but really takes*mut c_char(support.rs). Aligned the 4 decls to the real signatures; the one affected call site (mystrdup(line.as_ptr())in the MOTD loader) gained anas *mut c_charcast. No behaviour change — same symbols, same link. - Gate.
cargo build --workspace0 warnings, theircdbinary links pure Rust; noircd_syscrate incargo metadata;grepfinds no C source files in the tree;cargo test --workspace695 pass / 0 fail (was 698 — the delta is the deletedircd-syssmoke test + the C-iauth differential's cases, both of which tested now-absent C). The repository is now 100% Rust at the file level — not one line of C remains. Next: P9 (std-library cleanup), verified against theircd-commonsnapshot suite.
- Froze the bindgen view. Captured the generated