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

Configure Feed

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

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: nm confirms 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 pipe write, 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's Message/identifier newtypes don't apply. Per the user decision, formatting is format! 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 for format!) in ircd-common/src/s_auth.rs. Faithful to vsendto_iauth: adfd < 0 → -1; `abuf = line
      • b"\n"; the write(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 via CStr::to_bytes`) carries no embedded NUL.
    • Call sites (24). s_auth.rs (9), s_user.rs (3), s_bsd.rs (8, incl. the former sendto_iauth_p7ff link_name alias — now use … as sendto_iauth_p7ff), ircd.rs (3), res.rs (1). %s-of-C-string args go through ia_str; whole-buffer passthroughs (start_auth <fd> C …, setup-auth <fd> O packing) pass CStr::from_ptr(buf).to_bytes(). The 7 variadic extern "C" { fn sendto_iauth/_p7ff } decls deleted.
    • C drop. #ifndef PORT_S_AUTH_SENDTO_IAUTH_P8a around both C functions; -D added to the s_auth_link.o compile in ircd-sys/build.rs. nm confirms s_auth_link.o no longer defines or references the symbols, the binary carries only the mangled Rust ircd_common::s_auth::sendto_iauth (no exported C sendto_iauth), and the cref oracle s_auth.o keeps cref_sendto_iauth/cref_vsendto_iauth for 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 oracle cref_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 the adfd < 0 inverse (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 fatal write).
    • No regression / no S2S. The iauth L1 suite (start_auth_diff — whose adfd>=0 handoff now drives the Rust core —, read_iauth_diff, authports_diff, iauth_reporters_diff, start_iauth_diff) + golden_registration stay green; cargo build 0 warnings. No S2S — formats no remote-user wire fields.
    • Plan: docs/superpowers/plans/2026-06-07-p8a-sendto_iauth.md.

  • 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 — loop local[fdas.fd[i]] over directly-linked servers, skip one's uplink + me, vsendprep (format→trunc 510→CRLF) once + send_message each. But it is not a leaf of the C sender call-graph: the still-C sendto_ops_butone (send.c:989, the WALLOPS/oper-notice sender) calls it. With serv_butone ported (mangled Rust), that C caller broke the link (undefined reference to sendto_serv_butone). sendto_ops_butone is itself only Rust-called (a graph leaf) and only calls serv_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!/WirePart raw-byte line builder (NOT lossy String/format!) — quit/kill comments and other arbitrary remote content pass through byte-for-byte. leveva typed Messages adopt alongside later.
    • Port (ircd-common/src/send.rs). cstr_bytes(*const c_char) -> &[u8] (raw, NULL→empty); trait WirePart impls 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 = vsendprep faithful (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-terminated body_cstr for 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 the bindings:: imports (parse/s_serv) repointed to crate::send.
    • C drop. #ifndef PORT_SEND_SERV_BUTONE_P8b / #ifndef PORT_SEND_OPS_BUTONE_P8b around the two C bodies in common/send.c; both -Ds added to the send_link.o compile in ircd-sys/build.rs (alongside the P3 -DPORT_SEND_P3). nm confirms send_link.o no longer defines or references either symbol (still defines the other trampolines — sendto_one/sendto_flag/sendto_prefix_one), the binary carries only the mangled Rust ircd_common::send::sendto_{serv,ops}_butone (no exported C symbol), and the cref oracle keeps cref_sendto_{serv,ops}_butone for the L1 diff.
    • L1. New ircd-testkit/tests/sendto_serv_butone_diff.rs (mirrors send_diff.rs + the check_pings_diff.rs GLOBALS_LOCK shared-global pattern): parallel Rust local/fdas and oracle cref_local/cref_fdas worlds of fake server clients (fd −1 → buffer to sendQ), drive the Rust core vs the variadic cref_ 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.
  • 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 choicesendto_serv_v (send.c:541-572) is the near-identical sibling of the already-ported sendto_serv_butone: identical fdas.highest→0 loop over local[fdas.fd[i]], skip one's uplink (cptr != one->from) and me, build ONE vsendprep'd line on the first recipient, send_message each. Picked as the cleanest next target precisely because the delivery logic was already validated in P8b.
    • Config-resolved body — the ver parameter gates delivery on cptr->serv->version & ver, but that block is #if 0'd out in the C source ("We're not using it for now … --B."), so ver is dead code: no recipient is ever skipped on it and rc is never set to 1. The compiled behavior is sendto_serv_butone that additionally takes (and ignores) ver and always returns 0. The Rust port keeps the _ver parameter (call sites still pass SV_UID, faithful) and returns 0.
    • Call-graph checkgrep/nm confirmed NO live C caller: every caller (ircd.c, s_serv.c, s_user.c, channel.c) is already ported and its .o dropped, and send.c itself never calls sendto_serv_v. No C→C edge → ripped out alone (unlike P8b's coupled pair).
    • Mechanismpub unsafe fn sendto_serv_v(one: *mut aClient, _ver: c_int, body: &[u8]) -> c_int in ircd-common/src/send.rs, cloning sendto_serv_butone's body (shared prep_line ≤510+CRLF helper) and returning 0. 10 Rust call sites converted to &wire!(…): ircd s_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 %c separator → a b"!"/b" " byte slice); s_serv 4× EOB (:%s EOB / :%s EOB :%s) + SDIE relay ("%s" over the prebuilt buf) + m_sdie (:%s SDIE). Bindgen import of sendto_serv_v dropped from ircd/s_user/s_serv; the local variadic extern decl removed from channel.rs; all four now use crate::send::sendto_serv_v. C body guarded #ifndef PORT_SEND_SERV_V_P8c; -DPORT_SEND_SERV_V_P8c added to the send_link.o recipe in ircd-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), but cref_sendto_serv_v survives for the L1 byte-diff.
    • L1 — new ircd-testkit/tests/sendto_serv_v_diff.rs (cloned from sendto_serv_butone_diff.rs): drives the Rust sendto_serv_v(one, SV_UID, body) and cref_sendto_serv_v(one, SV_UID, "…%s…", …) on parallel local/fdas vs cref_local/cref_fdas worlds (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->from empty, 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 build 0 warnings.
  • 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_v is the near-identical sibling (same loop + filtering, extra dead ver param) so the two go out together, mirroring P8b (serv_butone+ops_butone) and P8c (serv_v).
    • Config-resolved body — JAPANESE OFF → get_channelmask(x) is rindex(x,':') (libc::strrchr); the jp_valid continue and the #if 0 version gate are not compiled. _v therefore always returns rc = 0 (faithful to the dead gate).
    • Faithfulness — exclusion is cptr == from directly (NOT one->from like serv_butone); &-leading channel returns immediately; mask filter is !BadPtr(mask) && match(mask, cptr->name) → skip on no-match. Line prepped once via prep_line (≤510 + CRLF) on the first surviving recipient. Cores reuse the existing wire!/prep_line machinery in ircd-common/src/send.rs.
    • Call sites — 12 in channel.rs converted to wire! bodies: TOPIC, PART, JOIN 0 (×2), MODE +o (the 5 sendto_match_servs); KICK (×3), NJOIN (×3, one :%s%s tail), MODE (the 7 sendto_match_servs_v). The two crate-wide extern "C" variadic decls removed; import switched to crate::send::{sendto_match_servs, sendto_match_servs_v}.
    • Guard#ifndef PORT_SEND_MATCH_SERVS_P8d around both C bodies; the define added to the send_link.o recipe in ircd-sys/build.rs. The cref oracle keeps the unguarded .o so cref_sendto_match_servs/_v survive for the L1 byte-diff.
    • L1 — new ircd-testkit/tests/sendto_match_servs_diff.rs: parallel local/fdas vs cref_local/cref_fdas worlds, sendQ byte-diff. 5 cases: broadcast (no channel), leading-& early return (nothing delivered), chan:server-mask filtering (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.
  • 2026-06-07 — P8e (sendto_prefix_one rip-out) merged. The first of the vsendpreprep-based prefix senders, ported alone as a clean leaf.

    • Cluster choicesendto_prefix_one (send.c:1017-1027) has no in-send.c caller: internal channel/match paths call the static 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 of vsendpreprep, so it is the natural place to introduce the prep_preprep Rust core that the later channel/match prefix senders (sendto_channel_butone/_butserv/sendto_match_butone/ sendto_common_channels) will reuse.
    • prep_preprep core — faithful port of vsendpreprep (send.c:388-419), IRCII_KLUDGE OFF. Special path (to && from && MyClient(to) && IsPerson(from)): collapse the :%s prefix arg to :from->name!from->user->username@from->user->host when mycmp(par, from->name) == 0, else keep :par; non-special: emit :par + rest verbatim. Builds the body into a Vec<u8> and hands it to the shared prep_line (≤510 + CRLF), then send_message. Added is_client/is_person/my_client inline macros + imported mycmp to 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 supplying par + the pre-formatted rest. (2) the C from == &anon disjunct is unreachable from the Rust callers — only the still-C channel senders substitute lfrm = &anon, and they keep using the C vsendpreprep — so prep_preprep encodes only the mycmp arm; the anon arm lands when those senders are ported. The static vsendto_prefix_one + vsendpreprep stay C for them.
    • Call sites (10) — s_numeric :%s %d %s%s numeric relay; channel INVITE ×2 (:%s INVITE %s :%s); s_user PRIVMSG/NOTICE ×4 (:%s %s %s :%s, the fmt var stays for the sibling sendto_one server/userhost relays) + KILL (:%s KILL %s :%s!%s). Each passes the original first-arg as par and builds the post-:%s remainder with wire!. Imports switched from ircd_sys::bindings::sendto_prefix_one (channel had a local variadic extern decl) to crate::send::sendto_prefix_one.
    • Seam#ifndef PORT_SEND_PREFIX_ONE_P8e around send.c:1017-1027; -DPORT_SEND_PREFIX_ONE_P8e added to the send_link.o recipe in build.rs. nm: sendto_prefix_one is now a mangled Rust symbol in the binary (no C def in send_link.o).
    • L1ircd-testkit/tests/sendto_prefix_one_diff.rs: drive Rust sendto_prefix_one(to, from, par, rest) vs the variadic cref_sendto_prefix_one on parallel make_client'd clients (fd 0 → MyConnect; STAT_CLIENT → MyClient), compare to->sendQ bytes. 4 cases: special-path collapse (par == from->name), special-path no-collapse (par != from->name), non-special (server to, not MyClient → verbatim), collapse >510 truncation. (Gotcha: fixtures must NUL-terminate before the C-string copy — an un-terminated b"alice" into strcpy overran namebuf and corrupted the heap; switched to a bounded set_cstr.)
    • L2golden_channel_invite (5), golden_s_user_message (7), golden_s2s_message, golden_s_user_kill (2), golden_s2s_kill all 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).
  • 2026-06-07 — P8f (sendto_match_butone rip-out) merged. The $$/$#-mask broadcast prefix sender, ported alone as a clean leaf.

    • Cluster choicesendto_match_butone (send.c:935-987) has no in-send.c caller (grep/nm of send.c shows no intra-C reference; the only live caller is m_message in s_user.rs). So ripping it out closes no C→C edge. It is the next clean leaf after P8e because it reuses the P8e prep_preprep/sendto_prefix_one core as-is: its delivery is vsendto_prefix_one(cptr, from, pattern, va) with the real from (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++ over local[i]: skip NULL, skip cptr == one (the origin). For IsServer(cptr): walk cptr->prev (the ordered dependent-client list) for the first IsRegisteredUser(srch) && srch->from == cptr && match_it(srch, mask, what); if none, skip. Else (my client): require IsRegisteredUser(cptr) && match_it(cptr, mask, what). Each survivor → vsendto_prefix_one.
    • match_it (send.c:772-782) — MATCH_HOST (2) matches mask against user->host (an array → .as_mut_ptr()), MATCH_SERVER (1) / default against user->server (a *mut c_char); match returns 0 on a match. IsRegisteredUser(x) = (status == STAT_CLIENT || STAT_OPER) && x->user, identical to the existing is_person helper under the locked config (reused; added a named is_registered_user alias for the faithful C mapping).
    • Faithfulness — delivery is per-recipient (the prefix collapse in prep_preprep depends on each to's MyClient-ness), so the line is re-prepped per recipient, exactly as the C vsendto_prefix_one does. A server recipient is not MyClient → the line is the verbatim :par …; a local person with par == from->name → the collapsed :nick!user@host …. The P8e :%s-lead reduction holds: the call site supplies par (the prefix nick) + the pre-formatted rest.
    • 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 from ircd_sys::bindings to crate::send.
    • Seam#ifndef PORT_SEND_MATCH_BUTONE_P8f around send.c:935-987; -DPORT_SEND_MATCH_BUTONE_P8f added to the send_link.o recipe in build.rs. The cref oracle keeps the unguarded .o so cref_sendto_match_butone survives for the L1 byte-diff. The static vsendto_prefix_one + vsendpreprep stay C (channel senders).
    • L1ircd-testkit/tests/sendto_match_butone_diff.rs: parallel local/highest_fd vs cref_local/cref_highest_fd worlds, sendQ byte-diff. 4 cases: (1) my-client MATCH_HOST — matching client receives the collapsed line, non-matching stays empty (inverse); (2) server prev-walk MATCH_SERVER — server with a matching dependent receives the verbatim line, server with a non-matching dependent skipped (inverse); (3) origin one excluded while another match receives; (4) an UNREGISTERED my-client with a matching host is skipped (the IsRegisteredUser gate). All green. (Gotcha: the mask/par fixtures are read as C strings — must be NUL-terminated before the Rust call; the bare b"*.ok"/b"alice" literals overran into garbage and match_ missed. Added a cstr() helper; cref_call already terminated its copies.)
    • L2 — existing golden_s_user_message (7) + golden_s2s_message byte-identical (the m_message routing 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.
  • 2026-06-07 — P8g (sendto_channel_butone rip-out + anon machinery) merged. The channel broadcaster, ported alongside the anonymous-identity placeholder it needs.

    • Cluster choicesendto_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 need anon. The other two are deferred: sendto_channel_butserv is called variadically by the still-C sendto_flag (send.c:1147) — ripping it out needs sendto_flag (247 Rust call sites) ported first; sendto_common_channels is a clean leaf (no C caller, no anon) for its own step.
    • The anon call-graph constraintanon (a file-static aClient) is referenced by initanonymous (def), vsendpreprep (line 403), channel_butone (468), and channel_butserv (746). Porting channel_butone + anon to Rust while channel_butserv/vsendpreprep stay C means the C side must still reference anon. Solution: Rust provides anon/ausr as #[no_mangle] pub static mut external symbols; the guarded-out C static definitions (-DPORT_SEND_ANON_P8g) become extern declarations so the still-C vsendpreprep/channel_butserv resolve to the Rust globals. initanonymous (called from the Rust boot spine) moves to Rust too. (The RED build confirmed this exactly: undefined anon at send.c:409 (vsendpreprep) + 754 (channel_butserv), plus initanonymous + sendto_channel_butone.)
    • anon staticsanon: aClient/ausr: anUser via MaybeUninit::zeroed().assume_init() (the ircst/cainfo precedent); initanonymous zeroes them with write_bytes then fills the identity (name="anonymous", user->{username,host}={"anonymous","anonymous."}), with the anon.from = &anon / anon.name = namebuf interior self-pointers stable because a static has a fixed address (the no-move invariant).
    • prep_preprep — gains the from == anon_ptr() disjunct alongside par == from->name, so an anonymous-channel member sees :anonymous!anonymous@anonymous. while the sender's own self-send (real from, never anon) shows their real nick.
    • Faithful mapping / equivalence — the C sends the raw vsendprep line (no prefix collapse) to non-eligible (server) recipients and the prefixed vsendpreprep line to MyClient recipients. Routing both through the P8e sendto_prefix_one/prep_preprep core is byte-identical because prep_preprep already branches on my_client(to): a server recipient (not MyClient) → else-path :par + rest (== vsendprep); a MyClient recipient → the same collapse. (A local STAT_CLIENT always has userMyConnect && IsClient ⇒ IsRegisteredUser, so the "MyConnect server/unknown in the else branch" case is never MyClient.) The acptr->from == one/IsMe skips and the acptr != from (don't re-send to the self-sent from) guard are preserved exactly.
    • Call sites (7) — s_numeric do_numeric (channel-numeric relay, :%s %d %s%s); channel can_join (invite-overriding-ban + invite-overriding-limit NOTICEs), reop_channel (the +%c (%d) enforce NOTICE — the %c mode char passed as a 1-byte &[ch] WirePart), set_mode (the two anonymous-flag warning NOTICEs); s_user m_message (channel PRIVMSG/NOTICE, :%s %s %s :%s). Each builds the post-:%s rest with wire!.
    • Seam#ifndef PORT_SEND_CHANNEL_BUTONE_P8g around send.c:459-510 and #ifndef PORT_SEND_ANON_P8g around the anon statics/initanonymous (with the #else extern decls); both -Ds added to the send_link.o recipe in build.rs. The cref oracle keeps the unguarded .o (its anon stays an internal static; cref_initanonymous survives for L1).
    • L1ircd-testkit/tests/sendto_channel_butone_diff.rs: parallel Rust-world / cref-world channels (each its own aChannel + member Link chain + clients, since channel_butone takes the channel by arg), sendQ byte-diff. 4 cases: (1) broadcast — a local registered member collapses, a server member gets the verbatim :par, an IsMe member is skipped; (2) one exclusion — a member whose from == one is empty while another receives (inverse); (3) self-send — a local registered from (one==null) gets exactly ONE line and the loop skips acptr == from (not doubled); (4) anonymous channel — a client from makes the member see :anonymous!anonymous@anonymous. while from's own self-send shows the real nick. All four call their resp. initanonymous. (Gotchas: the cref oracle formats into process-global sendbuf/psendbuf + anon/ausr are globals → a GLOBALS_LOCK mutex serializes the parallel #[test]s; and STAT_ME == -1, not -2, which is STAT_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-:par path + remote-user prefix formatting).
  • 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 choicesendto_common_channels has no in-send.c caller; its live callers are the now-Rust exit_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 with user, plus user itself 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 real user, never &anon. No new anon machinery.
    • The len-caching subtlety (why NOT sendto_prefix_one per recipient) — C computes vsendpreprep once (the if (!len) guard, DEBUGMODE off) into the global psendbuf and reuses that buffer for every send_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 a chptr->clist member can be remote (not MyClient) and still receives the cached collapsed line. So the faithful Rust port holds a local [u8;520] buf + len and calls prep_preprep(to_or_user, user, par, rest, &mut buf) only when len==0, then send_message(cptr, buf, len) — re-prepping per recipient via the public sendto_prefix_one (which re-runs prep_preprep against each to) would diverge in branch 2.
    • sentalong[cptr->fd] (branch 2) — remote fd = -1 — remote clients have fd==-1 (make_client), so C's sentalong[cptr->fd] does *(sentalong-1), a pre-existing OOB read C tolerates as pointer arithmetic. Ported as a per-call vec![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 at highest_fd>=50 (untested at L1).
    • Faithful mappingfrom == user for every call and the pattern leads :%s with par == 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 HUB IsMember-over-user->user->channel walk + the big-client clist/sentalong walk). New private is_member/is_quiet helpers mirror channel.rs (via the P2-ported find_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_user m_nick (par=parv0, rest=wire!(b" NICK :", nick)). Removed sendto_common_channels from the local extern "C" blocks in both files; imported crate::send::sendto_common_channels.
    • Seam#ifndef PORT_SEND_COMMON_CHANNELS_P8h around send.c:620-729 and around the now-unused sentalong static (only that function referenced it); -D added to the send_link.o recipe in build.rs. The cref oracle keeps the unguarded .o for L1.
    • L1ircd-testkit/tests/sendto_common_channels_diff.rs: parallel rust / cref local[]
      • highest_fd worlds with per-client user->channel Link 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 &anon substitution); (4) remote user (fd=-1) — no self-send, a local co-member still receives (line prepped from the first recipient since len starts 0). A GLOBALS_LOCK serializes the parallel #[test]s (process-global local/highest_fd + the oracle's psendbuf).
    • L2golden_s_user_nick (5) + golden_s_user_quit (4) byte-identical (NICK change + QUIT broadcast to common channels).
    • S2Sgolden_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).
  • 2026-06-07 — P8i (sendto_flog logfile cluster) merged. Ripped out the sendto_flog logger — 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 choicesendto_flog reads the file-private userlog/connlog fd statics (send.c:1202-1203), set by logfiles_open (1205) and cleared by logfiles_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 — svchans is a separate component shared with the still-C variadic sendto_flag (send.c:1141, 247 blocked call sites). Under the locked config (LOG_SERVER_CHANNELS OFF) the svchans open/close loops in logfiles_open/logfiles_close are not compiled, so the {userlog, connlog} component is cleanly separable.
    • Config-resolved bodyLOG_OLDFORMAT OFF → the new "%c %d %d %s %s %s %s %d %s %lu %llu %lu %llu " format (the #else). USE_SYSLOG/USE_SERVICES OFF → the syslog + check_services_butone USERLOG/CONNLOG relay blocks gone; the !USE_SERVICES && !(USE_SYSLOG&&…) early-return (if logfile==-1 return) IS compiled. LOGFILES_ALWAYS_CREATE OFF → open() has no O_CREAT (file must pre-exist). FNAME_USERLOG/FNAME_CONNLOG set (per-object -Ds on send.o's recipe).
    • Formatting = byte-identical by construction — the Rust sendto_flog calls libc::sprintf with the exact same C format string + args cast to match C's varargs promotions (%c/%dc_int, %s*c_char, %luc_ulong, %lluc_ulonglong; (u_int)firsttime/(u_int)timeofday via as u_int as c_int). Same libc engine + same format ⇒ no formatting logic to drift. Independently checked the rendered line against a standalone C sprintf of the same values.
    • FNAME paths into RustFNAME_USERLOG/FNAME_CONNLOG are recursively-expanded Makefile vars (per-object -Ds on send.o, absent from bindgen). build.rs expands both via make and exports IRCD_USERLOG_PATH/IRCD_CONNLOG_PATH rustc-envs (the PID_PATH trick); ircd-sys re-exposes them as USERLOG_PATH/CONNLOG_PATH consts; the Rust logfiles_open opens those exact paths so it hits the identical files the reference-C build does.
    • Seam#ifndef PORT_SEND_FLOG_P8i wraps send.c:1202-1440 (the two statics + the three fns); -DPORT_SEND_FLOG_P8i added to the send_link.o recipe in build.rs. The cref oracle keeps the unguarded send.o so cref_logfiles_open/cref_sendto_flog survive for L1. Symbols confirmed Rust-owned: nm target/debug/ircd | grep -E ' T (sendto_flog| logfiles_open|logfiles_close)$'.
    • L1ircd-testkit/tests/sendto_flog_diff.rs, the write_pidfile pattern (s_bsd_leaves_diff.rs): both worlds open the same hardcoded USERLOG_PATH/CONNLOG_PATH, so drive logfiles_open → sendto_flog → logfiles_close in the rust (c_*) and cref (cref_*) worlds, read the file back, assert identical bytes. O_CREAT off → pre-create the file empty; the pre-create failing (sandbox /usr/local/var/log absent) mirrors the open() failing → both userlog/connlog stay -1 → sendto_flog early-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. timeofday set on both ircd_sys::bindings::timeofday (rust) and cref_timeofday (oracle); a GLOBALS_LOCK serializes the parallel #[test]s (shared logfile paths + globals). Both pass.
    • No L2 / no S2Ssendto_flog writes a file, never the wire, and USE_SERVICES is OFF (the USERLOG/CONNLOG service relay is not compiled) → nothing reaches a golden client; not a msgtab handler. Boot smoke: logfiles_open runs at every ircd boot, so golden_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_close resolve their ircd.rs/s_conf.rs bindgen callers likewise.
  • 2026-06-07 — P8j (the keystone sendto_flag + dead_link) merged. Ripped out the server-notice broadcaster sendto_flag (send.c:1141), the largest remaining trampoline (~220 call sites across 16 ircd-common files), plus dead_link (its last C caller).

    • Cluster choice / connected componentsvchans[SCH_MAX] is a file-static in send.c shared by setup_svchans (populates each svc_ptr via find_channel) and sendto_flag (reads svc_ptr), so porting sendto_flag pulls in svchans + setup_svchans as one unit (the P8b/P8i precedent). sendto_channel_butserv stays 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 bodiessendto_flag: USE_SERVICES off → the check_services_butone NOTICE/ERROR relay gone; LOG_SERVER_CHANNELS off → the (svchans+chan)->fd logfile 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. svchans initializer: CLIENTS_CHANNEL off → 14 entries (ERROR..WALLOP, OPER), SCH_MAX == 14. dead_link: DEBUGMODE off → the trailing Debug() is a no-op.
    • svchans as a Rust static#[no_mangle] pub static mut svchans: [SChan; SCH_MAX], names via b"&...\0" byte statics so the svc_chname pointers fill in const context (a const fn sch() helper). setup_svchans is #[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 %#x the tally regex missed). Added impl WirePart for c_uint (%u) and pub newtypes Hex08/Hex/ HexUp/HexAlt(%#x)/Dec02/Dec2/Chr/Ptr. Integer specifiers render via Rust format! (byte-identical to C for these specs — verified {:08x}/{:02}/{:2} etc.); %#x and %p defer to libc::snprintf so they match glibc exactly (e.g. %#x of 0 → 0, not 0x0; %p of 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_bsd report_error takes a runtime format param (not a literal) → rendered via libc::sprintf with 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 %#x debug sentinels (list/whowas refcnt loops) use HexAlt.
    • Seam#ifndef PORT_SEND_FLAG_P8j wraps send.c:1057-1200 (svchans + setup_svchans + sendto_flag); #ifndef PORT_SEND_DEAD_LINK_P8j wraps send.c:50-76 (dead_link); both -Ds added to the send_link.o recipe in build.rs. The cref oracle keeps the unguarded send.o so cref_sendto_flag/cref_setup_svchans/cref_dead_link survive for L1. Symbols confirmed: nm target/debug/ircd shows setup_svchans T + svchans D Rust-owned, and send_link.o no longer defines sendto_flag/setup_svchans/dead_link.
    • L1ircd-testkit/tests/sendto_flag_diff.rs drives the Rust sendto_flag vs cref_sendto_flag through each world's setup_svchans + a &NOTICES channel inserted into its channel hash (add_to_channel_hash_table) with one local member, diffing the member sendQ. 4 cases: basic :me NOTICE &NOTICES :text wrap, mixed %d/%s/%u call-site rendering, chan >= SCH_MAX clamp to SCH_NOTICE, and the NULL-svc_ptr no-op inverse. A GLOBALS_LOCK serializes (svchans/me/channel-hash are process-global). All 4 pass; the P8b– P8i send-core L1 suite still green.
    • L2 — server-notice channels (&NOTICES etc.) are never created in the golden scenarios, so svc_ptr is NULL and sendto_flag is 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).
  • 2026-06-07 — P8k (sendto_channel_butserv) merged. Ripped out the channel local-members broadcaster sendto_channel_butserv (send.c:746-780) — the relay that pushes TOPIC/PART/KICK/JOIN/MODE (and the anonymous PART, and sendto_flag's server notices) to the members of a channel connected to this server.

    • Unblock / cluster choice — P8j made sendto_flag Rust; that was channel_butserv's only in-send.c caller, so it became the next clean leaf. Unlike P8g sendto_channel_butone, butserv never reaches a server recipient (the loop keeps only MyClient(acptr) — that's the "butSERV"), so every recipient is MyClient with a constant lfrm. That makes the per-recipient sendto_prefix_one (the P8e prep_preprep core) byte-identical to the C psendbuf/if (!len) single-prep cache — no need to replicate the cache.
    • Config-resolved body — JAPANESE off (no jp_valid), DEBUGMODE off (no Debug()): MyClient(from) self-send first via the real (un-anonymized) prefix, then if IsQuiet(chptr) return; IsAnonymous(chptr) && IsClient(from)lfrm = &anon (the P8g anon globals, now Rust); loop chptr->clist delivering one line to each MyClient(acptr) && acptr != from.
    • Rust corepub unsafe fn sendto_channel_butserv(chptr, from, par: *const c_char, rest: &[u8]) in ircd-common/src/send.rs, right after sendto_channel_butone. Reuses sendto_prefix_one/prep_preprep/is_quiet/is_anonymous/anon_ptr/my_client.
    • Seam#ifndef PORT_SEND_CHANNEL_BUTSERV_P8k over channel_butserv (746-780) AND its now-orphaned static helpers vsendpreprep (405-444) + vsendto_prefix_one (fwd decl 34 + def 1057-1065) + the psendbuf[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/sendbuf stay live (vsendto_one); the anon/ausr externs stay (harmless unused externs in send_link.o). -D added to the send_link.o recipe in build.rs. cref oracle keeps the unguarded send.o so cref_sendto_channel_butserv/cref_initanonymous survive for L1.
    • Call sites (14)par = the :%s prefix arg, rest = everything after :%s rebuilt with wire!. channel.rs: TOPIC, PART×3, KICK (%s %s :%s), JOIN (:%s), JOIN-burst (%s%s), MODE×5 (incl. the +%s%c %s %s op-grant via crate::send::Chr); s_misc.rs: the anonymous PART (:%s PART %s :None); send.rs: the sendto_flag wrapper now calls the Rust core directly (body_cstr still truncates at NUL to match the old %s). The local variadic extern fn sendto_channel_butserv decls in channel.rs + s_misc.rs replaced with use crate::send::sendto_channel_butserv; the stale bindgen import dropped from send.rs.
    • L1ircd-testkit/tests/sendto_channel_butserv_diff.rs builds a channel with a clist of members (a local from, a local co-member, a remote client, a server) in two parallel worlds and diffs each member's sendQ against cref_sendto_channel_butserv. 4 cases: (1) basic — from self-send + co-member receive collapsed, remote+server skipped (the "butserv" inverse); (2) quiet — from self-sends then the early return suppresses the loop; (3) anonymous — lfrm=&anon so the co-member gets :anonymous!anonymous@anonymous. while the self-send keeps the real prefix (both worlds' anon populated via initanonymous/cref_initanonymous — their anon statics are separate, and the path needs IsPerson(&anon)); (4) remote from — 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.)
  • 2026-06-07 — P8l (sendto_one keystone) 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 few rlen += callers read. The Rust fn is a plain (mangled) module fn, so it coexisted with the still-defined C sendto_one symbol throughout the per-file conversion — the tree stayed green after every file, and only the final #ifndef PORT_SEND_ONE_P8l guard-flip removed the C trampoline.
    • Call-site mechanism. Two builders. (1) wire! for c"..." 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 new reply_one!(to, fmt, args...) macro (send.rs) for reply(...) and runtime-char* patterns — 377 sites: it renders fmt+args through libc snprintf into 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 — vsendprep used libc vsprintf, the same engine, so any printf specifier (incl. %lu/%ld/%lld/width/ precision) and NULL %s ((null)) match exactly. The 2 rlen += sendto_one(...) accumulator sites (channel.rs ~1556/1583) call the core directly via an inline snprintf-into-__rbuf block (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-runtime reply(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 ~430 reply(ERR_CONST)/literal sites have a compile-time-known format. And because the delivery path formats via libc vsprintf (NOT the custom irc_vsprintf), the reply_one!/snprintf bridge is faithful for ALL of them — so sendto_one was rippable now without the P10 numeric layer (the idiomatic leveva-Numeric conversion 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 the sendto_one entry from class.rs's bindgen use). ircd-common compiled 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_P8l added to the send_link.o recipe in build.rs. The other sendbuf/vsendprep uses (send.c:501-946) are inside already-P8b…P8k-guarded senders, so the cut is clean. cref oracle keeps sendto_one unguarded for the L1 diff.
    • L1. New sendto_one_diff.rs: Rust core (+wire!/reply_one!) vs cref_sendto_one on parallel fresh clients, sendQ byte-diff; 5 cases (wire! literal, reply_one! %s template, %d/%02d numerics, NULL %s, >510 trunc). A GLOBALS_LOCK serializes — the oracle's cref_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_stats sq, golden_s_misc Sq/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).
  • 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.o shows the 4 public senders + the file-statics (esend_message/build_old_prefix/build_new_prefix/build_suffix), and grep confirms 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-private static mut (not ABI; addr_of_mut! access); CLEAR_LENGTHS is a helper run per public call. The variadic fmt, ... collapses to suffix: &[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 plain pub 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 %s prefix builders (build_old_prefix/ build_new_prefix) and the hardcoded :anonymous!anonymous@anonymous. %s %s local-member prefix defer to libc::sprintf with the exact C format strings rather than wire! — strictly more faithful: it reproduces glibc's (null) rendering for the NULL oname build_new_prefix can pass when dname != 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 C for(...; i--) semantics under continue: esendto_match_servs decrements right after fetching cptr (its body has continues), esendto_serv_butone at loop end (its body is a single if). The maxplen/lastmax + maxplen+slen>512 → slen=510-maxplen suffix truncation, the UID selection + newplen=-1 bail, 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.a no 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-rs 0 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.c local[]/timeofday/highest_fd, s_auth.c iauth_*), the support remnants (snprintf_append/dgets/make_isupport/ipv6string/minus_one), and send_link.o's lone rcsid. 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).
  • 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.o is the cleanest: nm --defined-only s_auth_link.o showed it defines ONLY the five iauth globals (+ module-private rcsid), 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* = bindgen LineItem*). All written/read by the now-Rust read_iauth + the two report_iauth_* reporters (this file); iauth_options/iauth_spawn additionally read by the iauth-timeout checks in ircd.rs/s_user.rs/s_bsd.rs.
    • Mechanism — defined them as #[no_mangle] pub static mut in ircd-common/src/s_auth.rs, zero/NULL-initialized exactly as the C BSS defs. Removed the two now-redundant local extern "C" decls (the iauth_conf/iauth_stats block + the iauth_version block) and dropped iauth_options from the use ircd_sys::bindings::{…} import (it would otherwise collide with the new module def); repointed the lone ircd_sys::bindings::iauth_spawn ref to the local def. The OTHER modules keep their ircd_sys::bindings::iauth_options/iauth_spawn imports untouched — those are bindgen extern "C" { static mut … } declarations that resolve to the Rust definitions at link, the same data-symbol seam P8g used for anon/ausr and P8j for svchans.
    • build.rss_auth.o added to PORTED (so link_objs() filters it out before the … => "s_auth_link.o" map, which was removed as dead); the whole s_auth_link.o second-compile step (the -DPORT_S_AUTH_REPORT_P7u … -DPORT_S_AUTH_SENDTO_IAUTH_P8a chain) deleted. s_auth.o stays in CREF_OBJS (the full unguarded compile) so the cref_* 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_diff is load-bearing here: it drives the Rust read_iauth (which mutates all five) against cref_read_iauth and byte-diffs the resulting conf/stats lists + client state, so a mis-wired global would fail it.
    • Gatenm target/debug/ircd shows 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 --workspace 0 warnings.
    • Remaining P8 Circd_link.o (me + the dorehash/timers/CLI data globals; me has the interior self-pointer hazard), s_bsd_link.o (local[]/highest_fd/timeofday/fd globals), support_link.o (ipv6string/minus_one data + dgets/make_isupport fns + the variadic snprintf_append, the last entangled with the P11 irc_sprintf deletion), and send_link.o's lone module-private rcsid (trivially droppable). Then: drop all cc::Build, mothball the oracle, migrate tests.
  • 2026-06-08 — P8o (send.o dropped outright) merged. The second P8 data-global drop, and the trivial one flagged at P8n's close.

    • Scopesend.c is 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 — plus dead_link, the sendto_flog logfile cluster, the svchans/setup_svchans component, and the anon/ausr/initanonymous machinery were #ifdef'd out of send_link.o and reimplemented in ircd-common/src/send.rs.
    • Why droppable nownm --defined-only cbuild/send_link.o showed exactly one symbol: rcsid (lowercase r = local, internal-linkage static const char[] version string, referenced by nothing). Every live send.c symbol comes from Rust. So send_link.o contributed nothing to the link except a dead static.
    • Mechanism — added "send.o" to PORTED in ircd-sys/build.rs (so link_objs() filters it out); deleted the "send.o" => "send_link.o" arm from the link-map match; deleted the entire send_link.o second-compile run(make … --eval=send_link.o: …) step plus its long -DPORT_SEND_P3 … -DPORT_SEND_ONE_P8l comment block, replacing it with a short P8o note. cref keeps the unguarded send.o in CREF_OBJS (native Makefile recipe, FNAME_* macros expanded) so the cref_* send oracle survives for L1.
    • FNAME paths — the Rust logfiles_open (ported P8i) reads FNAME_USERLOG/FNAME_CONNLOG via the IRCD_USERLOG_PATH/IRCD_CONNLOG_PATH rustc-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).
    • L2golden_registration (2) byte-identical (exercises the full boot send path).
    • Verifynm target/debug/ircd shows logfiles_open/svchans as Rust defs and no undefined send symbols (sendto_one/sendto_flag are now plain Rust fns, not C-ABI symbols). cargo build (full workspace) 0 warnings.
    • Remaining P8 Circd_link.o (me + the dorehash/timers/CLI data globals; me has the interior self-pointer hazard), s_bsd_link.o (local[]/highest_fd/timeofday/fd globals), support_link.o (ipv6string/minus_one data + dgets/make_isupport fns + the variadic snprintf_append, the last entangled with the P11 irc_sprintf deletion). Then: drop all cc::Build, mothball the oracle, migrate tests.
  • 2026-06-08 — P8p (s_bsd.o dropped outright) merged. The third P8 data-global drop, the s_bsd.c event-loop TU.

    • Cluster choice / scopes_bsd.c had 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.o then showed it defined ONLY data globals: local (the aClient *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-private rcsid. 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.rs as #[no_mangle] pub static mut with 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(). nm confirms adfd/resfd/udpfd land 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,…} bindgen extern static mut refs; those decls resolve to these Rust defs at link (the data case of the function-symbol seam, proven for anon/ausr in P8g, svchans in P8j, the iauth globals in P8n). s_bsd.rs's own full-path ircd_sys::bindings::X / b::X reads 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 in get_my_name's block (now the module static); (2) renamed read_message's local var let local = local_base()local_p (E0530: a let cannot shadow the new static); also pruned adfd/highest_fd from start_iauth's function-local use ircd_sys::bindings::{…} so it reads the module statics directly.
    • build.rss_bsd.o added to PORTED; deleted the whole s_bsd_link.o second-compile (the make --eval rule carrying the long -DPORT_S_BSD_LEAF_P7d .. -DPORT_S_BSD_READ_MESSAGE_P7ff chain + the IRCDPID/IAUTH path defines) and the "s_bsd.o" => "s_bsd_link.o" arm in link_objs(). The Rust write_pidfile/start_iauth still read IRCDPID_PATH/IAUTH_PATH/IAUTH via the existing IRCD_PID_PATH/IRCD_IAUTH_PATH/IRCD_IAUTH rustc-envs. cref keeps the unguarded s_bsd.o (still in CREF_OBJS) so cref_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 reads local[]/fdas/highest_fd/timeofday), add_connection_diff (writes local[]/highest_fd/ fdas/fdall), close_connection_diff/close_listeners_diff, list_diff (add_fd/del_fd mutate fdas/fdall), s_bsd_leaves_diff, connect_server_diff, check_client_diff, setup_ping_diff, send_ping_diff, check_pings_diff, get_my_name_diff (writes mysk vs cref_mysk), init_sys_diff, daemonize_diff, start_iauth_diff, delayed_kills_diff, calculate_preference_diff — all zero-diff. Cross-module wire seam checked via sendto_one_diff/ sendto_flag_diff/sendto_common_channels_diff (they read local through the seam). Boot path via golden_registration (full event loop over the globals) byte-identical. nm target/debug/ircd: all 10 globals resolve as Rust B/D defs, none U. cargo build 0 warnings.
  • 2026-06-08 — P8q (ircd.o dropped outright — the ircd.c data globals) merged. The fourth P8 pure-data-global drop, completing the ircd.c migration begun at P7c.

    • Cluster choice / why now. Over P7c..P7oo every function in ircd.c was ported to ircd-common/src/ircd.rs behind the -DPORT_IRCD_* guard chain. nm --defined-only cbuild/ircd_link.o then showed no T symbols — only data globals + the module-private rcsid — 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.o that 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 timers nextconnect/nextgarbage/nextping/nextexpire/ nextiarestart/nextpreference (= 1) + nextdnscheck/nexttkexpire/nextdelayclose (= 0).
    • The me self-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-ported make_server/setup_me, NOT at definition. So the Rust static is just MaybeUninit::<aClient>::zeroed().assume_init() (the same const-zeroed pattern P8p used for mysk), and client initializes to core::ptr::addr_of_mut!(me) in const. The "never move/copy me by value" invariant is already honored tree-wide (addr_of_mut!(me) / ircd_sys::bindings::me everywhere), 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 + the next* timers) are referenced by the other modules (s_bsd/s_user/…) through their ircd_sys::bindings::* extern static mut decls, 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) and nextpreference/nextiarestart (io_loop timers) are absent from any *_ext.h, so they were reached via two local extern "C" blocks in ircd.rs (one at the signal-handler cluster, one inside io_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 six next* timers (block @34), and configfile/serverbooting (the inner c_ircd_main block).
    • Path defaults. configfile = IRCDCONF_PATH / tunefile = IRCDTUNE_PATH are recursively-expanded Makefile path vars (absent from bindgen). build.rs now expands them via make --eval (the IRCD_MOTD_PATH/IRCD_PID_PATH/IRCD_SERVER_PATH pattern) and emits IRCD_CONF_PATH/IRCD_TUNE_PATH rustc-envs; ircd-sys re-exports NUL-terminated CONF_PATH_Z/TUNE_PATH_Z consts (the concat!+env! must run in ircd-sys), and the Rust *mut c_char statics init from .as_ptr(). The C never writes through these pointers (only reassigns on -f/-T), so pointing at a 'static byte string is faithful. Verified: strings target/debug/ircd shows /usr/local/etc/ircd.conf + /usr/local/var/run/ircd.tune.
    • build.rs. ircd.o added to PORTED; the ircd_link.o make second-compile (the long -DPORT_IRCD_TUNE_P7c .. -DPORT_IRCD_MAIN_P7oo chain + the -DIRCD*_PATH machine paths) and the "ircd.o" => "ircd_link.o" link-map arm deleted. The cref oracle keeps the unguarded ircd.o (still in CREF_OBJS, native Makefile recipe) so cref_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, writes dorehash/dorestart/ restart_iauth), ircd_tune_diff (9), ircd_cli_helpers_diff (3), setup_signals_diff (1), activate_delayed_listeners_diff (2) — all zero-diff vs cref_; golden_registration (2) byte-identical (boots the Rust ircd, which now owns me/the timers/configfile/tunefile). nm target/debug/ircd shows all ~26 globals resolving as Rust defs (B/D, none U) with the correct .data (non-zero inits) / .bss (zero inits) split; the cref ircd.o still carries 49 symbols (oracle intact). cargo build --workspace 0 warnings.
    • Remaining C. Only support_link.o keeps C logic — the format remnants dgets/make_isupport/snprintf_append + the data ipv6string/minus_one (and the P1-deferred irc_sprintf engine, to be deleted not ported in P11). Every other TU is now Rust or pure-data-dropped.
  • 2026-06-08 — P8r (support.o dropped outright — the LAST C-logic TU) merged. The final P8 TU drop. After P8n–q drained the data-global .os, support_link.o was the only remaining C translation unit defining logic; this drops it.

    • Cluster choice / why now. nm --defined-only support_link.o showed exactly: dgets/make_isupport (T), snprintf_append (T, variadic), ipv6string (B), minus_one (D), + the module-private rcsid/dgbuf.0. The pure subset (mystrdup/strtoken/myctime/inetntop/… + the MyMalloc trio) 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 was s_user.c's WHOX builder, which the Rust port (s_user.rs:387) already replaced with a field-by-field Vec<u8> builder. grep confirms 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 variadic extern "C" anyway; this sidesteps that entirely. (irc_sprintf, the deferred varargs engine, is likewise deleted-not-ported, in P11.)
    • dgets port. Faithful translation of the \r/\n-line reader with \-continued- newline splicing (support.c:702-808). C uses static char dgbuf[8192] + head/tail pointers; ported with head/tail as byte offsets into a module-private static mut DGBUF (behaviour-identical — the statics are private to dgets), 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 advances s/t through the *s++ = *t++ copy and then does t = 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_isupport port. Two MyMalloc(BUFSIZE) token strings + a 3-slot MyMalloc pointer array (support.c:810-840, #ifndef CLIENT_COMPILE always active in the daemon). The sprintf format 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 (raw copy_nonoverlapping of the base + optional NETWORK= + the bindgen networkname bytes) to avoid any UTF-8 reinterpretation of networkname.
    • Data globals via the seam. ipv6string: [c_char;46] = [0;46] (BSS) and minus_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 bindgen extern static mut decls — the proven DATA-symbol seam (local/me/svchans/iauth). bindgen surfaces minus_one as [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 outside support.rs.
    • build.rs. support.o added to PORTED; the support_link.o second-compile and the "support.o" => "support_link.o" arm in link_objs() deleted — link_objs() is now a plain CREF_OBJS \ PORTED set difference (no *_link.o variants remain anywhere). cref keeps the unguarded support.o in CREF_OBJS so cref_dgets/cref_make_isupport/cref_mystrdup/… survive for the L1 differentials.
    • Milestone — every ircd C TU is now Rust. With support.o dropped, CREF_OBJS \ PORTED is empty: the product link set (libircd_c.a) holds only the generated version.o (a version-string constant) + the L1 harness's ctruth.o. All ircd logic and data is Rust. Remaining P8 work: drop all cc::Build (including regenerating version.c as Rust), mothball the L1/L2 oracle after a final green run, and migrate the load-bearing tests into ircd-common.
    • Gate. New L1 differentials in support_diff.rs: dgets_matches_reference drives both dgets/cref_dgets over independent pipes carrying plain \n lines, an odd-backslash continuation (spliced), an even-backslash run (newline kept), a \r byte, an EOF tail with no trailing newline, and empty input — asserting the identical (ret, bytes) stream incl. the dgets(_,_,0) reset; make_isupport_matches_reference walks the char** to NULL and asserts identical token strings (networkname NULL → no NETWORK= token). Both zero-diff; full support_diff (7 tests) zero-diff. golden_registration (2) byte-identical — the 005 RPL_ISUPPORT numeric flows through make_isupport. Seam reads checked via s_conf_match_ipmask_diff (5) + res_gethost_diff (8) zero-diff. nm target/debug/ircd: dgets/make_isupport resolve T, ipv6string B, minus_one D (none U), snprintf_append absent. cargo build --workspace 0 warnings.
  • 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-contained ircd-common/tests/<name>_snap.rs insta-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 by ircd_common::link_anchor() (ircd-sys's whole-archive version.o/ctruth.o come transitively, so bindgen types still resolve). All cref_* externs and ircd_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>_diff is green at capture time (Rust == cref). Combined with the freshly-captured snapshot == Rust, that gives snapshot == cref golden — so the frozen .snap carries 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 (gettimeofday shims) and process-global Mutex serialization 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.rs duplicates + their orphan snapshots; silenced dead_code on 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.rs test files / 707 committed .snap fixtures, exact 1:1 coverage (no missing/extra/orphan), ircd-common suite green and stable across repeated runs, cargo build --workspace 0 warnings. ircd-testkit + the cref_/ircd-golden oracle are left intact for now (still the differential proof); dropping all cc::Build (incl. regenerating version.c as Rust) and mothballing the oracle is the remaining P8 work.
  • 2026-06-08 — P8t (version.c → Rust) merged. Ported ircd/version.c — the last generated C translation unit — to ircd-common/src/version.rs, then deleted its generation + compile from ircd-sys/build.rs.

    • Scope / classification. version.c is pure data (nm version.o = only D/B/r, no T): char *generation, char *creation, char *pass_version, char *infotext[], char **isupport, + the module-private rcsid static. It is generated by ircd/version.c.SH (bumps generation, embeds a date creation, expands PATCHLEVEL
      • source-file checksums). Not in CREF_OBJSversion.o was archived straight into the product libircd_c.a, so there is no cref_* oracle for it and no L1 differential.
    • Mechanism (data-symbol seam, as P8n–P8q). Defined the five globals as #[no_mangle] pub static mut in version.rs + a VERSION_LINK_ANCHOR (added to lib.rs::link_anchor()). The consuming modules (s_serv m_info walks infotext and prints creation/generation; s_user RPL_CREATED; s_debug/s_bsd read isupport/pass_version) reach them through their existing ircd_sys::bindings::* extern static mut decls — which come from s_externs.h, independent of which .o compiles — 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 via addr_of!(infotext) (the local[]/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 new ircd-common/build.rs that formats SystemTime::now() into the C-like Wkd Mon D YYYY at HH:MM:SS UTC shape (dependency-free Hinnant civil-from-days; UTC to avoid host-TZ nondeterminism; pinned via a lone rerun-if-changed=build.rs so it only re-stamps on clean/build.rs-edited builds). pass_version = the bindgen PATCHLEVEL const (b"0211030000\0"); infotext[] copied byte-for-byte from the generated C (including the trailing C source-checksum lines). isupport = NULL (runtime-filled by make_isupport).
    • build.rs drop. Removed the version.c.SH generation block, the version.o compile block, and ar.arg("version.o") from ircd-sys/build.rs; updated the CREF_OBJS doc comment. link_objs() was already empty (every hand-written TU ported), so libircd_c.a now contains only the L1 harness ctruth.o.
    • Canonicalizer. creation is now genuinely build-time volatile (the reference-C version.o stamp vs the Rust crate-metadata stamp legitimately differ), so added a 003 RPL_CREATED masking rule to ircd-golden's canonicalize() (:This server was created <MASKED>), the exact analogue of the existing 371 Birth-Date rule that masks the same creation/generation tokens in /INFO. No snapshot captures the value — the only .snap mentioning it (s_err replies table) holds the static format template ":%s 003 %s :This server was created %s", unaffected.
    • Gate. cargo build --workspace 0 warnings; nm target/debug/ircd shows generation/creation/pass_version/infotext as D and isupport as B (Rust defs, none U), version.o absent from the archive (ar t = ctruth.o only); string values confirmed (build stamp / 0211030000 / IRC --). ircd-common snapshot suite green (117 test-result groups). Golden suite 58 pass + the one known pre-existing golden_s2s_s_serv_stats flake (reference-C uninitialized-sendq garbage: …3544950802210619392kB sq vs Rust 0kB 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 by golden_s_serv_info_links/golden_registration (both green post-mask).
  • 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 (L1 cref_* 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 emits 0kB 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: the make CREF_OBJS object build; the RES_CACHE_EXPOSE cref res.o recompile; the ctruth.o layout-truth compile; the libircd_c.a archive (ar); the whole-archive link directive (static:+whole-archive=ircd_c) and -lz/-lm/-lcrypt; and the cargo:objs/cargo:cbuild metadata. The CREF_OBJS/PORTED/link_objs/EXTRA_CFLAGS constants + the makefile_var helper are gone. Link-lib finding: none of z/m/crypt are needed by the Rust workspace — no Rust code calls crypt/zlib, and pow resolves from libc (pow@GLIBC_2.29; glibc merged libm in 2.29), so the pure-Rust binary links clean without them.
    • What build.rs still does (no C compiled). Two things still read the C source tree: (1) bindgen over wrapper.hircd-common still 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.h headers, so configure is still run purely to emit setup.h/config.h (read via -I cbuild). (2) The install-path exports — a new make_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 --eval does var expansion only — nothing is built. So the C source headers stay in-tree as bindgen input; the .c files are no longer compiled (the reference-C tree is "mothballed" in the build-the-oracle sense).
    • Mothballed the oracle crates. ircd-testkit (L1) and ircd-golden (L2) can no longer build (no reference C to compile / link), so both are excluded from the workspace members in the root Cargo.toml — left on disk (git-recoverable), not deleted, with a note. The ircd-sys-local drift-net — ircd-sys/tests/layout.rs (asserted bindgen vs the real compile via ctruth.o's cref_sizeof_*) and its C source ircd-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-contained insta snapshots in ircd-common/tests/*_snap.rs (P8s), which carry the reference-C correctness forward with no C in the loop.
    • Doc updates. ircd-sys/src/lib.rs module doc rewritten (no longer "compiles the C tree"); the c_ircd_main extern decl's doc notes it now resolves to ircd-common's #[no_mangle] c_ircd_main (P7oo) rather than the -Dmain=c_ircd_main C main.
    • Gate. cargo metadata shows ircd-testkit/ircd-golden gone (exclude works); cargo build --workspace 0 warnings, the ircd binary links pure Rust (boots to the usage banner; nm shows c_ircd_main as T, zero cref_/ctruth symbols, no ircd_c link directive in the current build-script output); cargo test --workspace 698 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 the ircd-common snapshot suite.
  • 2026-06-08 — P8v (C source tree deleted; ircd-sys retired) merged. The literal end of C in the repo. P8u dropped all C compilation but left ircd-sys alive to run configure + bindgen over the C headers (the struct view ircd-common still builds against). P8v removes that last dependency on C files and deletes ircd-sys itself.

    • Froze the bindgen view. Captured the generated bindings.rs (4953 lines, rust-bindgen 0.72.1 over os.h+s_defines.h+s_externs.h+irc_sprintf_ext.h under the locked configure — USE_IAUTH/TKLINE/ENABLE_CIDR_LIMITS on, AFINET=AF_INET6, MAXCONNECTIONS=50) and committed it verbatim as ircd-common/src/bindings.rs with a provenance header + the original #![allow(non_camel_case_types, …, clippy::all)]. It is self-contained (no include!/env!/ external-crate refs; the extern "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.rs gained pub mod bindings; + extern crate self as ircd_sys;, so every existing ircd_sys::bindings::X / ircd_sys::MOTD_PATH path 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) — formerly env!-injected by ircd-sys's build.rs via make --eval — are frozen as literals at the locked ./configure defaults (/usr/local/...), since the Makefile they were expanded from is now deleted.
    • Deletions. ircd-sys crate (build.rs/wrapper.h/src/lib.rs); the mothballed oracle crates ircd-testkit (L1) and ircd-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 root configure wrapper, .clang-format/.clang-format-ignore, and the now-purposeless .github/workflows/clangformat.yml (the repo's only CI, a C formatter). Dropped /cbuild from .gitignore. doc/ is kept (reference docs, not C).
    • Rewiring. ircd-rs dropped its ircd-sys dep and now calls ircd_common::ircd::c_ircd_main directly (the P7oo Rust boot spine; the stale "still entirely C core" main.rs doc was corrected). Workspace members lost ircd-sys; the exclude list (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_table were forward-declared -> () in s_misc.rs but really return c_int (s_serv.rs/hash.rs), and mystrdup was forward-declared *const c_char in 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 an as *mut c_char cast. No behaviour change — same symbols, same link.
    • Gate. cargo build --workspace 0 warnings, the ircd binary links pure Rust; no ircd_sys crate in cargo metadata; grep finds no C source files in the tree; cargo test --workspace 695 pass / 0 fail (was 698 — the delta is the deleted ircd-sys smoke 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 the ircd-common snapshot suite.