Proof of concept mechanical port of ircnet/ircd to Rust as part of a bit about C being insecure for network services
92 kB
1076 lines
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.