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.

1# P8 — Delete the C + retire the oracle harness 2 3P8 removes the last C from the workspace. By the P7 exit, all C *logic* was already Rust; 4only data globals + the ~25 variadic sender trampolines (`sendto_one`/`sendto_flag`/…/ 5`sendto_iauth`, which stable Rust can't define as `extern "C"` variadics) remained. P8 6replaces each trampoline with a **non-variadic Rust sender API** and converts its call 7sites (`sendto_foo(c"fmt %s", …)``sendto_foo(format!("fmt {}", …).as_bytes())`), then — 8once the last C symbol is gone — drops all `cc::Build` compilation from `ircd-sys` and 9mothballs the L1 (`cref_*`) / L2 (`ircd-golden`) oracle, migrating the load-bearing tests 10into `ircd-common`. leveva typed-message adoption rides along with the IRC senders; the 11faithful byte-output is preserved first (`format!`), per the user decision. 12 13One-line summaries: [`PLAN-P8-progress.md`](../../PLAN-P8-progress.md). 14 15--- 16 17- **2026-06-07 — P8a (`sendto_iauth` — the first trampoline) merged.** Ripped out the C 18 variadic iauth-pipe writer; the iauth control protocol now flows entirely through Rust. 19 - **Cluster choice.** `sendto_iauth`/`vsendto_iauth` (s_auth.c:120-170) is the cleanest 20 first trampoline: `nm` confirms **zero undefined references** to either symbol in any 21 link-set object (`s_bsd_link.o`/`ircd_link.o`/`s_auth_link.o`/`send_link.o`/ 22 `support_link.o`) — every remaining C caller sits in an already-`#ifdef`'d-out body — so 23 the **only** live callers are the 24 Rust sites. It is also self-contained (a single 24 pipe `write`, no broadcast/target iteration like the IRC senders). 25 - **leveva note.** This trampoline carries the **iauth pipe protocol** (`<id> <cat> 26 <info>`), not IRC messages, so leveva's `Message`/identifier newtypes don't apply. 27 Per the user decision, formatting is `format!` now (faithful bytes first); leveva typed 28 messages arrive with the IRC sender clusters. 29 - **Port.** New `pub unsafe fn sendto_iauth(line: &[u8]) -> c_int` + `pub unsafe fn 30 ia_str(*const c_char) -> String` (lossy C-string read for `format!`) in 31 `ircd-common/src/s_auth.rs`. Faithful to `vsendto_iauth`: `adfd < 0` → -1; `abuf = line 32 + b"\n"`; the `write(adfd, abuf, len)` loop re-issues from the buffer **base** each 33 iteration (the C `p` cursor is advanced but never passed to `write` — replicated, 34 harmless since the pipe takes each small line in one write); EAGAIN/EWOULDBLOCK → 0-byte 35 partial; hard error → `sendto_flag(SCH_AUTH, "Aiiie! lost slave…")` + `close` + 36 `adfd=-1` + `start_iauth(0)` → -1. `len == abuf.len()` matches the C `strlen(p)` because 37 a `format!`-built line (or a NUL-terminated C buffer read via `CStr::to_bytes`) carries 38 no embedded NUL. 39 - **Call sites (24).** s_auth.rs (9), s_user.rs (3), s_bsd.rs (8, incl. the former 40 `sendto_iauth_p7ff` `link_name` alias — now `use … as sendto_iauth_p7ff`), ircd.rs (3), 41 res.rs (1). `%s`-of-C-string args go through `ia_str`; whole-buffer passthroughs 42 (start_auth `<fd> C …`, setup-auth `<fd> O` packing) pass `CStr::from_ptr(buf).to_bytes()`. 43 The 7 variadic `extern "C" { fn sendto_iauth/_p7ff }` decls deleted. 44 - **C drop.** `#ifndef PORT_S_AUTH_SENDTO_IAUTH_P8a` around both C functions; `-D` added 45 to the `s_auth_link.o` compile in `ircd-sys/build.rs`. `nm` confirms `s_auth_link.o` no 46 longer defines or references the symbols, the binary carries only the mangled Rust 47 `ircd_common::s_auth::sendto_iauth` (no exported C `sendto_iauth`), and the cref oracle 48 `s_auth.o` keeps `cref_sendto_iauth`/`cref_vsendto_iauth` for the L1 diff. Only 49 s_auth.c's data globals (`iauth_conf`/`iauth_stats`/`adfd`/`iauth_options`/…) remain C. 50 - **L1.** New `ircd-testkit/tests/sendto_iauth_diff.rs`: on two AF_UNIX socketpairs, build 51 the same iauth line through the oracle `cref_sendto_iauth(fmt, …)` and the Rust core 52 (pre-formatted bytes), read both pipe ends, assert byte-identical. Covers every 53 `%s`/`%d`-of-fd format family at the call sites, the built-buffer passthrough, and the 54 `adfd < 0` inverse (both return -1, write nothing). Not byte-diffed (faithful by 55 inspection, noted): the debug `-1 E last=%u start=%x …` line (formats raw heap pointer 56 addresses → nondeterministic) and the lost-slave hard-error branch (no deterministic way 57 to force a fatal `write`). 58 - **No regression / no S2S.** The iauth L1 suite (`start_auth_diff` — whose `adfd>=0` 59 handoff now drives the Rust core —, `read_iauth_diff`, `authports_diff`, 60 `iauth_reporters_diff`, `start_iauth_diff`) + `golden_registration` stay green; 61 `cargo build` 0 warnings. No S2S — formats no remote-user wire fields. 62 - **Plan:** `docs/superpowers/plans/2026-06-07-p8a-sendto_iauth.md`. 63 64--- 65 66- **2026-06-07 — P8b (`sendto_serv_butone` + `sendto_ops_butone` — the coupled 67 server-broadcast pair) merged.** Ripped out the second and third variadic sender 68 trampolines in one step, because they form a C→C edge. 69 - **Cluster choice / the coupling.** `sendto_serv_butone` (send.c:517-535) is a clean 70 self-contained broadcast — loop `local[fdas.fd[i]]` over directly-linked servers, 71 skip `one`'s uplink + `me`, `vsendprep` (format→trunc 510→CRLF) once + `send_message` 72 each. But it is **not** a leaf of the C sender call-graph: the still-C 73 `sendto_ops_butone` (send.c:989, the WALLOPS/oper-notice sender) calls it. With 74 `serv_butone` ported (mangled Rust), that C caller broke the link 75 (`undefined reference to sendto_serv_butone`). `sendto_ops_butone` is itself only 76 Rust-called (a graph leaf) and only calls `serv_butone` (now Rust) + `sendto_flag` 77 (stays C), so porting it too closes the edge cleanly — no transitional C shim. 78 - **leveva note.** These carry IRC server-protocol messages. Per the user decision the 79 bytes are built faithfully first via a new `wire!`/`WirePart` raw-byte line builder 80 (NOT lossy `String`/`format!`) — quit/kill comments and other arbitrary remote content 81 pass through byte-for-byte. leveva typed `Message`s adopt alongside later. 82 - **Port (`ircd-common/src/send.rs`).** `cstr_bytes(*const c_char) -> &[u8]` (raw, 83 NULL→empty); `trait WirePart` impls for `&[u8;N]`/`&[u8]`/`*const c_char`/`*mut c_char`/ 84 `c_int` (the lone `%d`, faithful decimal); `wire!(b":", uid, b" QUIT :", comment)` macro 85 (`#[macro_export]`); `prep_line` = `vsendprep` faithful (IRCII_KLUDGE OFF: ≤510 + CRLF); 86 `pub unsafe fn sendto_serv_butone(one, body: &[u8])` (the C loop verbatim, build line on 87 first recipient); `pub unsafe fn sendto_ops_butone(one, from, body: &[u8])` = 88 `serv_butone(:from WALLOPS :body)` + `sendto_flag(SCH_WALLOP, "!%s! %s", from, body)` 89 (the still-C variadic flag sender gets a NUL-terminated `body_cstr` for its `%s`). 90 - **Call sites (18).** serv_butone (11): parse(1 KILL), s_misc(1 QUIT), s_user(6: 91 MODE ±a / KILL ×3 / NICK), channel(2 PART). ops_butone (7): s_user(3: MODE-notice / 92 Bad UID / UID collision), s_serv(4: Remote CONNECT[+%d] / SQUIT / ERROR-relay / 93 brought-in). Each per-file `extern "C" { fn sendto_serv_butone/ops_butone }` decl 94 deleted (s_misc/s_user/channel) and the `bindings::` imports (parse/s_serv) repointed 95 to `crate::send`. 96 - **C drop.** `#ifndef PORT_SEND_SERV_BUTONE_P8b` / `#ifndef PORT_SEND_OPS_BUTONE_P8b` 97 around the two C bodies in common/send.c; both `-D`s added to the `send_link.o` compile 98 in ircd-sys/build.rs (alongside the P3 `-DPORT_SEND_P3`). `nm` confirms `send_link.o` 99 no longer defines or references either symbol (still defines the other trampolines — 100 sendto_one/sendto_flag/sendto_prefix_one), the binary carries only the mangled Rust 101 `ircd_common::send::sendto_{serv,ops}_butone` (no exported C symbol), and the cref 102 oracle keeps `cref_sendto_{serv,ops}_butone` for the L1 diff. 103 - **L1.** New `ircd-testkit/tests/sendto_serv_butone_diff.rs` (mirrors `send_diff.rs` + 104 the `check_pings_diff.rs` `GLOBALS_LOCK` shared-global pattern): parallel Rust 105 `local`/`fdas` and oracle `cref_local`/`cref_fdas` worlds of fake server clients 106 (fd −1 → buffer to sendQ), drive the Rust core vs the variadic `cref_` oracle, compare 107 each recipient's sendQ bytes. 4 cases: QUIT broadcast (high-byte comment passthrough), 108 >510 truncation, **one-uplink exclusion** (the inverse — excluded server's sendQ stays 109 empty), and ops WALLOPS broadcast (sendto_flag half a no-op with no oper watchers). 110 - **L2-S2S.** The existing broadcast goldens boot reference-C + Rust and diff the wire — 111 a direct C-vs-Rust comparison of these senders. `golden_s2s_{away,quit,nick,kill, 112 wallops,membership,join,save,s_serv_connect,s_serv_squit}` all byte-identical. 113 - **Plan:** `docs/superpowers/plans/2026-06-07-p8b-sendto_serv_butone.md`. 114 115- **2026-06-07 — P8c (`sendto_serv_v`) merged.** Third variadic server-broadcast 116 trampoline ripped out, following P8a (`sendto_iauth`) and P8b (`sendto_serv_butone` + 117 `sendto_ops_butone`). 118 - **Cluster choice** — `sendto_serv_v` (send.c:541-572) is the near-identical sibling of 119 the already-ported `sendto_serv_butone`: identical `fdas.highest`→0 loop over 120 `local[fdas.fd[i]]`, skip `one`'s uplink (`cptr != one->from`) and `me`, build ONE 121 `vsendprep`'d line on the first recipient, `send_message` each. Picked as the cleanest 122 next target precisely because the delivery logic was already validated in P8b. 123 - **Config-resolved body** — the `ver` parameter gates delivery on 124 `cptr->serv->version & ver`, but that block is `#if 0`'d out in the C source ("We're 125 not using it for now … --B."), so `ver` is dead code: no recipient is ever skipped on 126 it and `rc` is never set to 1. The compiled behavior is `sendto_serv_butone` that 127 additionally takes (and ignores) `ver` and always returns 0. The Rust port keeps the 128 `_ver` parameter (call sites still pass `SV_UID`, faithful) and returns 0. 129 - **Call-graph check** — `grep`/`nm` confirmed NO live C caller: every caller (ircd.c, 130 s_serv.c, s_user.c, channel.c) is already ported and its `.o` dropped, and send.c itself 131 never calls `sendto_serv_v`. No C→C edge → ripped out alone (unlike P8b's coupled pair). 132 - **Mechanism** — `pub unsafe fn sendto_serv_v(one: *mut aClient, _ver: c_int, body: 133 &[u8]) -> c_int` in `ircd-common/src/send.rs`, cloning `sendto_serv_butone`'s body 134 (shared `prep_line` ≤510+CRLF helper) and returning 0. 10 Rust call sites converted to 135 `&wire!(…)`: ircd `s_die` (`:%s SDIE`); channel NJOIN tail (`:%s NJOIN %s :%s%s`); 136 s_user KILL propagation (`:%s KILL %s :%s!%s`) + SAVE (`:%s SAVE %s :%s%c%s`, the `%c` 137 separator → a `b"!"`/`b" "` byte slice); s_serv 4× EOB (`:%s EOB` / `:%s EOB :%s`) + 138 SDIE relay (`"%s"` over the prebuilt `buf`) + `m_sdie` (`:%s SDIE`). Bindgen import of 139 `sendto_serv_v` dropped from ircd/s_user/s_serv; the local variadic `extern` decl 140 removed from channel.rs; all four now `use crate::send::sendto_serv_v`. C body guarded 141 `#ifndef PORT_SEND_SERV_V_P8c`; `-DPORT_SEND_SERV_V_P8c` added to the `send_link.o` 142 recipe in `ircd-sys/build.rs`. The cref oracle keeps the unguarded copy → the symbol is 143 no longer a C-ABI export in the Rust binary (it is now a plain Rust fn), but 144 `cref_sendto_serv_v` survives for the L1 byte-diff. 145 - **L1** — new `ircd-testkit/tests/sendto_serv_v_diff.rs` (cloned from 146 `sendto_serv_butone_diff.rs`): drives the Rust `sendto_serv_v(one, SV_UID, body)` and 147 `cref_sendto_serv_v(one, SV_UID, "…%s…", …)` on parallel `local`/`fdas` vs 148 `cref_local`/`cref_fdas` worlds (fake servers, fd −1), diffs every recipient's sendQ. 149 Cases: EOB broadcast (one=NULL) + asserts both return 0; >510 truncation (512-byte 150 clipped line); one-uplink exclusion (fd 5 = `one->from` empty, fd 7 receives). Zero diff. 151 - **L2-S2S** — no new test needed; the wire paths are already covered byte-identical by 152 `golden_s2s_save` (SAVE), `golden_s2s_join`/`golden_s2s_membership` (NJOIN), 153 `golden_s2s_kill` (KILL), `golden_s2s_s_serv_connect`/`golden_s2s_s_serv_squit` (EOB). 154 All re-run green. `cargo build` 0 warnings. 155 156- **2026-06-07 — P8d (`sendto_match_servs` + `sendto_match_servs_v`) merged.** The 157 channel-mask server-broadcast pair (send.c:790-876) — the next clean trampoline after the 158 serv_butone/serv_v family. 159 - **Cluster choice** — both are clean leaves: `nm`/grep of send.c showed no intra-C caller 160 (their only callers, all 12, are Rust in channel.rs already). `sendto_match_servs_v` is 161 the near-identical sibling (same loop + filtering, extra dead `ver` param) so the two go 162 out together, mirroring P8b (serv_butone+ops_butone) and P8c (serv_v). 163 - **Config-resolved body** — JAPANESE OFF → `get_channelmask(x)` is `rindex(x,':')` 164 (`libc::strrchr`); the `jp_valid` continue and the `#if 0` version gate are not compiled. 165 `_v` therefore always returns rc = 0 (faithful to the dead gate). 166 - **Faithfulness** — exclusion is `cptr == from` *directly* (NOT `one->from` like 167 serv_butone); `&`-leading channel returns immediately; mask filter is `!BadPtr(mask) && 168 match(mask, cptr->name)` → skip on no-match. Line prepped once via `prep_line` (≤510 + 169 CRLF) on the first surviving recipient. Cores reuse the existing `wire!`/`prep_line` 170 machinery in ircd-common/src/send.rs. 171 - **Call sites** — 12 in channel.rs converted to `wire!` bodies: TOPIC, PART, JOIN 0 (×2), 172 MODE +o (the 5 `sendto_match_servs`); KICK (×3), NJOIN (×3, one `:%s%s` tail), MODE (the 173 7 `sendto_match_servs_v`). The two crate-wide `extern "C"` variadic decls removed; import 174 switched to `crate::send::{sendto_match_servs, sendto_match_servs_v}`. 175 - **Guard** — `#ifndef PORT_SEND_MATCH_SERVS_P8d` around both C bodies; the define added to 176 the `send_link.o` recipe in ircd-sys/build.rs. The cref oracle keeps the unguarded .o so 177 `cref_sendto_match_servs`/`_v` survive for the L1 byte-diff. 178 - **L1** — new `ircd-testkit/tests/sendto_match_servs_diff.rs`: parallel `local`/`fdas` vs 179 `cref_local`/`cref_fdas` worlds, sendQ byte-diff. 5 cases: broadcast (no channel), 180 leading-`&` early return (nothing delivered), `chan:server-mask` filtering (matching 181 server receives, non-matching stays empty — the inverse invariant), `from`-exclusion, 182 and `_v` >510 truncation + rc == 0. All green. 183 - **L2-S2S** — covered by the existing `golden_s2s_{topic,kick,membership,mode,njoin,join}` 184 (the propagation paths through these senders), all byte-identical vs reference-C. No new 185 S2S file needed — every converted path already has peer-server golden coverage. 186 187- **2026-06-07 — P8e (`sendto_prefix_one` rip-out) merged.** The first of the 188 `vsendpreprep`-based prefix senders, ported alone as a clean leaf. 189 - **Cluster choice** — `sendto_prefix_one` (send.c:1017-1027) has no in-send.c caller: 190 internal channel/match paths call the `static vsendto_prefix_one`, only external 191 callers (s_numeric/s_user/channel — all now Rust) reach the public variadic. So 192 ripping it out closes no C→C edge. It is the simplest consumer of `vsendpreprep`, so 193 it is the natural place to introduce the `prep_preprep` Rust core that the later 194 channel/match prefix senders (`sendto_channel_butone`/`_butserv`/`sendto_match_butone`/ 195 `sendto_common_channels`) will reuse. 196 - **`prep_preprep` core** — faithful port of `vsendpreprep` (send.c:388-419), IRCII_KLUDGE 197 OFF. Special path (`to && from && MyClient(to) && IsPerson(from)`): collapse the `:%s` 198 prefix arg to `:from->name!from->user->username@from->user->host` when 199 `mycmp(par, from->name) == 0`, else keep `:par`; non-special: emit `:par` + rest 200 verbatim. Builds the body into a `Vec<u8>` and hands it to the shared `prep_line` 201 (≤510 + CRLF), then `send_message`. Added `is_client`/`is_person`/`my_client` inline 202 macros + imported `mycmp` to send.rs. 203 - **Faithfulness notes** — (1) the C `!strncmp(pattern, ":%s", 3)` guard: every call site 204 leads its pattern with `:%s`, so in the non-variadic port (no pattern) the guard 205 reduces to the to/from eligibility test, with the caller supplying `par` + the 206 pre-formatted `rest`. (2) the C `from == &anon` disjunct is unreachable from the Rust 207 callers — only the still-C channel senders substitute `lfrm = &anon`, and they keep 208 using the C `vsendpreprep` — so `prep_preprep` encodes only the `mycmp` arm; the anon 209 arm lands when those senders are ported. The static `vsendto_prefix_one` + `vsendpreprep` 210 stay C for them. 211 - **Call sites (10)** — s_numeric `:%s %d %s%s` numeric relay; channel INVITE ×2 212 (`:%s INVITE %s :%s`); s_user PRIVMSG/NOTICE ×4 (`:%s %s %s :%s`, the `fmt` var stays 213 for the sibling `sendto_one` server/userhost relays) + KILL (`:%s KILL %s :%s!%s`). 214 Each passes the original first-arg as `par` and builds the post-`:%s` remainder with 215 `wire!`. Imports switched from `ircd_sys::bindings::sendto_prefix_one` (channel had a 216 local variadic extern decl) to `crate::send::sendto_prefix_one`. 217 - **Seam** — `#ifndef PORT_SEND_PREFIX_ONE_P8e` around send.c:1017-1027; 218 `-DPORT_SEND_PREFIX_ONE_P8e` added to the `send_link.o` recipe in build.rs. 219 `nm`: `sendto_prefix_one` is now a mangled Rust symbol in the binary (no C def in 220 send_link.o). 221 - **L1** — `ircd-testkit/tests/sendto_prefix_one_diff.rs`: drive Rust 222 `sendto_prefix_one(to, from, par, rest)` vs the variadic `cref_sendto_prefix_one` on 223 parallel make_client'd clients (fd 0 → MyConnect; STAT_CLIENT → MyClient), compare 224 `to->sendQ` bytes. 4 cases: special-path collapse (`par == from->name`), special-path 225 no-collapse (`par != from->name`), non-special (server `to`, not MyClient → verbatim), 226 collapse >510 truncation. (Gotcha: fixtures must NUL-terminate before the C-string 227 copy — an un-terminated `b"alice"` into `strcpy` overran `namebuf` and corrupted the 228 heap; switched to a bounded `set_cstr`.) 229 - **L2** — `golden_channel_invite` (5), `golden_s_user_message` (7), `golden_s2s_message`, 230 `golden_s_user_kill` (2), `golden_s2s_kill` all byte-identical vs reference-C. 231 - **S2S** — covered by `golden_s2s_message`/`golden_s2s_kill` (the relay paths format 232 remote-user prefixes); no new S2S file needed (the special-path collapse is a 233 local-recipient behavior, exercised by the local-client message/kill goldens). 234 235- **2026-06-07 — P8f (`sendto_match_butone` rip-out) merged.** The `$$`/`$#`-mask 236 broadcast prefix sender, ported alone as a clean leaf. 237 - **Cluster choice** — `sendto_match_butone` (send.c:935-987) has no in-send.c caller 238 (grep/`nm` of send.c shows no intra-C reference; the only live caller is `m_message` 239 in s_user.rs). So ripping it out closes no C→C edge. It is the next clean leaf after 240 P8e because it reuses the P8e `prep_preprep`/`sendto_prefix_one` core *as-is*: its 241 delivery is `vsendto_prefix_one(cptr, from, pattern, va)` with the **real** `from` 242 (never `&anon` — only the still-C channel senders substitute `&anon`), so no new anon 243 machinery is needed. 244 - **Config-resolved body** — DEBUGMODE off, JAPANESE off (verified). The loop is 245 `for i = 0; i <= highest_fd; i++` over `local[i]`: skip NULL, skip `cptr == one` (the 246 origin). For `IsServer(cptr)`: walk `cptr->prev` (the ordered dependent-client list) 247 for the first `IsRegisteredUser(srch) && srch->from == cptr && match_it(srch, mask, 248 what)`; if none, skip. Else (`my client`): require `IsRegisteredUser(cptr) && 249 match_it(cptr, mask, what)`. Each survivor → `vsendto_prefix_one`. 250 - **`match_it`** (send.c:772-782) — `MATCH_HOST` (2) matches `mask` against 251 `user->host` (an array → `.as_mut_ptr()`), `MATCH_SERVER` (1) / default against 252 `user->server` (a `*mut c_char`); `match` returns 0 on a match. `IsRegisteredUser(x)` 253 = `(status == STAT_CLIENT || STAT_OPER) && x->user`, identical to the existing 254 `is_person` helper under the locked config (reused; added a named `is_registered_user` 255 alias for the faithful C mapping). 256 - **Faithfulness** — delivery is per-recipient (the prefix collapse in `prep_preprep` 257 depends on each `to`'s `MyClient`-ness), so the line is re-prepped per recipient, 258 exactly as the C `vsendto_prefix_one` does. A server recipient is not MyClient → the 259 line is the verbatim `:par …`; a local person with `par == from->name` → the collapsed 260 `:nick!user@host`. The P8e `:%s`-lead reduction holds: the call site supplies `par` 261 (the prefix nick) + the pre-formatted `rest`. 262 - **Call site (1)** — s_user.rs `m_message`: `sendto_match_butone(one, sptr, nick.add(2), 263 syntax, fmt=":%s %s %s :%s", parv0, cmd, nick, parv2)` → `(one, sptr, nick.add(2), 264 syntax, par=parv0, rest=wire!(b" ", cmd, b" ", nick, b" :", parv2))`. Import moved from 265 `ircd_sys::bindings` to `crate::send`. 266 - **Seam** — `#ifndef PORT_SEND_MATCH_BUTONE_P8f` around send.c:935-987; 267 `-DPORT_SEND_MATCH_BUTONE_P8f` added to the `send_link.o` recipe in build.rs. The cref 268 oracle keeps the unguarded `.o` so `cref_sendto_match_butone` survives for the L1 269 byte-diff. The static `vsendto_prefix_one` + `vsendpreprep` stay C (channel senders). 270 - **L1** — `ircd-testkit/tests/sendto_match_butone_diff.rs`: parallel `local`/`highest_fd` 271 vs `cref_local`/`cref_highest_fd` worlds, sendQ byte-diff. 4 cases: (1) my-client 272 MATCH_HOST — matching client receives the collapsed line, non-matching stays empty 273 (inverse); (2) server `prev`-walk MATCH_SERVER — server with a matching dependent 274 receives the verbatim line, server with a non-matching dependent skipped (inverse); 275 (3) origin `one` excluded while another match receives; (4) an UNREGISTERED my-client 276 with a matching host is skipped (the `IsRegisteredUser` gate). All green. (Gotcha: 277 the `mask`/`par` fixtures are read as C strings — must be NUL-terminated before the 278 Rust call; the bare `b"*.ok"`/`b"alice"` literals overran into garbage and `match_` 279 missed. Added a `cstr()` helper; `cref_call` already terminated its copies.) 280 - **L2** — existing `golden_s_user_message` (7) + `golden_s2s_message` byte-identical 281 (the `m_message` routing around the `$$`/`$#` path is unperturbed). The oper-mask 282 delivery itself (needs an oper + a matching registered target) is the L1 gate's job; 283 no new S2S file needed. 284 285- **2026-06-07 — P8g (`sendto_channel_butone` rip-out + `anon` machinery) merged.** The 286 channel broadcaster, ported alongside the anonymous-identity placeholder it needs. 287 - **Cluster choice** — `sendto_channel_butone` (send.c:459-510) has no in-send.c caller 288 (its callers are the now-Rust channel/numeric/message handlers). It is the first of the 289 three vsendpreprep-based channel senders and the first to need `anon`. The other two are 290 deferred: `sendto_channel_butserv` is called variadically by the still-C `sendto_flag` 291 (send.c:1147) — ripping it out needs `sendto_flag` (247 Rust call sites) ported first; 292 `sendto_common_channels` is a clean leaf (no C caller, no anon) for its own step. 293 - **The anon call-graph constraint** — `anon` (a file-`static aClient`) is referenced by 294 `initanonymous` (def), `vsendpreprep` (line 403), `channel_butone` (468), and 295 `channel_butserv` (746). Porting `channel_butone` + `anon` to Rust while 296 `channel_butserv`/`vsendpreprep` stay C means the C side must still *reference* `anon`. 297 Solution: Rust **provides** `anon`/`ausr` as `#[no_mangle] pub static mut` external 298 symbols; the guarded-out C `static` definitions (`-DPORT_SEND_ANON_P8g`) become `extern` 299 declarations so the still-C `vsendpreprep`/`channel_butserv` resolve to the Rust globals. 300 `initanonymous` (called from the Rust boot spine) moves to Rust too. (The RED build 301 confirmed this exactly: undefined `anon` at send.c:409 (vsendpreprep) + 754 302 (channel_butserv), plus `initanonymous` + `sendto_channel_butone`.) 303 - **anon statics** — `anon: aClient`/`ausr: anUser` via `MaybeUninit::zeroed().assume_init()` 304 (the `ircst`/`cainfo` precedent); `initanonymous` zeroes them with `write_bytes` then fills 305 the identity (`name="anonymous"`, `user->{username,host}={"anonymous","anonymous."}`), 306 with the `anon.from = &anon` / `anon.name = namebuf` interior self-pointers stable because 307 a static has a fixed address (the no-move invariant). 308 - **prep_preprep** — gains the `from == anon_ptr()` disjunct alongside `par == from->name`, 309 so an anonymous-channel member sees `:anonymous!anonymous@anonymous.` while the sender's 310 own self-send (real `from`, never anon) shows their real nick. 311 - **Faithful mapping / equivalence** — the C sends the **raw** `vsendprep` line (no prefix 312 collapse) to non-eligible (server) recipients and the **prefixed** `vsendpreprep` line to 313 MyClient recipients. Routing both through the P8e `sendto_prefix_one`/`prep_preprep` core 314 is byte-identical because `prep_preprep` already branches on `my_client(to)`: a server 315 recipient (not MyClient) → else-path `:par + rest` (== `vsendprep`); a MyClient recipient 316 → the same collapse. (A local `STAT_CLIENT` always has `user` ⇒ `MyConnect && IsClient ⇒ 317 IsRegisteredUser`, so the "MyConnect server/unknown in the else branch" case is never 318 MyClient.) The `acptr->from == one`/`IsMe` skips and the `acptr != from` (don't re-send to 319 the self-sent `from`) guard are preserved exactly. 320 - **Call sites (7)** — s_numeric `do_numeric` (channel-numeric relay, `:%s %d %s%s`); 321 channel `can_join` (invite-overriding-ban + invite-overriding-limit NOTICEs), `reop_channel` 322 (the `+%c (%d)` enforce NOTICE — the `%c` mode char passed as a 1-byte `&[ch]` WirePart), 323 `set_mode` (the two anonymous-flag warning NOTICEs); s_user `m_message` (channel 324 PRIVMSG/NOTICE, `:%s %s %s :%s`). Each builds the post-`:%s` `rest` with `wire!`. 325 - **Seam** — `#ifndef PORT_SEND_CHANNEL_BUTONE_P8g` around send.c:459-510 and 326 `#ifndef PORT_SEND_ANON_P8g` around the anon statics/`initanonymous` (with the `#else` 327 extern decls); both `-D`s added to the `send_link.o` recipe in build.rs. The cref oracle 328 keeps the unguarded `.o` (its `anon` stays an internal static; `cref_initanonymous` 329 survives for L1). 330 - **L1** — `ircd-testkit/tests/sendto_channel_butone_diff.rs`: parallel Rust-world / 331 cref-world channels (each its own `aChannel` + member `Link` chain + clients, since 332 `channel_butone` takes the channel by arg), sendQ byte-diff. 4 cases: (1) broadcast — a 333 local registered member collapses, a server member gets the verbatim `:par`, an `IsMe` 334 member is skipped; (2) `one` exclusion — a member whose `from == one` is empty while 335 another receives (inverse); (3) self-send — a local registered `from` (one==null) gets 336 exactly ONE line and the loop skips `acptr == from` (not doubled); (4) anonymous channel — 337 a client `from` makes the member see `:anonymous!anonymous@anonymous.` while `from`'s own 338 self-send shows the real nick. All four call their resp. `initanonymous`. (Gotchas: the 339 cref oracle formats into process-global `sendbuf`/`psendbuf` + `anon`/`ausr` are globals → 340 a `GLOBALS_LOCK` mutex serializes the parallel `#[test]`s; and `STAT_ME == -1`, not -2, 341 which is `STAT_UNKNOWN`.) 342 - **L2** — channel goldens join (5) / part (4) / kick (2) / topic (2) / mode_set (1) / 343 invite (5) + s_user_message (7) all byte-identical vs reference-C. 344 - **S2S** — golden_s2s message (1) / join (2) / kick (2) / topic (1) / mode (2) 345 byte-identical (the server-recipient verbatim-`:par` path + remote-user prefix formatting). 346 347- **2026-06-07 — P8h (`sendto_common_channels`) merged.** Ripped out the local-channel-peers 348 broadcaster variadic trampoline (send.c:620-729), the clean leaf after P8g. 349 - **Cluster choice** — `sendto_common_channels` has no in-send.c caller; its live callers are 350 the now-Rust `exit_client` (s_misc, QUIT) + the two NICK-change paths in s_user. It sends a 351 line to every person on this local server sharing a non-Quiet, non-Anonymous channel with 352 `user`, plus `user` itself when local — so peers see a NICK/QUIT once. 353 - **No anon** — unlike P8g's `sendto_channel_butone`, this sender explicitly skips anonymous 354 channels (`if IsAnonymous(chptr) continue`), so the per-recipient prefix source is always the 355 real `user`, never `&anon`. No new anon machinery. 356 - **The `len`-caching subtlety (why NOT `sendto_prefix_one` per recipient)** — C computes 357 `vsendpreprep` **once** (the `if (!len)` guard, DEBUGMODE off) into the global `psendbuf` and 358 reuses that buffer for every `send_message`. The prepped content depends on the FIRST 359 recipient's MyClient-ness. In branch 1 (`highest_fd<50`) all recipients are local (MyClient) 360 so the cache is moot, but in branch 2 a `chptr->clist` member can be **remote** (not MyClient) 361 and still receives the cached *collapsed* line. So the faithful Rust port holds a local 362 `[u8;520]` buf + `len` and calls `prep_preprep(to_or_user, user, par, rest, &mut buf)` only 363 when `len==0`, then `send_message(cptr, buf, len)` — re-prepping per recipient via the public 364 `sendto_prefix_one` (which re-runs `prep_preprep` against each `to`) would diverge in branch 2. 365 - **`sentalong[cptr->fd]` (branch 2) — remote fd = -1** — remote clients have `fd==-1` 366 (`make_client`), so C's `sentalong[cptr->fd]` does `*(sentalong-1)`, a pre-existing OOB read C 367 tolerates as pointer arithmetic. Ported as a per-call `vec![0 as c_int; MAXCONNECTIONS]` 368 indexed via raw `.offset((*cptr).fd as isize)` into its base — matches C's arithmetic exactly 369 (no panic, same bytes). Branch 2 is only entered at `highest_fd>=50` (untested at L1). 370 - **Faithful mapping** — `from == user` for every call and the pattern leads `:%s` with 371 `par == user->name`, so MyClient recipients collapse to `:nick!user@host` (== `prep_preprep`) 372 and non-MyClient (server) recipients get `:par`. Both Comstud size-gated branches ported 373 verbatim (the HUB `IsMember`-over-`user->user->channel` walk + the big-client 374 `clist`/`sentalong` walk). New private `is_member`/`is_quiet` helpers mirror channel.rs (via 375 the P2-ported `find_channel_link`). 376 - **Call sites (3)** — s_misc `exit_client` (`:%s QUIT :%s` → par=`(*sptr).name`, rest= 377 `wire!(b" QUIT :", comment)`); s_user nick-save (`:%s NICK :%s` → par=`(*sptr).name`, rest= 378 `wire!(b" NICK :", uid)`); s_user `m_nick` (par=`parv0`, rest=`wire!(b" NICK :", nick)`). 379 Removed `sendto_common_channels` from the local `extern "C"` blocks in both files; imported 380 `crate::send::sendto_common_channels`. 381 - **Seam** — `#ifndef PORT_SEND_COMMON_CHANNELS_P8h` around send.c:620-729 **and** around the 382 now-unused `sentalong` static (only that function referenced it); `-D` added to the 383 `send_link.o` recipe in build.rs. The cref oracle keeps the unguarded `.o` for L1. 384 - **L1** — `ircd-testkit/tests/sendto_common_channels_diff.rs`: parallel rust / cref `local[]` 385 + `highest_fd` worlds with per-client `user->channel` Link lists, per-recipient sendQ 386 byte-diff. 4 cases: (1) shared open channel — user self-send (MyConnect) + a co-member both 387 collapse, a client in a different channel + a server in a local slot are skipped (inverse); 388 (2) quiet shared channel — co-member skipped, user still self-sends; (3) anonymous shared 389 channel — co-member skipped (no `&anon` substitution); (4) remote `user` (fd=-1) — no 390 self-send, a local co-member still receives (line prepped from the first recipient since 391 `len` starts 0). A `GLOBALS_LOCK` serializes the parallel `#[test]`s (process-global 392 `local`/`highest_fd` + the oracle's `psendbuf`). 393 - **L2** — `golden_s_user_nick` (5) + `golden_s_user_quit` (4) byte-identical (NICK change + 394 QUIT broadcast to common channels). 395 - **S2S** — `golden_s2s_nick` + `golden_s2s_quit` (2) byte-identical (the broadcast only 396 touches *local* members, so S2S exercises the surrounding propagation routing, not a new 397 branch in this sender — no dedicated S2S path). 398 399- **2026-06-07 — P8i (`sendto_flog` logfile cluster) merged.** Ripped out the 400 `sendto_flog` logger — the first **non-variadic** P8 leaf (the memory flagged it "port 401 plainly"). It writes one formatted line per client exit to a logfile fd. 402 - **Cluster choice** — `sendto_flog` reads the file-private `userlog`/`connlog` fd statics 403 (send.c:1202-1203), set by `logfiles_open` (1205) and cleared by `logfiles_close` (1262). 404 Those three functions + the two statics form the connected component over the shared fds, 405 so they port together. `setup_svchans`/`svchans` (1060-1139) stay C — `svchans` is a 406 *separate* component shared with the still-C variadic `sendto_flag` (send.c:1141, 247 407 blocked call sites). Under the locked config (`LOG_SERVER_CHANNELS` OFF) the svchans 408 open/close loops in `logfiles_open`/`logfiles_close` are not compiled, so the {userlog, 409 connlog} component is cleanly separable. 410 - **Config-resolved body** — `LOG_OLDFORMAT` OFF → the new `"%c %d %d %s %s %s %s %d %s %lu 411 %llu %lu %llu "` format (the `#else`). `USE_SYSLOG`/`USE_SERVICES` OFF → the syslog + 412 `check_services_butone` USERLOG/CONNLOG relay blocks gone; the 413 `!USE_SERVICES && !(USE_SYSLOG&&…)` early-return (`if logfile==-1 return`) IS compiled. 414 `LOGFILES_ALWAYS_CREATE` OFF → `open()` has no `O_CREAT` (file must pre-exist). 415 `FNAME_USERLOG`/`FNAME_CONNLOG` set (per-object `-D`s on send.o's recipe). 416 - **Formatting = byte-identical by construction** — the Rust `sendto_flog` calls 417 `libc::sprintf` with the *exact same* C format string + args cast to match C's varargs 418 promotions (`%c`/`%d`→`c_int`, `%s`→`*c_char`, `%lu`→`c_ulong`, `%llu`→`c_ulonglong`; 419 `(u_int)firsttime`/`(u_int)timeofday` via `as u_int as c_int`). Same libc engine + same 420 format ⇒ no formatting logic to drift. Independently checked the rendered line against a 421 standalone C `sprintf` of the same values. 422 - **FNAME paths into Rust** — `FNAME_USERLOG`/`FNAME_CONNLOG` are recursively-expanded 423 Makefile vars (per-object `-D`s on send.o, absent from bindgen). build.rs expands both via 424 make and exports `IRCD_USERLOG_PATH`/`IRCD_CONNLOG_PATH` rustc-envs (the `PID_PATH` 425 trick); `ircd-sys` re-exposes them as `USERLOG_PATH`/`CONNLOG_PATH` consts; the Rust 426 `logfiles_open` opens those exact paths so it hits the identical files the reference-C 427 build does. 428 - **Seam** — `#ifndef PORT_SEND_FLOG_P8i` wraps send.c:1202-1440 (the two statics + the 429 three fns); `-DPORT_SEND_FLOG_P8i` added to the `send_link.o` recipe in build.rs. The cref 430 oracle keeps the unguarded `send.o` so `cref_logfiles_open`/`cref_sendto_flog` survive 431 for L1. Symbols confirmed Rust-owned: `nm target/debug/ircd | grep -E ' T (sendto_flog| 432 logfiles_open|logfiles_close)$'`. 433 - **L1** — `ircd-testkit/tests/sendto_flog_diff.rs`, the **write_pidfile pattern** 434 (s_bsd_leaves_diff.rs): both worlds open the same hardcoded `USERLOG_PATH`/`CONNLOG_PATH`, 435 so drive `logfiles_open → sendto_flog → logfiles_close` in the rust (`c_*`) and cref 436 (`cref_*`) worlds, read the file back, assert identical bytes. `O_CREAT` off → pre-create 437 the file empty; the pre-create failing (sandbox `/usr/local/var/log` absent) mirrors the 438 `open()` failing → both `userlog`/`connlog` stay -1 → `sendto_flog` early-returns → both 439 files absent (`None == None`). On a configured box the exact bytes are pinned 440 differentially. 2 cases: `EXITC_REG`→userlog, a non-REG code→connlog. `timeofday` set on 441 both `ircd_sys::bindings::timeofday` (rust) and `cref_timeofday` (oracle); a `GLOBALS_LOCK` 442 serializes the parallel `#[test]`s (shared logfile paths + globals). Both pass. 443 - **No L2 / no S2S** — `sendto_flog` writes a file, never the wire, and `USE_SERVICES` is OFF 444 (the USERLOG/CONNLOG service relay is not compiled) → nothing reaches a golden client; not 445 a `msgtab` handler. Boot smoke: `logfiles_open` runs at every ircd boot, so 446 `golden_s_user_quit` (4) staying byte-identical confirms the boot/rehash path is intact. 447 - **Caller** — one live Rust caller of `sendto_flog`: `s_bsd.rs add_connection` (clone 448 reject, `EXITC_CLONE`) via the bindgen decl, which now resolves to the Rust symbol. 449 `logfiles_open`/`logfiles_close` resolve their ircd.rs/s_conf.rs bindgen callers likewise. 450 451- **2026-06-07 — P8j (the keystone `sendto_flag` + `dead_link`) merged.** Ripped out the 452 server-notice broadcaster `sendto_flag` (send.c:1141), the largest remaining trampoline 453 (~220 call sites across 16 ircd-common files), plus `dead_link` (its last C caller). 454 - **Cluster choice / connected component** — `svchans[SCH_MAX]` is a **file-static** in 455 send.c shared by `setup_svchans` (populates each `svc_ptr` via `find_channel`) and 456 `sendto_flag` (reads `svc_ptr`), so porting sendto_flag pulls in `svchans` + `setup_svchans` 457 as one unit (the P8b/P8i precedent). `sendto_channel_butserv` stays C (still variadic) — the 458 Rust core calls it via the bindgen variadic decl; it is channel_butserv's only C caller, so 459 channel_butserv is unblocked as the next step. `dead_link` (send.c:50, variadic, exposed for 460 the Rust send core) was the **last C caller of sendto_flag**, so it ports here to close that 461 edge — its only callers are the already-Rust send_message/send_queued. 462 - **Config-resolved bodies** — `sendto_flag`: USE_SERVICES off → the check_services_butone 463 NOTICE/ERROR relay gone; LOG_SERVER_CHANNELS off → the `(svchans+chan)->fd` logfile write 464 gone; reduces to clamp → svchans lookup → `sendto_channel_butserv(chptr,&me,":%s NOTICE %s 465 :%s",ME,chname,nbuf)`. `setup_svchans`: LOG_SERVER_CHANNELS off → only the find_channel loop 466 compiled. `svchans` initializer: CLIENTS_CHANNEL off → 14 entries (ERROR..WALLOP, OPER), 467 SCH_MAX == 14. `dead_link`: DEBUGMODE off → the trailing `Debug()` is a no-op. 468 - **svchans as a Rust static** — `#[no_mangle] pub static mut svchans: [SChan; SCH_MAX]`, names 469 via `b"&...\0"` byte statics so the `svc_chname` pointers fill in const context (a `const fn 470 sch()` helper). `setup_svchans` is `#[no_mangle] extern "C"` so the still-bindgen caller 471 (channel.rs rehash) resolves to it. 472 - **wire! / WirePart growth** — the call sites carry more than `%s`/`%d`: tally was 298 `%s`, 473 75 `%d`, 8 `%08x`, 6 `%u`, 6 `%02d`, 4 `%x`, 3 `%p`, 3 `%2d`, 2 `%X`, 1 `%c` (+ 4 `%#x` the 474 tally regex missed). Added `impl WirePart for c_uint` (`%u`) and pub newtypes `Hex08`/`Hex`/ 475 `HexUp`/`HexAlt`(`%#x`)/`Dec02`/`Dec2`/`Chr`/`Ptr`. Integer specifiers render via Rust 476 `format!` (byte-identical to C for these specs — verified `{:08x}`/`{:02}`/`{:2}` etc.); 477 `%#x` and `%p` defer to `libc::snprintf` so they match glibc exactly (e.g. `%#x` of 0 → `0`, 478 not `0x0`; `%p` of NULL → `(nil)`). 479 - **Faithfulness of the ~220 conversions** — a string-aware checker reconstructed each new 480 `wire!` skeleton (literal byte-runs + specifier markers) and compared it to the original C 481 format string: **203 call sites byte-faithful**, 0 real mismatches. Edge cases handled: 482 s_bsd `report_error` takes a *runtime* format param (not a literal) → rendered via 483 `libc::sprintf` with the runtime format then handed the bytes (exactly what the old variadic 484 did); the one NULL `%s` (s_service, server-always-NULL path) renders the literal `(null)` 485 glibc's vsprintf emits; the `%#x` debug sentinels (list/whowas refcnt loops) use `HexAlt`. 486 - **Seam** — `#ifndef PORT_SEND_FLAG_P8j` wraps send.c:1057-1200 (svchans + setup_svchans + 487 sendto_flag); `#ifndef PORT_SEND_DEAD_LINK_P8j` wraps send.c:50-76 (dead_link); both `-D`s 488 added to the `send_link.o` recipe in build.rs. The cref oracle keeps the unguarded send.o so 489 `cref_sendto_flag`/`cref_setup_svchans`/`cref_dead_link` survive for L1. Symbols confirmed: 490 `nm target/debug/ircd` shows `setup_svchans` T + `svchans` D Rust-owned, and send_link.o no 491 longer defines sendto_flag/setup_svchans/dead_link. 492 - **L1** — `ircd-testkit/tests/sendto_flag_diff.rs` drives the Rust `sendto_flag` vs 493 `cref_sendto_flag` through each world's `setup_svchans` + a `&NOTICES` channel inserted into 494 its channel hash (`add_to_channel_hash_table`) with one local member, diffing the member 495 `sendQ`. 4 cases: basic `:me NOTICE &NOTICES :text` wrap, mixed `%d/%s/%u` call-site 496 rendering, `chan >= SCH_MAX` clamp to SCH_NOTICE, and the NULL-svc_ptr no-op inverse. A 497 `GLOBALS_LOCK` serializes (svchans/me/channel-hash are process-global). All 4 pass; the P8b– 498 P8i send-core L1 suite still green. 499 - **L2** — server-notice channels (`&NOTICES` etc.) are never created in the golden scenarios, 500 so `svc_ptr` is NULL and `sendto_flag` is a no-op there; the value of the golden run is 501 confirming the ~220 call-site conversions did **not** change any *non-notice* wire output. 16 502 golden binaries byte-identical: registration, s_user_nick (5), s_user_quit (4), channel 503 join/part/kick/topic, s2s nick/quit/kill/join/eob/server/squit/njoin/s_serv_connect. No 504 dedicated L2 path for the notice text itself (no `&`-channel membership in the harness). 505 506- **2026-06-07 — P8k (`sendto_channel_butserv`) merged.** Ripped out the channel 507 local-members broadcaster `sendto_channel_butserv` (send.c:746-780) — the relay that pushes 508 TOPIC/PART/KICK/JOIN/MODE (and the anonymous PART, and `sendto_flag`'s server notices) to the 509 members of a channel **connected to this server**. 510 - **Unblock / cluster choice** — P8j made `sendto_flag` Rust; that was channel_butserv's only 511 in-`send.c` caller, so it became the next clean leaf. Unlike P8g `sendto_channel_butone`, 512 butserv never reaches a server recipient (the loop keeps only `MyClient(acptr)` — that's the 513 "butSERV"), so every recipient is MyClient with a constant `lfrm`. That makes the 514 per-recipient `sendto_prefix_one` (the P8e `prep_preprep` core) **byte-identical** to the C 515 `psendbuf`/`if (!len)` single-prep cache — no need to replicate the cache. 516 - **Config-resolved body** — JAPANESE off (no `jp_valid`), DEBUGMODE off (no `Debug()`): 517 `MyClient(from)` self-send first via the real (un-anonymized) prefix, then `if IsQuiet(chptr) 518 return`; `IsAnonymous(chptr) && IsClient(from)` → `lfrm = &anon` (the P8g anon globals, now 519 Rust); loop `chptr->clist` delivering one line to each `MyClient(acptr) && acptr != from`. 520 - **Rust core** — `pub unsafe fn sendto_channel_butserv(chptr, from, par: *const c_char, rest: 521 &[u8])` in `ircd-common/src/send.rs`, right after `sendto_channel_butone`. Reuses 522 `sendto_prefix_one`/`prep_preprep`/`is_quiet`/`is_anonymous`/`anon_ptr`/`my_client`. 523 - **Seam** — `#ifndef PORT_SEND_CHANNEL_BUTSERV_P8k` over channel_butserv (746-780) AND its 524 now-orphaned static helpers `vsendpreprep` (405-444) + `vsendto_prefix_one` (fwd decl 34 + 525 def 1057-1065) + the `psendbuf[2048]` buffer (35): channel_butserv was their last unguarded 526 caller (the other callers — channel_butone/common_channels/prefix_one/match_butone — were 527 already guarded out in P8e-h). `vsendprep`/`sendbuf` stay live (`vsendto_one`); the 528 `anon`/`ausr` externs stay (harmless unused externs in send_link.o). `-D` added to the 529 send_link.o recipe in build.rs. cref oracle keeps the unguarded send.o so 530 `cref_sendto_channel_butserv`/`cref_initanonymous` survive for L1. 531 - **Call sites (14)** — `par` = the `:%s` prefix arg, `rest` = everything after `:%s` rebuilt 532 with `wire!`. channel.rs: TOPIC, PART×3, KICK (`%s %s :%s`), JOIN (`:%s`), JOIN-burst 533 (`%s%s`), MODE×5 (incl. the `+%s%c %s %s` op-grant via `crate::send::Chr`); s_misc.rs: the 534 anonymous PART (`:%s PART %s :None`); send.rs: the `sendto_flag` wrapper now calls the Rust 535 core directly (`body_cstr` still truncates at NUL to match the old `%s`). The local variadic 536 `extern fn sendto_channel_butserv` decls in channel.rs + s_misc.rs replaced with `use 537 crate::send::sendto_channel_butserv`; the stale bindgen import dropped from send.rs. 538 - **L1** — `ircd-testkit/tests/sendto_channel_butserv_diff.rs` builds a channel with a `clist` 539 of members (a local `from`, a local co-member, a remote client, a server) in two parallel 540 worlds and diffs each member's `sendQ` against `cref_sendto_channel_butserv`. 4 cases: (1) 541 basic — `from` self-send + co-member receive collapsed, remote+server skipped (the "butserv" 542 inverse); (2) quiet — `from` self-sends then the early return suppresses the loop; (3) 543 anonymous — `lfrm=&anon` so the co-member gets `:anonymous!anonymous@anonymous.` while the 544 self-send keeps the real prefix (both worlds' `anon` populated via 545 `initanonymous`/`cref_initanonymous` — their `anon` statics are separate, and the path needs 546 `IsPerson(&anon)`); (4) remote `from` — no self-send, co-member still receives. All 4 pass; 547 `sendto_flag_diff` (4) still green. 548 - **L2** — 14 golden binaries byte-identical: channel join/part/kick/topic/mode_set/modes + 549 s2s join/topic/kick/mode/njoin — confirming the 14 call-site conversions moved no wire 550 output. (No dedicated notice-channel L2: `&`-server-channels aren't created in the harness.) 551 552- **2026-06-07 — P8l (`sendto_one` keystone) merged.** The last broadcast/notice variadic 553 sender. `sendto_one(to, pattern, ...)` was the single-client sender behind ~every numeric 554 reply; ~437 textual hits → 435 real call sites across 15 ircd-common files. 555 - **Core.** Non-variadic `pub unsafe fn send::sendto_one(to: *mut aClient, body: &[u8]) -> 556 c_int` = `vsendprep` (the libc-`vsprintf` ≤510 truncation + CRLF into a local `[u8;520]`) 557 + `send_message`, returning the prepped length the few `rlen +=` callers read. The Rust 558 fn is a plain (mangled) module fn, so it coexisted with the still-defined C `sendto_one` 559 symbol throughout the per-file conversion — the tree stayed green after every file, and 560 only the final `#ifndef PORT_SEND_ONE_P8l` guard-flip removed the C trampoline. 561 - **Call-site mechanism.** Two builders. (1) `wire!` for `c"..."` literal patterns whose 562 specifiers are all in the P8j WirePart vocab (`%s %d %u %c %x %X %08x %#x %p %02d %2d`) — 563 58 sites. (2) A new `reply_one!(to, fmt, args...)` macro (send.rs) for `reply(...)` and 564 runtime-`char*` patterns — 377 sites: it renders `fmt`+args through libc `snprintf` into 565 a 2048-byte scratch buffer (oversized so its own cap never bites before the core's 510), 566 then hands the C string to the core. Byte-identical by construction — `vsendprep` used 567 libc `vsprintf`, the same engine, so any printf specifier (incl. `%lu`/`%ld`/`%lld`/width/ 568 precision) and NULL `%s` (`(null)`) match exactly. The 2 `rlen += sendto_one(...)` 569 accumulator sites (channel.rs ~1556/1583) call the core directly via an inline 570 snprintf-into-`__rbuf` block (the macro returns `()`). 571 - **Faithfulness finding.** PLAN/[[p8-started]] marked sendto_one "blocked on leveva 572 typed-numerics (P10)" on the premise "most sites use a runtime `reply(i)` fmt." That 573 conflated two cases: only ~6 sites use a genuinely-runtime `reply(var)` 574 (`reply(num)` channel.rs:4752, `reply(cj as u32)` :2754, `reply(rpl)`/`reply(tmp_rpl)`/ 575 `reply(tmp_rpl2)`, `reply(REPORT_ARRAY[idx][1])` s_serv.rs:2580). The other ~430 576 `reply(ERR_CONST)`/literal sites have a compile-time-known format. And because the 577 delivery path formats via libc `vsprintf` (NOT the custom `irc_vsprintf`), the 578 `reply_one!`/snprintf bridge is faithful for ALL of them — so sendto_one was rippable now 579 without the P10 numeric layer (the idiomatic leveva-`Numeric` conversion of the reply 580 sites stays P11). 581 - **Conversion.** A 15-agent ultracode workflow (one agent per ircd-common file, edit-only 582 to avoid the shared-build-dir race; central verify after) converted all sites + removed 583 each file's `extern "C" { fn sendto_one(...,...); }` decl (and the `sendto_one` entry from 584 class.rs's bindgen `use`). `ircd-common` compiled clean first try. 585 - **Guard.** `sendbuf` (send.c:31), `vsendprep` (385), `vsendto_one`+`sendto_one` (450-468) 586 wrapped in `#ifndef PORT_SEND_ONE_P8l`; `-DPORT_SEND_ONE_P8l` added to the send_link.o 587 recipe in build.rs. The other `sendbuf`/`vsendprep` uses (send.c:501-946) are inside 588 already-P8b…P8k-guarded senders, so the cut is clean. cref oracle keeps `sendto_one` 589 unguarded for the L1 diff. 590 - **L1.** New `sendto_one_diff.rs`: Rust core (+wire!/reply_one!) vs `cref_sendto_one` on 591 parallel fresh clients, sendQ byte-diff; 5 cases (wire! literal, reply_one! `%s` template, 592 `%d`/`%02d` numerics, NULL `%s`, >510 trunc). A GLOBALS_LOCK serializes — the oracle's 593 `cref_sendbuf` + the shared dbuf free-list are process globals (the first run failed with 594 cref content bleeding between parallel #[test]s; the [[p7-l1-shared-global-race]] pattern). 595 - **L2.** Full 111-test golden suite byte-identical; the only 2 failures are the documented 596 [[p5-s2s-stats-flake]] pair (`golden_s2s_s_serv_stats` `sq`, `golden_s_misc` `Sq/Yg/Fl`) 597 — uninitialized per-process counter garbage, identical code path to before, not sendto_one 598 output (every other numeric line in those very tests matches byte-for-byte). 599 600- **2026-06-08 — P8m (the `esendto_*` cluster, ircd/s_send.c) merged.** The last variadic 601 SENDER trampolines — the UID-aware "extended" service-routing senders — ripped out into a 602 faithful non-variadic Rust cluster. 603 - **Cluster choice.** s_send.c was the only TU still defining variadic sender trampolines 604 (`esendto_one`/`esendto_serv_butone`/`esendto_channel_butone`/`esendto_match_servs`) after 605 P8a–P8l drained send.c. Picked as the next (and last) sender rip-out per the user's "keep 606 ripping out the variadic trampolines" directive. 607 - **Classification / config.** USE_SERVICES is OFF → `nm cbuild/s_send.o` shows the 4 public 608 senders + the file-statics (esend_message/build_old_prefix/build_new_prefix/build_suffix), 609 and `grep` confirms ZERO callers in both the remaining C and ircd-common/src. So: no 610 call-site conversion, **no L2/L2-S2S path** (services never run on the wire) — L1 is the 611 gate. build_prefix (s_send.c:147-200) is `#if 0`'d → not compiled, not ported. 612 - **Port mechanism.** New ircd-common/src/s_send.rs (`pub mod s_send;` + S_SEND_LINK_ANCHOR 613 in link_anchor()). The four buffers (oldprefixbuf/newprefixbuf/prefixbuf/suffixbuf[2048]) + 614 six length ints (oldplen/newplen/plen/slen/maxplen/lastmax) are module-private `static mut` 615 (not ABI; addr_of_mut! access); CLEAR_LENGTHS is a helper run per public call. The variadic 616 `fmt, ...` collapses to `suffix: &[u8]` = the bytes vsprintf produced; build_suffix stores 617 suffix + CRLF + the trailing `'0'` sentinel (faithful; length-bounded so never on the wire). 618 Each public esendto_X is a plain `pub unsafe fn` (NOT extern "C": the `&[u8]` body param is 619 not FFI-safe and there are zero C callers — matches the P8b..P8l body-taking-sender 620 convention). Delivery via crate::send::send_message. 621 - **Faithfulness calls.** (1) The `:%s %s %s` prefix builders (build_old_prefix/ 622 build_new_prefix) and the hardcoded `:anonymous!anonymous@anonymous. %s %s` local-member 623 prefix defer to **libc::sprintf** with the exact C format strings rather than `wire!` — 624 strictly more faithful: it reproduces glibc's `(null)` rendering for the NULL `oname` 625 build_new_prefix can pass when `dname != NULL`, the same libc-for-format-semantics precedent 626 as P8j (`%#x`/`%p`) and P8l (reply_one!/snprintf). (2) The loop-index decrement is placed to 627 preserve the C `for(...; i--)` semantics under `continue`: esendto_match_servs decrements 628 right after fetching `cptr` (its body has `continue`s), esendto_serv_butone at loop end (its 629 body is a single `if`). The maxplen/lastmax + `maxplen+slen>512 → slen=510-maxplen` suffix 630 truncation, the UID selection + `newplen=-1` bail, and the `&`-channel return + 631 get_channelmask + match() mask filter + BadPtr guard are all replicated exactly. 632 - **build.rs.** "s_send.o" added to PORTED (dropped outright — no s_send_link.o, no -DPORT_* 633 guard); it stays in CREF_OBJS so the cref oracle keeps cref_esendto_* for L1. Verified 634 `ar t libircd_c.a` no longer lists s_send.o while libcref.a still exports the cref_ symbols. 635 - **L1.** New ircd-testkit/tests/s_send_diff.rs (template = sendto_serv_butone_diff.rs + 636 sendto_channel_butone_diff.rs): each cref_esendto_X(..., c"%s", payload) variadic oracle vs 637 the Rust core on parallel local[]/fdas + channel worlds, byte-comparing every recipient 638 sendQ via dbuf_map; GLOBALS_LOCK serializes the cref's process-global statics. 9 cases cover 639 the keystone branches AND inverses (new vs old prefix, the newplen=-1 bail, one-exclusion + 640 IsMe skip, the anonymous-vs-server prefix split + orig/one skips, the &-channel return + mask 641 match()/non-match, and the >512 suffix truncation). 9/9 zero-diff. 642 - **Gate.** `cargo build -p ircd-rs` 0 warnings; s_send_diff 9/9; golden_registration 2/2 643 byte-identical (confirms dropping s_send.o didn't disturb the link/wire). No L2/S2S (services 644 unreachable under the locked config — documented above). Verified by a 5-agent ultracode 645 workflow: 1 implementer + 3 adversarial faithfulness auditors (all returned "faithful", zero 646 findings) + 1 golden smoke. 647 - **Status.** With the esendto_* cluster gone, **every variadic sender trampoline in the tree 648 is now Rust.** The remaining C is non-sender: data globals (ircd.c `me`/timers, s_bsd.c 649 `local[]`/`timeofday`/`highest_fd`, s_auth.c `iauth_*`), the support remnants 650 (snprintf_append/dgets/make_isupport/ipv6string/minus_one), and send_link.o's lone `rcsid`. 651 Next P8 work is porting those data globals + support remnants (then dropping all cc::Build, 652 mothballing the L1/L2/cref oracle, migrating tests into ircd-common). 653 654- **2026-06-08 — P8n (s_auth.c iauth data globals) merged.** The first P8 *data-global* drop — all 655 variadic sender trampolines were already Rust (P8a–P8m), so P8 now turns to the residual C data 656 globals. `s_auth.o` is the cleanest: `nm --defined-only s_auth_link.o` showed it defines ONLY the 657 five iauth globals (+ module-private `rcsid`), every function already `-DPORT_S_AUTH_*`-guarded out. 658 - **Cluster / scope** — the five s_auth.c file-scope globals: `iauth_options` (`u_char`), 659 `iauth_spawn` (`u_int`), `iauth_version` (`char*`), `iauth_conf`/`iauth_stats` (`aExtCf*`/`aExtData*` 660 = bindgen `LineItem*`). All written/read by the now-Rust `read_iauth` + the two `report_iauth_*` 661 reporters (this file); `iauth_options`/`iauth_spawn` additionally read by the iauth-timeout checks 662 in ircd.rs/s_user.rs/s_bsd.rs. 663 - **Mechanism** — defined them as `#[no_mangle] pub static mut` in `ircd-common/src/s_auth.rs`, 664 zero/NULL-initialized exactly as the C BSS defs. Removed the two now-redundant local `extern "C"` 665 decls (the `iauth_conf`/`iauth_stats` block + the `iauth_version` block) and dropped `iauth_options` 666 from the `use ircd_sys::bindings::{…}` import (it would otherwise collide with the new module def); 667 repointed the lone `ircd_sys::bindings::iauth_spawn` ref to the local def. The OTHER modules keep 668 their `ircd_sys::bindings::iauth_options`/`iauth_spawn` imports untouched — those are bindgen 669 `extern "C" { static mut … }` *declarations* that resolve to the Rust *definitions* at link, the 670 same data-symbol seam P8g used for `anon`/`ausr` and P8j for `svchans`. 671 - **build.rs** — `s_auth.o` added to `PORTED` (so `link_objs()` filters it out before the 672 `… => "s_auth_link.o"` map, which was removed as dead); the whole `s_auth_link.o` second-compile 673 step (the `-DPORT_S_AUTH_REPORT_P7u … -DPORT_S_AUTH_SENDTO_IAUTH_P8a` chain) deleted. `s_auth.o` 674 stays in `CREF_OBJS` (the full unguarded compile) so the `cref_*` oracle symbols survive for L1. 675 - **No new test / no L2 path beyond boot** — pure data globals have no behavioral L1 diff of their 676 own; the gate is the existing iauth differentials that exercise the globals end-to-end. `read_iauth_diff` 677 is load-bearing here: it drives the Rust `read_iauth` (which mutates all five) against `cref_read_iauth` 678 and byte-diffs the resulting conf/stats lists + client state, so a mis-wired global would fail it. 679 - **Gate** — `nm target/debug/ircd` shows all five as Rust BSS defs, none undefined. L1: 680 `read_iauth_diff` (10) + `iauth_reporters_diff` (6) + `sendto_iauth_diff` (2) + `start_iauth_diff` (3) 681 all zero-diff. L2: `golden_registration` (2) byte-identical. `cargo build --workspace` 0 warnings. 682 - **Remaining P8 C** — `ircd_link.o` (`me` + the dorehash/timers/CLI data globals; `me` has the 683 interior self-pointer hazard), `s_bsd_link.o` (`local[]`/`highest_fd`/`timeofday`/fd globals), 684 `support_link.o` (`ipv6string`/`minus_one` data + `dgets`/`make_isupport` fns + the variadic 685 `snprintf_append`, the last entangled with the P11 `irc_sprintf` deletion), and `send_link.o`'s lone 686 module-private `rcsid` (trivially droppable). Then: drop all `cc::Build`, mothball the oracle, migrate tests. 687 688- **2026-06-08 — P8o (`send.o` dropped outright) merged.** The second P8 data-global drop, and 689 the trivial one flagged at P8n's close. 690 - **Scope** — `send.c` is now FULLY ported: over P3d (delivery core) + P8b..P8l every sender — 691 `sendto_serv_butone`/`sendto_ops_butone`/`sendto_serv_v`/`sendto_match_servs[_v]`/ 692 `sendto_prefix_one`/`sendto_match_butone`/`sendto_channel_butone`/`sendto_common_channels`/ 693 `sendto_channel_butserv`/`sendto_flag`/`sendto_one` — plus `dead_link`, the `sendto_flog` 694 logfile cluster, the `svchans`/`setup_svchans` component, and the `anon`/`ausr`/`initanonymous` 695 machinery were `#ifdef`'d out of `send_link.o` and reimplemented in `ircd-common/src/send.rs`. 696 - **Why droppable now** — `nm --defined-only cbuild/send_link.o` showed exactly one symbol: 697 `rcsid` (lowercase `r` = local, internal-linkage `static const char[]` version string, referenced 698 by nothing). Every live `send.c` symbol comes from Rust. So `send_link.o` contributed nothing to 699 the link except a dead static. 700 - **Mechanism** — added `"send.o"` to `PORTED` in `ircd-sys/build.rs` (so `link_objs()` filters it 701 out); deleted the `"send.o" => "send_link.o"` arm from the link-map `match`; deleted the entire 702 `send_link.o` second-compile `run(make … --eval=send_link.o: …)` step plus its long 703 `-DPORT_SEND_P3 … -DPORT_SEND_ONE_P8l` comment block, replacing it with a short P8o note. cref 704 keeps the unguarded `send.o` in `CREF_OBJS` (native Makefile recipe, FNAME_* macros expanded) so 705 the `cref_*` send oracle survives for L1. 706 - **FNAME paths** — the Rust `logfiles_open` (ported P8i) reads `FNAME_USERLOG`/`FNAME_CONNLOG` 707 via the `IRCD_USERLOG_PATH`/`IRCD_CONNLOG_PATH` rustc-envs (set elsewhere in build.rs), so dropping 708 the send_link.o compile (which previously also carried the FNAME_* `-D`s) changes nothing. 709 - **Classification** — pure drop, no new Rust and no behavioral L1 diff of its own (send_link.o was 710 already inert at link). Gate = the existing send differentials + a golden smoke. 711 - **L1** — all 14 send differentials zero-diff (57 tests): `send_diff` (9), `sendto_one_diff` (5), 712 `sendto_flag_diff` (4), `sendto_channel_butone_diff` (4), `sendto_channel_butserv_diff` (4), 713 `sendto_serv_butone_diff` (4), `sendto_serv_v_diff` (3), `sendto_match_servs_diff` (5), 714 `sendto_match_butone_diff` (4), `sendto_prefix_one_diff` (4), `sendto_common_channels_diff` (4), 715 `sendto_flog_diff` (2), `sendto_iauth_diff` (2), `s_send_diff` (9). 716 - **L2** — `golden_registration` (2) byte-identical (exercises the full boot send path). 717 - **Verify** — `nm target/debug/ircd` shows `logfiles_open`/`svchans` as Rust defs and no 718 undefined send symbols (`sendto_one`/`sendto_flag` are now plain Rust `fn`s, not C-ABI symbols). 719 `cargo build` (full workspace) 0 warnings. 720 - **Remaining P8 C** — `ircd_link.o` (`me` + the dorehash/timers/CLI data globals; `me` has the 721 interior self-pointer hazard), `s_bsd_link.o` (`local[]`/`highest_fd`/`timeofday`/fd globals), 722 `support_link.o` (`ipv6string`/`minus_one` data + `dgets`/`make_isupport` fns + the variadic 723 `snprintf_append`, the last entangled with the P11 `irc_sprintf` deletion). Then: drop all 724 `cc::Build`, mothball the oracle, migrate tests. 725 726- **2026-06-08 — P8p (`s_bsd.o` dropped outright) merged.** The third P8 data-global drop, the 727 `s_bsd.c` event-loop TU. 728 - **Cluster choice / scope** — `s_bsd.c` had all its *logic* ported over P7d..P7ff (the 729 select/poll event loop, listeners, add_connection/connect_server, start_iauth/daemonize, the 730 socket/file leaves). `nm --defined-only cbuild/s_bsd_link.o` then showed it defined ONLY data 731 globals: `local` (the `aClient *local[MAXCONNECTIONS]` fd→client slab), `fdas`/`fdall` (FdAry 732 poll/select sets), `highest_fd`, `readcalls`, `udpfd`/`resfd`/`adfd` (-1-init fds), `timeofday` 733 (the cached wall clock), `mysk` (the outgoing-bind source addr, de-static'd P7s) + the 734 module-private `rcsid`. So the TU is a pure data-global drop, the same shape as P8n (s_auth.o) 735 and P8o (send.o). 736 - **The defs** — moved all ten to `ircd-common/src/s_bsd.rs` as `#[no_mangle] pub static mut` 737 with byte-exact C initializers (s_bsd.c:51-55): `local = [null; MAXCONNECTIONS=50]`; 738 `fdas`/`fdall = FdAry{fd:[0;50],highest:0}`; `highest_fd`/`readcalls = 0`; `udpfd`/`resfd`/ 739 `adfd = -1`; `timeofday = 0`; `mysk = MaybeUninit::<sockaddr_in6>::zeroed()`. `nm` confirms 740 `adfd`/`resfd`/`udpfd` land in `.data` (D, =-1) and the rest in BSS (B) — matching the C object. 741 - **Data-symbol seam** — the other modules (send/parse/s_user/s_auth/s_service/s_send) keep their 742 `ircd_sys::bindings::{local,highest_fd,fdas,fdall,timeofday,…}` bindgen `extern static mut` refs; 743 those decls resolve to these Rust defs at link (the data case of the function-symbol seam, proven 744 for `anon`/`ausr` in P8g, `svchans` in P8j, the iauth globals in P8n). s_bsd.rs's own full-path 745 `ircd_sys::bindings::X` / `b::X` reads resolve the same way — no in-module name clash because 746 they are distinct paths from the new module statics. 747 - **Two in-module clears** — (1) dropped the old `extern "C" { static mut mysk: libc::sockaddr_in6 }` 748 decl in `get_my_name`'s block (now the module static); (2) renamed `read_message`'s local var 749 `let local = local_base()` → `local_p` (E0530: a `let` cannot shadow the new static); also pruned 750 `adfd`/`highest_fd` from `start_iauth`'s function-local `use ircd_sys::bindings::{…}` so it reads 751 the module statics directly. 752 - **build.rs** — `s_bsd.o` added to `PORTED`; deleted the whole `s_bsd_link.o` second-compile 753 (the `make --eval` rule carrying the long `-DPORT_S_BSD_LEAF_P7d .. -DPORT_S_BSD_READ_MESSAGE_P7ff` 754 chain + the IRCDPID/IAUTH path defines) and the `"s_bsd.o" => "s_bsd_link.o"` arm in `link_objs()`. 755 The Rust `write_pidfile`/`start_iauth` still read IRCDPID_PATH/IAUTH_PATH/IAUTH via the existing 756 `IRCD_PID_PATH`/`IRCD_IAUTH_PATH`/`IRCD_IAUTH` rustc-envs. cref keeps the unguarded `s_bsd.o` 757 (still in CREF_OBJS) so `cref_local`/`cref_highest_fd`/`cref_mysk`/… survive for L1. 758 - **Gate (pure-data drop)** — no behavioral L1 diff of the globals themselves; leaned on the 759 existing differentials that drive them end-to-end: `read_message_diff` (event loop reads 760 `local[]`/`fdas`/`highest_fd`/`timeofday`), `add_connection_diff` (writes `local[]`/`highest_fd`/ 761 `fdas`/`fdall`), `close_connection_diff`/`close_listeners_diff`, `list_diff` (add_fd/del_fd mutate 762 `fdas`/`fdall`), `s_bsd_leaves_diff`, `connect_server_diff`, `check_client_diff`, `setup_ping_diff`, 763 `send_ping_diff`, `check_pings_diff`, `get_my_name_diff` (writes `mysk` vs `cref_mysk`), 764 `init_sys_diff`, `daemonize_diff`, `start_iauth_diff`, `delayed_kills_diff`, 765 `calculate_preference_diff` — all zero-diff. Cross-module wire seam checked via `sendto_one_diff`/ 766 `sendto_flag_diff`/`sendto_common_channels_diff` (they read `local` through the seam). Boot path 767 via `golden_registration` (full event loop over the globals) byte-identical. `nm target/debug/ircd`: 768 all 10 globals resolve as Rust B/D defs, none `U`. `cargo build` 0 warnings. 769 770- **2026-06-08 — P8q (`ircd.o` dropped outright — the `ircd.c` data globals) merged.** The 771 fourth P8 pure-data-global drop, completing the `ircd.c` migration begun at P7c. 772 - **Cluster choice / why now.** Over P7c..P7oo every *function* in `ircd.c` was ported to 773 `ircd-common/src/ircd.rs` behind the `-DPORT_IRCD_*` guard chain. `nm --defined-only 774 cbuild/ircd_link.o` then showed **no `T` symbols** — only data globals + the module-private 775 `rcsid` — proving the logic is fully drained. This is the same terminal state that triggered 776 P8n (`s_auth.o`), P8o (`send.o`), P8p (`s_bsd.o`): a `*_link.o` that defines only data, ready 777 to be dropped outright by moving those data globals to Rust statics. 778 - **The globals (ircd.c:31-96).** `me` (aClient), `client` (= &me), `istat`/`iconf`, 779 `myargv`/`sbrk0`/`ListenerLL` (NULL), `rehashed`/`firstrejoindone` (0), `serverbooting` (1), 780 `portnum`/`debuglevel` (-1), `bootopt` (BOOT_PROT|BOOT_STRICTPROT = 768), 781 `configfile`/`tunefile` (IRCDCONF_PATH/IRCDTUNE_PATH), `dorehash`/`dorestart`/`restart_iauth` 782 (volatile int = 0), and the event timers `nextconnect`/`nextgarbage`/`nextping`/`nextexpire`/ 783 `nextiarestart`/`nextpreference` (= 1) + `nextdnscheck`/`nexttkexpire`/`nextdelayclose` (= 0). 784 - **The `me` self-pointer hazard — a non-issue at definition.** `aClient me;` is a BSS 785 all-zero def; `client = &me`. The interior self-pointers (`me.name → me.serv->namebuf`) are 786 set at *runtime* by the already-ported `make_server`/`setup_me`, NOT at definition. So the 787 Rust static is just `MaybeUninit::<aClient>::zeroed().assume_init()` (the same const-zeroed 788 pattern P8p used for `mysk`), and `client` initializes to `core::ptr::addr_of_mut!(me)` in 789 const. The "never move/copy `me` by value" invariant is already honored tree-wide 790 (`addr_of_mut!(me)` / `ircd_sys::bindings::me` everywhere), so nothing else changes. 791 - **DATA-symbol seam.** The bindgen-surfaced globals (`me`/`client`/`istat`/`iconf`/`myargv`/ 792 `rehashed`/`portnum`/`configfile`/`debuglevel`/`bootopt`/`serverbooting`/`firstrejoindone`/ 793 `sbrk0`/`tunefile`/`ListenerLL` + the `next*` timers) are referenced by the other modules 794 (s_bsd/s_user/…) through their `ircd_sys::bindings::*` `extern static mut` decls, which 795 resolve to these `#[no_mangle]` defs at link — the proven seam (`local`/`anon`/`svchans`/the 796 iauth globals). No source changes outside ircd.rs for those. 797 - **The non-bindgen five.** `dorehash`/`dorestart`/`restart_iauth` (signal flags, de-static'd 798 P7kk) and `nextpreference`/`nextiarestart` (io_loop timers) are absent from any `*_ext.h`, so 799 they were reached via two local `extern "C"` blocks in ircd.rs (one at the signal-handler 800 cluster, one inside `io_loop`). Both blocks removed; the symbols are now module statics and 801 the bare-name references inside ircd.rs resolve to them directly. 802 - **Import clash.** ircd.rs both *imported* these globals from bindgen (for its own use) and 803 now *defines* them, so the three `use ircd_sys::bindings::{…}` lists were trimmed of the 804 now-defined names: `bootopt`/`iconf`/`istat`/`me`/`myargv`/`rehashed`/`sbrk0`/`tunefile` 805 (block @20), the six `next*` timers (block @34), and `configfile`/`serverbooting` (the inner 806 `c_ircd_main` block). 807 - **Path defaults.** `configfile = IRCDCONF_PATH` / `tunefile = IRCDTUNE_PATH` are 808 recursively-expanded Makefile path vars (absent from bindgen). build.rs now expands them via 809 `make --eval` (the `IRCD_MOTD_PATH`/`IRCD_PID_PATH`/`IRCD_SERVER_PATH` pattern) and emits 810 `IRCD_CONF_PATH`/`IRCD_TUNE_PATH` rustc-envs; ircd-sys re-exports NUL-terminated 811 `CONF_PATH_Z`/`TUNE_PATH_Z` consts (the `concat!`+`env!` must run in ircd-sys), and the Rust 812 `*mut c_char` statics init from `.as_ptr()`. The C never writes *through* these pointers 813 (only reassigns on `-f`/`-T`), so pointing at a `'static` byte string is faithful. Verified: 814 `strings target/debug/ircd` shows `/usr/local/etc/ircd.conf` + `/usr/local/var/run/ircd.tune`. 815 - **build.rs.** `ircd.o` added to `PORTED`; the `ircd_link.o` `make` second-compile (the long 816 `-DPORT_IRCD_TUNE_P7c .. -DPORT_IRCD_MAIN_P7oo` chain + the `-DIRCD*_PATH` machine paths) and 817 the `"ircd.o" => "ircd_link.o"` link-map arm deleted. The cref oracle keeps the unguarded 818 `ircd.o` (still in CREF_OBJS, native Makefile recipe) so `cref_me`/`cref_try_connections`/ 819 `cref_dorehash`/… survive for the L1 differentials. 820 - **Gate (pure-data drop, per P8n/o/p — no new test; the existing differentials are the 821 gate).** The ircd L1 differentials that exercise these globals end-to-end — 822 `try_connections_diff` (8), `check_pings_diff` (5), `calculate_preference_diff` (5), 823 `delayed_kills_diff` (6), `ircd_signals_diff` (5, writes `dorehash`/`dorestart`/ 824 `restart_iauth`), `ircd_tune_diff` (9), `ircd_cli_helpers_diff` (3), `setup_signals_diff` (1), 825 `activate_delayed_listeners_diff` (2) — all zero-diff vs `cref_`; `golden_registration` (2) 826 byte-identical (boots the Rust ircd, which now owns `me`/the timers/`configfile`/`tunefile`). 827 `nm target/debug/ircd` shows all ~26 globals resolving as Rust defs (B/D, none `U`) with the 828 correct `.data` (non-zero inits) / `.bss` (zero inits) split; the cref `ircd.o` still carries 829 49 symbols (oracle intact). `cargo build --workspace` 0 warnings. 830 - **Remaining C.** Only `support_link.o` keeps C *logic* — the format remnants 831 `dgets`/`make_isupport`/`snprintf_append` + the data `ipv6string`/`minus_one` (and the 832 P1-deferred `irc_sprintf` engine, to be deleted not ported in P11). Every other TU is now 833 Rust or pure-data-dropped. 834 835- **2026-06-08 — P8r (`support.o` dropped outright — the LAST C-logic TU) merged.** The 836 final P8 TU drop. After P8n–q drained the data-global `.o`s, `support_link.o` was the only 837 remaining C translation unit defining *logic*; this drops it. 838 - **Cluster choice / why now.** `nm --defined-only support_link.o` showed exactly: 839 `dgets`/`make_isupport` (`T`), `snprintf_append` (`T`, variadic), `ipv6string` (`B`), 840 `minus_one` (`D`), + the module-private `rcsid`/`dgbuf.0`. The pure subset 841 (`mystrdup`/`strtoken`/`myctime`/`inetntop`/… + the `MyMalloc` trio) was already Rust from 842 P1c/P2e behind `-DPORT_SUPPORT_P1 -DPORT_SUPPORT_P2`. So three real ports + two data globals 843 remained. 844 - **`snprintf_append` — dead in Rust, not ported.** Its only ircd caller was `s_user.c`'s 845 WHOX builder, which the Rust port (`s_user.rs:387`) already replaced with a field-by-field 846 `Vec<u8>` builder. `grep` confirms the sole Rust reference is a comment → no live caller → 847 it simply died with the `.o` (no trampoline needed). Stable Rust can't define a variadic 848 `extern "C"` anyway; this sidesteps that entirely. (`irc_sprintf`, the deferred varargs 849 engine, is likewise deleted-not-ported, in P11.) 850 - **`dgets` port.** Faithful translation of the `\r`/`\n`-line reader with `\`-continued- 851 newline splicing (support.c:702-808). C uses `static char dgbuf[8192]` + `head`/`tail` 852 *pointers*; ported with head/tail as byte *offsets* into a module-private `static mut DGBUF` 853 (behaviour-identical — the statics are private to `dgets`), pulled into locals for the body 854 and written back before every return. `index()`→a scan-to-NUL helper, `bcopy()``ptr::copy`, 855 `read()``libc::read`. The subtle bit: the odd-backslash de-escape loop advances `s`/`t` 856 through the `*s++ = *t++` copy and then does `t = s`, so the scan resumes from the post- 857 splice position (end) exactly as C does — replicated verbatim (separate copy cursors would 858 have diverged on multi-continuation buffers). 859 - **`make_isupport` port.** Two `MyMalloc(BUFSIZE)` token strings + a 3-slot `MyMalloc` 860 pointer array (support.c:810-840, `#ifndef CLIENT_COMPILE` always active in the daemon). 861 The `sprintf` format args are config macros resolved to the locked config (`common/*.h`, 862 not surfaced by bindgen): `MAXMODEPARAMS=3 MAXCHANNELSPERUSER=21 LOCALNICKLEN=15 863 TOPICLEN=255 MAXBANS=64 CHANNELLEN=50 CHIDLEN=5`, `BUFSIZE=512` — baked as named consts. 864 `tis[1]` is built byte-faithfully (raw `copy_nonoverlapping` of the base + optional 865 ` NETWORK=` + the bindgen `networkname` bytes) to avoid any UTF-8 reinterpretation of 866 `networkname`. 867 - **Data globals via the seam.** `ipv6string: [c_char;46] = [0;46]` (BSS) and 868 `minus_one: [c_uchar;17] = [255;16]+[0]` (`.data`) defined `#[no_mangle] pub static mut`. 869 The cross-module reads (`res`/`s_bsd`/`s_conf`/`s_serv`/`s_auth`/`send`/`s_misc`/`s_user`) 870 reach them through their bindgen `extern static mut` decls — the proven DATA-symbol seam 871 (`local`/`me`/`svchans`/iauth). bindgen surfaces `minus_one` as `[c_uchar;0]` (incomplete 872 array) — the size mismatch is irrelevant (callers take the address + copy 16 bytes); the 873 linker resolves by name. No source changes outside `support.rs`. 874 - **build.rs.** `support.o` added to `PORTED`; the `support_link.o` second-compile and the 875 `"support.o" => "support_link.o"` arm in `link_objs()` deleted — `link_objs()` is now a plain 876 `CREF_OBJS \ PORTED` set difference (no `*_link.o` variants remain anywhere). cref keeps the 877 unguarded `support.o` in `CREF_OBJS` so `cref_dgets`/`cref_make_isupport`/`cref_mystrdup`/… 878 survive for the L1 differentials. 879 - **Milestone — every ircd C TU is now Rust.** With `support.o` dropped, `CREF_OBJS \ PORTED` 880 is empty: the product link set (`libircd_c.a`) holds only the generated `version.o` (a 881 version-string constant) + the L1 harness's `ctruth.o`. All ircd *logic and data* is Rust. 882 Remaining P8 work: drop all `cc::Build` (including regenerating `version.c` as Rust), 883 mothball the L1/L2 oracle after a final green run, and migrate the load-bearing tests into 884 `ircd-common`. 885 - **Gate.** New L1 differentials in `support_diff.rs`: `dgets_matches_reference` drives both 886 `dgets`/`cref_dgets` over independent pipes carrying plain `\n` lines, an odd-backslash 887 continuation (spliced), an even-backslash run (newline kept), a `\r` byte, an EOF tail with 888 no trailing newline, and empty input — asserting the identical (ret, bytes) stream incl. the 889 `dgets(_,_,0)` reset; `make_isupport_matches_reference` walks the `char**` to NULL and 890 asserts identical token strings (networkname NULL → no NETWORK= token). Both zero-diff; full 891 `support_diff` (7 tests) zero-diff. `golden_registration` (2) byte-identical — the 005 892 RPL_ISUPPORT numeric flows through `make_isupport`. Seam reads checked via 893 `s_conf_match_ipmask_diff` (5) + `res_gethost_diff` (8) zero-diff. `nm target/debug/ircd`: 894 `dgets`/`make_isupport` resolve `T`, `ipv6string` `B`, `minus_one` `D` (none `U`), 895 `snprintf_append` absent. `cargo build --workspace` 0 warnings. 896 897- **2026-06-08 — P8s (L1 test migration to ircd-common) merged.** Migrated all 115 of 898 ircd-testkit's `cref_`-oracle L1 differential tests into self-contained 899 `ircd-common/tests/<name>_snap.rs` insta-snapshot characterization tests — the PLAN's 900 "migrate the load-bearing tests into ircd-common" step that unblocks mothballing the oracle. 901 - **Mechanism.** Each new test drives ONLY the Rust port: original-name `extern "C"` 902 decls resolve to ircd-common's `#[no_mangle]` defs, pulled onto the test binary's link 903 line by `ircd_common::link_anchor()` (ircd-sys's whole-archive `version.o`/`ctruth.o` 904 come transitively, so bindgen types still resolve). All `cref_*` externs and 905 `ircd_testkit::link_reference_archives()` deleted. `insta = "1"` added as a dev-dependency. 906 - **Soundness (capture chain).** Step 0 of every conversion confirms the matching 907 `ircd-testkit --test <name>_diff` is green at capture time (Rust == cref). Combined with 908 the freshly-captured `snapshot == Rust`, that gives `snapshot == cref golden` — so the 909 frozen `.snap` carries the reference-C correctness forward with no C oracle in the loop. 910 These are now regression/characterization gates for the P9–P12 idiomatic passes. 911 - **Dual-World → single-world.** The complex tests (list/hash/res_*/s_conf_*/channel_*/ 912 the sendto_* senders) built two parallel worlds (cref + Rust) and asserted field-equality. 913 Collapsed to a single Rust world snapshotting the observable state over the SAME corpus + 914 branches + inverses (no coverage shrink). 915 - **Determinism.** Never snapshot raw pointers/addresses/timestamps/fd-numbers/ephemeral 916 ports/uninitialized memory/HashMap order. Self/interior-pointer assertions 917 (`name==namebuf`, `from==self`, list linkage) became derived invariants (bools, offsets, 918 name/id walk-sequences, buffer bytes). Clock interposition (`gettimeofday` shims) and 919 process-global `Mutex` serialization were preserved from the originals. Verified by two 920 clean re-runs per file + three full-suite re-runs (stably green). 921 - **Process.** Hand-proved the pipeline on `s_err` (table snapshot) + `s_id` (pure-fn 922 tuples), then a 6-file pilot spanning every tier (`s_user_canonize`/`dbuf`/`patricia`/ 923 `list`/`res_hash`/`channel_modes`), then a multi-agent Workflow over the remaining 107. 924 The first 107-wide fan-out tripped the org token-rate limit (64 agents 429'd); the 65 925 affected were redone in throttled sequential batches of 8 with zero failures. Conversion 926 guide: `docs/superpowers/plans/2026-06-08-p8-test-migration-guide.md`. 927 - **Cleanup.** Removed run-1 misnamed `*_diff_snap.rs` duplicates + their orphan snapshots; 928 silenced `dead_code` on the snapshot-shape structs (fields read only via `#[derive(Debug)]`) 929 with a file-level `#![allow(dead_code)]` in the 34 affected files; dropped one unused import. 930 - **Gate.** 115 `_snap.rs` test files / 707 committed `.snap` fixtures, exact 1:1 coverage 931 (no missing/extra/orphan), `ircd-common` suite green and stable across repeated runs, 932 `cargo build --workspace` 0 warnings. `ircd-testkit` + the `cref_`/`ircd-golden` oracle are 933 left intact for now (still the differential proof); dropping all `cc::Build` (incl. 934 regenerating `version.c` as Rust) and mothballing the oracle is the remaining P8 work. 935 936- **2026-06-08 — P8t (version.c → Rust) merged.** Ported `ircd/version.c` — the last 937 *generated* C translation unit — to `ircd-common/src/version.rs`, then deleted its 938 generation + compile from `ircd-sys/build.rs`. 939 - **Scope / classification.** `version.c` is pure data (`nm version.o` = only `D`/`B`/`r`, 940 no `T`): `char *generation`, `char *creation`, `char *pass_version`, `char *infotext[]`, 941 `char **isupport`, + the module-private `rcsid` static. It is *generated* by 942 `ircd/version.c.SH` (bumps `generation`, embeds a `date` `creation`, expands `PATCHLEVEL` 943 + source-file checksums). Not in `CREF_OBJS``version.o` was archived straight into the 944 product `libircd_c.a`, so there is no `cref_*` oracle for it and no L1 differential. 945 - **Mechanism (data-symbol seam, as P8n–P8q).** Defined the five globals as 946 `#[no_mangle] pub static mut` in `version.rs` + a `VERSION_LINK_ANCHOR` (added to 947 `lib.rs::link_anchor()`). The consuming modules (`s_serv` `m_info` walks `infotext` and 948 prints `creation`/`generation`; `s_user` RPL_CREATED; `s_debug`/`s_bsd` read 949 `isupport`/`pass_version`) reach them through their existing `ircd_sys::bindings::*` 950 `extern static mut` decls — which come from `s_externs.h`, independent of which `.o` 951 compiles — so they resolve to the Rust defs at link. `infotext`'s bindgen decl is an 952 incomplete array `[*mut c_char; 0]`; the real length (46, NULL-terminated) lives only in 953 the Rust def, walked via `addr_of!(infotext)` (the `local[]`/`tolowertab[]` pattern). 954 - **Version sourcing (user-directed): crate metadata at build time.** `generation` = 955 `concat!(env!("CARGO_PKG_VERSION"), "\0")`; `creation` = `env!("IRCD_BUILD_CREATION")`, 956 emitted by a new `ircd-common/build.rs` that formats `SystemTime::now()` into the C-like 957 `Wkd Mon D YYYY at HH:MM:SS UTC` shape (dependency-free Hinnant civil-from-days; UTC to 958 avoid host-TZ nondeterminism; pinned via a lone `rerun-if-changed=build.rs` so it only 959 re-stamps on clean/build.rs-edited builds). `pass_version` = the bindgen `PATCHLEVEL` const 960 (`b"0211030000\0"`); `infotext[]` copied byte-for-byte from the generated C (including the 961 trailing C source-checksum lines). `isupport` = NULL (runtime-filled by `make_isupport`). 962 - **build.rs drop.** Removed the `version.c.SH` generation block, the `version.o` compile 963 block, and `ar.arg("version.o")` from `ircd-sys/build.rs`; updated the `CREF_OBJS` doc 964 comment. `link_objs()` was already empty (every hand-written TU ported), so `libircd_c.a` 965 now contains **only** the L1 harness `ctruth.o`. 966 - **Canonicalizer.** `creation` is now genuinely build-time volatile (the reference-C 967 `version.o` stamp vs the Rust crate-metadata stamp legitimately differ), so added a 003 968 RPL_CREATED masking rule to `ircd-golden`'s `canonicalize()` (`:This server was created 969 <MASKED>`), the exact analogue of the existing 371 Birth-Date rule that masks the same 970 `creation`/`generation` tokens in `/INFO`. No snapshot captures the value — the only 971 `.snap` mentioning it (`s_err` replies table) holds the static format *template* 972 `":%s 003 %s :This server was created %s"`, unaffected. 973 - **Gate.** `cargo build --workspace` 0 warnings; `nm target/debug/ircd` shows 974 `generation`/`creation`/`pass_version`/`infotext` as `D` and `isupport` as `B` (Rust defs, 975 none `U`), `version.o` absent from the archive (`ar t` = `ctruth.o` only); string values 976 confirmed (build stamp / `0211030000` / `IRC --`). `ircd-common` snapshot suite green (117 977 test-result groups). Golden suite 58 pass + the one known pre-existing 978 `golden_s2s_s_serv_stats` flake (reference-C uninitialized-sendq garbage: 979 `…3544950802210619392kB sq` vs Rust `0kB sq` — STATS, not version; documented in the 980 p5-s2s-stats-flake memory). No version-touching golden diverged. 981 - **No S2S path concern.** Pure data globals; `m_info`/RPL_CREATED already covered by 982 `golden_s_serv_info_links`/`golden_registration` (both green post-mask). 983 984- **2026-06-08 — P8u (FINAL: drop all C compilation + mothball the L1/L2 oracle) merged. P8 COMPLETE.** 985 With every ircd C translation unit already Rust (P8r dropped the last C-logic TU; P8t the 986 last *generated* one), the only remaining P8 work was retiring the differential oracle that 987 had proven each port. This step ran it one last time, then deleted the C build. 988 - **Final differential run (the gate).** `cargo test -p ircd-testkit` (L1 `cref_*` unit 989 diffs) — exit 0, all pass. `cargo test -p ircd-golden` (L2 wire-output golden vs the 990 reference-C binary) — 86 pass, 1 fail: `golden_s2s_s_serv_stats::s2s_stats_match_reference`, 991 which is the **documented pre-existing flake** ([[p5-s2s-stats-flake]]): reference-C emits 992 uninitialized-sendq garbage (`…3544950540217614336kB sq`) where the Rust port correctly 993 emits `0kB sq` — i.e. Rust is the *correct* side, reference-C is wrong. Not a regression. 994 This is the last time the reference-C oracle runs; its verdict is captured in git history. 995 - **Dropped all C compilation from `ircd-sys/build.rs`.** Removed: the `make CREF_OBJS` 996 object build; the `RES_CACHE_EXPOSE` cref `res.o` recompile; the `ctruth.o` layout-truth 997 compile; the `libircd_c.a` archive (`ar`); the whole-archive link directive 998 (`static:+whole-archive=ircd_c`) and `-lz`/`-lm`/`-lcrypt`; and the `cargo:objs`/`cargo:cbuild` 999 metadata. The `CREF_OBJS`/`PORTED`/`link_objs`/`EXTRA_CFLAGS` constants + the `makefile_var` 1000 helper are gone. **Link-lib finding:** none of `z`/`m`/`crypt` are needed by the Rust 1001 workspace — no Rust code calls `crypt`/zlib, and `pow` resolves from libc (`pow@GLIBC_2.29`; 1002 glibc merged libm in 2.29), so the pure-Rust binary links clean without them. 1003 - **What `build.rs` still does (no C *compiled*).** Two things still read the C *source tree*: 1004 (1) **bindgen** over `wrapper.h``ircd-common` still uses the bindgen struct/type view of 1005 the ABI (`aClient`/`dbuf`/`Message`/…), replaced with native Rust types only in P9–P12; this 1006 parses the unmodified `*_def.h`/`*_ext.h` *headers*, so `configure` is still run purely to 1007 emit `setup.h`/`config.h` (read via `-I cbuild`). (2) The **install-path exports** — a new 1008 `make_var()` helper expands the recursively-evaluated Makefile path vars 1009 (`IRCDCONF_PATH`/`IRCDMOTD_PATH`/`FNAME_USERLOG`/`IAUTH_PATH`/…) that embed machine-specific 1010 install locations the Rust runtime still defaults to. `make --eval` does var expansion only — 1011 nothing is built. So the C *source headers* stay in-tree as bindgen input; the `.c` files are 1012 no longer compiled (the reference-C tree is "mothballed" in the build-the-oracle sense). 1013 - **Mothballed the oracle crates.** `ircd-testkit` (L1) and `ircd-golden` (L2) can no longer 1014 build (no reference C to compile / link), so both are `exclude`d from the workspace `members` 1015 in the root `Cargo.toml` — left on disk (git-recoverable), not deleted, with a note. The 1016 `ircd-sys`-local drift-net — `ircd-sys/tests/layout.rs` (asserted bindgen vs the real compile 1017 via `ctruth.o`'s `cref_sizeof_*`) and its C source `ircd-sys/ctruth.c` — is deleted: with no 1018 C compiled there is nothing for bindgen's view to drift *against*. The load-bearing L1 1019 characterization the oracle anchored lives on as the self-contained `insta` snapshots in 1020 `ircd-common/tests/*_snap.rs` (P8s), which carry the reference-C correctness forward with no 1021 C in the loop. 1022 - **Doc updates.** `ircd-sys/src/lib.rs` module doc rewritten (no longer "compiles the C 1023 tree"); the `c_ircd_main` extern decl's doc notes it now resolves to `ircd-common`'s 1024 `#[no_mangle] c_ircd_main` (P7oo) rather than the `-Dmain=c_ircd_main` C `main`. 1025 - **Gate.** `cargo metadata` shows `ircd-testkit`/`ircd-golden` gone (exclude works); 1026 `cargo build --workspace` 0 warnings, the `ircd` binary links pure Rust (boots to the usage 1027 banner; `nm` shows `c_ircd_main` as `T`, zero `cref_`/`ctruth` symbols, no `ircd_c` link 1028 directive in the current build-script output); `cargo test --workspace` 698 pass / 0 fail 1029 across 125 test binaries. **The workspace is now 100% Rust — zero C TUs compiled. P8 is 1030 COMPLETE; the migration's faithful mechanical port is done end-to-end. Next: P9 (std-library 1031 cleanup), verified against the `ircd-common` snapshot suite.** 1032 1033- **2026-06-08 — P8v (C source tree deleted; `ircd-sys` retired) merged.** The literal end of C 1034 in the repo. P8u dropped all C *compilation* but left `ircd-sys` alive to run `configure` + 1035 bindgen over the C *headers* (the struct view `ircd-common` still builds against). P8v removes 1036 that last dependency on C files and deletes `ircd-sys` itself. 1037 - **Froze the bindgen view.** Captured the generated `bindings.rs` (4953 lines, rust-bindgen 1038 0.72.1 over `os.h`+`s_defines.h`+`s_externs.h`+`irc_sprintf_ext.h` under the locked configure 1039`USE_IAUTH`/`TKLINE`/`ENABLE_CIDR_LIMITS` on, `AFINET=AF_INET6`, `MAXCONNECTIONS=50`) and 1040 committed it verbatim as `ircd-common/src/bindings.rs` with a provenance header + the original 1041 `#![allow(non_camel_case_types, …, clippy::all)]`. It is self-contained (no `include!`/`env!`/ 1042 external-crate refs; the `extern "C"` blocks resolve at link to this crate's own `#[no_mangle]` 1043 defs — the seam is unchanged, just no longer regenerated). The idiomatic-Rust passes (P9–P12) 1044 replace these bindgen types with native types field by field; until then this is the frozen ABI. 1045 - **Self-alias seam (zero src churn).** Rather than rewrite `ircd_sys::bindings::*` across all 28 1046 ported modules, `ircd-common/src/lib.rs` gained `pub mod bindings;` + `extern crate self as 1047 ircd_sys;`, so every existing `ircd_sys::bindings::X` / `ircd_sys::MOTD_PATH` path now resolves 1048 to the in-crate module / the frozen install-path consts. The 11 install-path consts 1049 (`MOTD_PATH`/`PID_PATH`/`USERLOG_PATH`/`CONNLOG_PATH`/`IAUTH_PATH`/`IAUTH`/`SERVER_PATH`/ 1050 `CONF_PATH`/`TUNE_PATH`/`CONF_PATH_Z`/`TUNE_PATH_Z`) — formerly `env!`-injected by `ircd-sys`'s 1051 `build.rs` via `make --eval` — are frozen as literals at the locked `./configure` defaults 1052 (`/usr/local/...`), since the Makefile they were expanded from is now deleted. 1053 - **Deletions.** `ircd-sys` crate (build.rs/wrapper.h/src/lib.rs); the mothballed oracle crates 1054 `ircd-testkit` (L1) and `ircd-golden` (L2); `iauth-rs/tests/differential_c.rs` (the C-iauth 1055 handshake differential — it only ever diffed against C-iauth, now gone; iauth-rs's per-module 1056 `#[cfg(test)]` units remain the gate); and the **entire C source tree**: `common/`, `ircd/`, 1057 `iauth/`, `support/` (Makefile.in + configure + config.h.dist), `contrib/`, `cbuild/`, the root 1058 `configure` wrapper, `.clang-format`/`.clang-format-ignore`, and the now-purposeless 1059 `.github/workflows/clangformat.yml` (the repo's only CI, a C formatter). Dropped `/cbuild` from 1060 `.gitignore`. `doc/` is kept (reference docs, not C). 1061 - **Rewiring.** `ircd-rs` dropped its `ircd-sys` dep and now calls `ircd_common::ircd::c_ircd_main` 1062 directly (the P7oo Rust boot spine; the stale "still entirely C core" main.rs doc was corrected). 1063 Workspace `members` lost `ircd-sys`; the `exclude` list (the two oracle crates) is gone with them. 1064 - **Clash fixes.** Putting the bindgen decls and the modules' local cross-module forward-decls in 1065 one crate exposed 4 `clashing_extern_declarations`: `unregister_server` / 1066 `del_from_hostname_hash_table` / `del_from_ip_hash_table` were forward-declared `-> ()` in 1067 s_misc.rs but really return `c_int` (s_serv.rs/hash.rs), and `mystrdup` was forward-declared 1068 `*const c_char` in s_misc.rs + res.rs but really takes `*mut c_char` (support.rs). Aligned the 4 1069 decls to the real signatures; the one affected call site (`mystrdup(line.as_ptr())` in the MOTD 1070 loader) gained an `as *mut c_char` cast. No behaviour change — same symbols, same link. 1071 - **Gate.** `cargo build --workspace` 0 warnings, the `ircd` binary links pure Rust; no `ircd_sys` 1072 crate in `cargo metadata`; `grep` finds no C source files in the tree; `cargo test --workspace` 1073 695 pass / 0 fail (was 698 — the delta is the deleted `ircd-sys` smoke test + the C-iauth 1074 differential's cases, both of which tested now-absent C). **The repository is now 100% Rust at 1075 the file level — not one line of C remains.** Next: P9 (std-library cleanup), verified against 1076 the `ircd-common` snapshot suite.