# Mechanically rewrite IRCnet ircd 2.11 from C to Rust (strangler fig)

## Context

`ircd.rs` is the IRCnet ircd 2.11 IRC daemon: ~51k LOC of C across `common/`, `ircd/`, and `iauth/`,
producing 5 binaries (`ircd`, `iauth`, `chkconf`, `ircdwatch`, `mkpasswd`). It is a single‑threaded
`select()/poll()` server with a table‑driven command dispatcher, a bundled BIND DNS resolver, pervasive
fixed‑size buffers, and ~230 `strcpy/sprintf`‑family calls — i.e. exactly the memory‑safety profile a
Rust rewrite is meant to eliminate. There are **no existing tests** (only a clang‑format CI check).

Goal: convert the codebase to Rust **module‑by‑module**, keeping the program building and running at
every step, with **every important part fully tested** against the original C as a reference oracle.

This plan reflects four locked decisions from the user:

1. **Faithful mechanical port now**, then **one dedicated idiomatic‑Rust pass at the very end** (after C is gone). No async/ownership redesign mid‑migration.
2. **Rust‑driven build from day one**: `fn main()` is Rust; remaining C compiles as a `*-sys` crate; we strangle _inward_.
3. **iauth rewritten first**, standalone greenfield Rust binary speaking the identical pipe protocol.
4. **Full 4‑layer testing** (differential unit, golden, fuzz, conformance) + a **permanent C reference build** in CI.

The leverage point: every `foo.c` already ships `foo_ext.h` (its public `extern` API) — **41 headers / ~417 symbols** that form a ready‑made per‑module C ABI contract. Dispatch is a function‑pointer table (`msgtab[]` in `common/parse.c`). These two facts make the strangler seam tractable.

---

## The migration model (the seam)

**Workspace.** A cargo workspace at repo root, coexisting with the untouched autoconf tree:

- `ircd-sys/` — `build.rs` runs `support/configure` to generate `setup.h`/`config.h`, then `cc::Build` compiles the C objects (mirroring `IRCD_COMMON_OBJS`+`IRCD_OBJS` from `support/Makefile.in:144‑149`) **minus** whatever has been ported, plus `bindgen` over a `wrapper.h` that `#include`s `common/struct_def.h` + all `*_ext.h`/`*_def.h`.
- `ircd-rs/` — the binary crate; `src/main.rs` owns the real `fn main()`. Ported modules live here / in leaf sub‑crates.
- `iauth-rs/` — standalone greenfield `iauth` binary (no `ircd-sys` dep).
- `ircd-testkit/` — the L1–L4 harness. `ircd-fuzz/` — `cargo-fuzz` targets.

**Runnable baseline (day one).** Compile the C `main` (`ircd/ircd.c:766`) as `c_ircd_main` via `cc::Build::define("main", Some("c_ircd_main"))`. Rust `main()` marshals argc/argv and calls it. The product is byte‑for‑byte the C ircd, just entered from Rust. (Also generate `version.c` from `ircd/version.c.SH.in` in `build.rs` and compile it — the C link needs `version.o`.)

**Strangling inward (per module).** To port `foo.c`: read `foo_ext.h` for the exact ABI; write Rust `#[no_mangle] pub extern "C" fn …` (and `#[no_mangle] pub static …` for exported data) matching the **exact** signatures (note: C uses mutable `char*`, not `const`); **remove `foo.c` from `build.rs`'s source list**. Because the `.c` is _dropped_ (not overridden), exactly one definition of each symbol exists at all times → no duplicate‑symbol/LTO games, program always links. Cross‑language LTO stays **off** during migration. CI builds + boots the server after every move.

**Single source of truth for compile defines.** `build.rs` must mirror `Makefile.in` **per‑object** defines (e.g. `-DFNAME_USERLOG=…`, `-DIRCDCONF_PATH=…`, `CLIENT_COMPILE`), and feed the **identical** `-include config.h/setup.h` + `-D` set to `cc::Build`, to `bindgen`, **and** to the reference‑archive build. A divergence here silently corrupts `aClient` layout. Default config to lock: `USE_IAUTH` on, `XLINE` off, `ZIP_LINKS` off, `AFINET=AF_INET6` (so `IN_ADDR` = libc `in6_addr`).

**Non‑ircd binaries are out of strangler scope.** `ircdwatch`, `chkconf`, `mkpasswd` and the `cl*.o` duplicate‑compiled common objects (`-DCLIENT_COMPILE`) stay in the **permanent C reference tree only**; they are never routed through `ircd-sys`. `chkconf` gets a Rust port opportunistically in P6.

---

## Keystone hazards & how we neutralize them (all verified in‑tree)

| Hazard                                 | Reality (verified)                                                                                                                                                                                   | Neutralization                                                                                                                                                                                                                                                                                                                                                                           |
| -------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **C‑variadic exports**                 | ~14 in `send_ext.h` (`sendto_one`, `sendto_flag`, …) + `irc_sprintf`; stable Rust _cannot define_ an `extern "C"` variadic fn. Each already has a `va_list` sibling (`vsendto_one`, `irc_vsprintf`). | Keep a **thin C trampoline** per variadic symbol (cc‑compiled): `va_start` → format via `irc_vsprintf` into a `BUFSIZE` buffer → call a **non‑variadic** Rust core `rs_sendto_one_str(to, msg)`. Rust never sees `va_list`. These ~25 trampolines persist through the faithful port and are deleted in **P8** (call sites switch to Rust `format!`). ⇒ "100% Rust" is a P8 goal, not P7. |
| **`aClient` interior self‑pointers**   | `make_client` sets `cptr->name = cptr->namebuf` (`list.c:121,226`); `me` is a by‑value global with `client = &me`.                                                                                   | **Invariant:** never bind/move/copy an `aClient` by value in Rust. All access through stable raw pointers; `name` set via `addr_of_mut!((*c).namebuf)`.                                                                                                                                                                                                                                  |
| **`CLIENT_REMOTE_SIZE` trick**         | Remote clients allocated `offsetof(aClient,count)` bytes; local‑only tail must never be touched when `from != self`.                                                                                 | Port `make_client` to branch on `offset_of!(aClient,count)` vs `size_of::<aClient>()`; build‑test asserts both equal the C `offsetof/sizeof`, **plus** `offset_of!` of every local‑tail field (`count,buffer,sendQ,recvQ,ip,hostp,…`).                                                                                                                                                   |
| **`repr(C)` layout drift**             | Layout gated by `USE_IAUTH/XLINE/ZIP_LINKS/INET6/TKLINE/…`.                                                                                                                                          | bindgen + cc share one define set (above); compile‑time `static_assert`s on size/offset; incomplete‑array externs (`local[]`, `tolowertab[]`) get element counts from **bindgen constants** (`MAXCONNECTIONS`) with a C `static_assert` that `sizeof(arr)/sizeof(arr[0])` matches the Rust length.                                                                                       |
| **Mutable dispatch table**             | `msgtab[]` entries are `struct Cmd{handler;count;rcount;bytes;rbytes}` **mutated every dispatch**, read by STATS.                                                                                    | Rust `static mut msgtab: [Message; …]` of the bindgen type, exact field order; handlers point at still‑C `m_*` until ported; L1 test asserts identical `count/bytes` after N dispatches.                                                                                                                                                                                                 |
| **Function‑like macros**               | bindgen drops `IsServer`, `MyConnect`, `MyFree`, `MyMalloc`, `DBufLength`, `SetXxx`, …                                                                                                               | Hand‑audited `macros.rs` of inline equivalents, each cross‑checked by an L1 test (C macro via tiny accessor vs Rust inline).                                                                                                                                                                                                                                                             |
| **Intrusive lists + manual refcounts** | `next/prev/hnext`, `Link` union, `anUser.refcnt`/`aServer.refcnt`.                                                                                                                                   | **Invariant during faithful port:** lists stay raw‑pointer intrusive lists — **no `Box`/`Drop`/Rust ownership**; refcnt inc/dec byte‑for‑byte as C. Enum‑`Link` and real ownership are **P8 only**.                                                                                                                                                                                      |
| **`find_*` ownership**                 | `find_client/uid/server/name/person` live in **`parse.c:144‑417`** (not hash.c), wrapping `hash_find_*`.                                                                                             | Port these with parse (**P4**); `hash_find_*` with hash (**P2**).                                                                                                                                                                                                                                                                                                                        |

---

## The 4‑layer test harness (built in P0, used every phase)

- **Permanent C reference build.** The pristine C tree stays in‑repo and buildable for the project's life; CI builds it every run as the oracle.
- **L1 — differential unit.** Build the reference `.c` into a **separate static archive** whose symbols are bulk‑renamed (`objcopy --redefine-syms` / `--prefix-symbols=cref_`, applied over the **full transitive** symbol set so internal callers follow the rename), exporting `cref_match`, `cref_MyMalloc`, … Link that archive beside the Rust crate in the test binary and assert Rust‑vs‑`cref_` identical outputs (and, for data structures, identical bytes/pointer‑walk order/counters). This is the workhorse for leaf/data/format tiers.
- **L2 — golden / characterization.** Run reference‑C ircd and the in‑progress Rust ircd side‑by‑side, diff wire output. Determinism is load‑bearing:
    - **Clock pinning:** `LD_PRELOAD` shim overriding `time()`/`gettimeofday()` to a fixed value in _both_ processes (the loop reads `time(NULL)` directly every iteration — a tune/faketime file is insufficient).
    - **Output canonicalizer:** mask known‑volatile token positions (WHOISIDLE idle/signon, STATSUPTIME, RPL_TIME/RPL_CREATED, fd‑derived ids). Gate = byte‑identical _after_ clock‑pin + canonicalization.
    - **Strict serialization:** drive scripted clients **one action at a time with barriers** — hash buckets prepend at head, so WHO/NAMES/LINKS/LIST order follows arrival order. No concurrent clients.
    - **S2S:** server‑burst diffing uses a **TS‑aware linking peer** (or link‑against‑reference‑C‑and‑observe), not canned replay.
- **L3 — differential fuzzing.** Feed identical bytes **through the real reassembly path** (`dbuf_getmsg` in `packet.c`, to hit 512‑byte truncation/framing) to C `parse()` vs Rust; diff the **full post‑state** — mutated `aClient` bytes, `ircstp` counters, and selected handler identity — not just `parv[]`.
- **L4 — conformance.** Rescoped: (1) extract the numeric→format table from `s_err.c` `replies[]` and assert the Rust port reproduces it (an L1 test); (2) curated scripted‑client assertions on numeric **code + arg arity/semantics** from `doc/whox.md`, `doc/whoistls.txt`, `rfc2812.txt`. (There is no machine‑readable RFC oracle; `replies[]` _is_ the truth.)

---

## Phases

> Each phase keeps the program building + L2/L4 green. "Drop" = remove the `.c` from `build.rs`.

| Phase                                                   | Modules                                                                                                                                                                    | Approach & gotchas                                                                                                                                                                                                                                                                                    | Test gate (exit)                                                                                                                                                                                                               |
| ------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| **P0 — Scaffold + baseline + harness 🔶 PARTIAL**       | `ircd-sys` (cc+bindgen, `main`→`c_ircd_main`, `version.c.SH`), `ircd-rs/main.rs`, `ircd-testkit`, `ircd-fuzz`, frozen C reference build                                    | Establish the Rust‑driven build; lock the configure‑define single source of truth; emit layout `static_assert`s; stand up all 4 layers. **Done as P0a (baseline), P0b (bindgen + layout drift-net), P0c (L1 differential harness), P0‑L2 (golden harness `ircd-golden`: reference‑C binary + scripted‑client diff; registration scenario byte‑identical).** **Not yet built:** L3 fuzz (`ircd-fuzz`), L4 conformance, `iauth-rs`.                                                                                                                                                               | `cargo build` boots an ircd that links iauth and **diffs byte‑identical (post‑canonicalization) vs reference‑C** across the full scripted suite; layout asserts pass. **(L1 + layout MET; L2/L4 gate pending those layers.)**                                                          |
| **P‑IAUTH — greenfield iauth (parallel, starts at P0)** | `iauth/*` → `iauth-rs`                                                                                                                                                     | Idiomatic Rust allowed (separate process, narrow text ABI). Reimplement event loop (mio), `cldata` fd‑slab, the `<id> <category> <info>` parser, the 8‑fn `aModule` vtable as a trait with 5 impls (`dnsbl/socks/webproxy/rfc931/pipe`); replace dlopen DSM with a static registry from `iauth.conf`. | Drive C‑iauth vs iauth‑rs with an **identical scripted `<id>` sequence** (ids are fixed in input, since `<id>`=ircd fd); byte‑identical replies per category + per‑module fixtures; end‑to‑end ircd+iauth‑rs golden identical. |
| **P1 — Leaf / pure utils ✅ DONE**                       | `common/match.c` (+`tolowertab/touppertab/char_atribs`), `dbuf.c`, **pure** subset of `support.c` (`mystrdup/strtoken/myctime/mybasename/inetntop/inetpton/irc_memcmp`). ~~`irc_sprintf.c`~~ → **deferred to P8** (variadic `va_arg` engine; stable Rust can't consume `va_list`, and it has no post-format core to hand a string to Rust → stays C, no trampoline). `snprintf_append`, `dgets`, `make_isupport`, `ipv6string` also stay C for now.                                                | Faithful; exact `char*` signatures; tables `#[no_mangle] static [u8;256]` (byte-equality asserted); `dbuf` owns `poolsize/freelist`. **`support.c` partial port** via inert `#ifndef PORT_SUPPORT_P1` guards + a `support_link.o` second-compile substituted into the link set; `MyMalloc/MyRealloc/MyFree` + `outofmemory()` **defer to P2**.                                                                | **MET:** L1 zero-diff over all corpora (match incl. tables + `MAX_ITERATIONS` cap; dbuf incl. `getmsg` framing + over-poolsize rollback; support incl. 8-case inet round-trip). Rust-driven `ircd` binary links; layout net green; 0 warnings. (L3 fuzz / L2/L4 not built yet — deferred from P0.)                                                                                                                                                                   |
| **P2 — Data & lookup ✅ DONE**                           | `patricia.c`, `class.c`, `whowas.c` (ring; `m_whowas` stays C), `hash.c` (6 tables; `m_hash` stays C), `list.c` (allocators, `make_client`, `svrtop/numclients`) + `MyMalloc`/`MyRealloc` + `outofmemory`            | Faithful; preserved intrusive pointers + refcounts (raw‑pointer invariant); replicated `CLIENT_REMOTE_SIZE` branch + hash **iteration order** exactly. **Verified findings:** `ENABLE_CIDR_LIMITS` ON (patricia live, `add_class` carries `cidrlen_s`); `USE_HOSTHASH`+`USE_IPHASH` ON (6 tables); **`MyFree` is a macro** (trio = `MyMalloc`+`MyRealloc`+`outofmemory`). `m_whowas`/`m_hash` kept in C via `whowas_link.o`/`hash_link.o` partial‑ports (`-DPORT_*_P2`).                                                                                                                                                  | **MET:** L1 zero‑diff over all corpora (patricia tree walk; class list + CIDR; whowas ring/index + uwas; hash client/channel/uid bucket chains + counters + stored hashv; `make_client` local/remote self‑pointers). Rust‑driven `ircd` binary links with `list.o` dropped; layout net green; 0 warnings. **whowas/class notification (`m_whowas`/`report_classes`) `sendto_*` path deferred to post‑P3 (L2).**                          |
| **P3 — Transport & format ✅ DONE**                     | `s_err.c` (`replies[]`), `s_id.c` (UID/SID/CID), `s_numeric.c` (`do_numeric`), `send.c` **delivery core** (`send_message`/`send_queued`/`flush_*`). **Finding:** the whole `sendto_*`/`esendto_*` family is irreducibly C trampolines (variadic; `vsendpreprep` consumes `va_arg` per‑recipient) → no non‑variadic cores extractable; senders + `sendto_flog`/`logfiles`/`setup_svchans` stay C (move to P7/P8). | Faithful; `replies[]` as Rust static (dumped from the oracle so config‑gated `#if` entries resolve); exact UID/SID base‑N; `send.c` partial‑ported via `send_link.o` (`-DPORT_SEND_P3`, `dead_link` exposed). ZIP/USE_SERVICES/LOG_SERVER_CHANNELS/etc. all OFF. | **MET (L1):** zero‑diff vs `cref_` on `replies[]` (all 1000), `ltoid`/`idtol`/`sid_valid`/`cid_ok`/`check_uid`, `do_numeric` guards, `send_message` sendQ buffering + `send_queued` early returns. **L2 (post‑P3):** `do_numeric`/sender wire broadcasts + `deliver_it` write path. |
| **P4 — Parser & dispatch ✅ DONE**                      | `common/parse.c` (tokenizer, `msgtab[]`, all 11 `find_*` wrappers, `getfield`, `m_nop`/`m_nopriv`/`m_unreg`/`m_reg`, `find_sender`/`cancel_clients`/`remove_unknown`)        | Faithful; `msgtab` as `#[no_mangle] static mut [Message; 66]` (config‑resolved: TKLINE on, SERVSET/KLINE/SIDTRACE off, MOTD→m_unreg; mutable counters); the ~60 real `m_*` handlers stay C and are referenced as fn pointers; **`parse.o` dropped outright** (every symbol ported — no `parse_link.o`). Rust **calls** the C variadic senders (`sendto_one`/`sendto_flag`/`sendto_serv_butone`) via bindgen decls (the s_numeric.rs pattern). Exact tokenize semantics (512 trunc, leading/trailing colon, empty trailing param, `MPAR` cap). **Deliverable:** per‑handler‑file static‑symbol cluster map (`specs/2026-06-02-p5-handler-cluster-map.md`).                            | **MET (L1):** zero‑diff vs `cref_` on the `msgtab[]` structure (cmd/min/max per row + row count), `getfield` (delimiter/escape/trailing/empty), `find_mask`/`find_name` fallbacks, and `parse()`'s no‑server paths (IsDead, empty, unknown‑cmd, numeric, ENCAP→m_nop dispatch — return code + `cptr.flags` + `ircstp` deltas). **Full `parse()` post‑state + hash‑populated `find_*` = L3/L2** (need the server fixture). |
| **P5 — Command handlers (bulk) 🔶 IN PROGRESS**          | `channel.c`, `s_user.c`+`s_auth.c`, `s_serv.c`+`s_service.c`+`s_zip.c` (zlib→flate2, **bit‑exact**), `s_misc.c` (`exit_client`), `s_debug.c`. **P5a DONE: `s_service.c`** (first cluster — established the handler‑porting mechanism). **P5b DONE: `s_debug.c`** (first utility/callee TU — STATS sub‑reports) + **`s_zip.c` dropped for free** (ZIP_LINKS off → only `rcsid`). **P5c DONE: `s_misc.c`** (the `exit_client`/`exit_one_client`/`exit_server` exit cascade + client‑name/date/stats utilities; `s_misc.o` dropped). **P5d DONE: `s_user.c` WHO/WHOIS cluster** (`m_who`/`m_whois`/`who_one`/`who_channel`/`who_find`/`send_whois`/`parse_who_arg`) via `s_user_link.o` partial port; rest of `s_user.c` stays C. **P5e DONE: `s_user.c` USERHOST/ISON cluster** (`m_userhost`/`m_ison`) — same `s_user_link.o` partial port (guard extended). **P5f DONE: `s_user.c` message‑routing cluster** (`m_message`/`m_private`/`m_notice` — PRIVMSG/NOTICE delivery core) — same `s_user_link.o` partial port; golden harness gained multi‑client `Client` + `boot_standalone` to cover `#`/`+`/`&` channel relay. **P5g DONE: `s_user.c` AWAY cluster** (`m_away` + `send_away`) — same `s_user_link.o` partial port; `send_away` now resolves to Rust for the already‑ported `m_message`/`m_whois` callers. **P5h DONE: `s_user.c` UMODE cluster** (`m_umode` + `send_umode` + `send_umode_out` + the `user_modes[]` table) — same `s_user_link.o` partial port; `send_umode` now resolves to Rust for the still‑C `register_user`/`s_service.c`/`s_serv.c` callers. **P5i DONE: `s_user.c` PING/PONG cluster** (`m_ping` + `m_pong`) — same `s_user_link.o` partial port; pure routing handlers (no statics). **P5j DONE: `s_user.c` OPER cluster** (`m_oper`) — same `s_user_link.o` partial port; reuses the Rust `send_umode_out`; new dedicated `oper.conf` fixture + `boot_conf` harness fn to L2‑test the auth paths (491/464/success). **P5k DONE: `s_user.c` QUIT/POST exit cluster** (`m_quit` + `m_post`) — same `s_user_link.o` partial port; both terminate in the already‑Rust `exit_client` (P5c); L2 covers QUIT (registered) + POST (unregistered http‑proxy alias). **`s_auth.c` deferred to P7/P‑IAUTH** (no `m_*`; pure socket/ident/iauth‑pipe I/O, no L2 path under `‑s` boot).** | Faithful, but **cluster‑based, not per‑function**: `channel.c` has 37 file‑statics (`set_mode`, `can_join`, `get_channel`, …); migrate each **connected component over shared statics** as a unit via the `msgtab` fn‑pointer seam. **s_zip.o is empty (ZIP_LINKS off) → drop for free, no port.**                                                                   | Per‑cluster L2 golden + L4 conformance byte‑identical incl. WHO/WHOIS/WHOX, full server‑burst/netsplit; zlib bytes match.                                                                                                      |
| **P6 — Config & DNS**                                   | `s_conf.c`+`config_read.c` (rehash; `conf/kconf/networkname`), `chkconf.c`, `res.c`+`res_comp.c`+`res_init.c`+`res_mkquery.c`                                              | Faithful config grammar + rehash. Resolver: faithful port **or** hickory‑dns behind `res_ext.h`, but golden runs use a **fixture nameserver** for determinism.                                                                                                                                        | Config‑parse + chkconf L1/L3 zero‑diff on fixtures; resolver encode/decode identical; L2/L4 identical with fixture DNS.                                                                                                        |
| **P7 — I/O core + event loop + glue (last)**            | `s_bsd.c` (select/poll loop, `local[]`, `highest_fd`, `timeofday`, `FdAry`), `fileio.c`, `packet.c`, `bsd.c`, `ircd.c` (main, io_loop, signals, rehash/restart, tune file) | Faithful: keep **single‑threaded select/poll via mio** (not tokio); preserve per‑client state machine; globals become Rust‑owned statics; signals via signal‑hook/nix + atomics. Removes `c_ircd_main`.                                                                                               | All C _logic_ gone (only variadic trampolines remain); full L2+L3+L4 + soak byte‑identical under connect storms, partial reads, sendQ backpressure, netsplit, rehash, restart.                                                 |
| **P8 — Idiomatic pass (after C is gone)**               | whole Rust tree                                                                                                                                                            | The single allowed redesign: async (tokio) _if desired_, owned state (slotmap/arena) replacing intrusive lists, `enum Link`, delete variadic trampolines (call sites → `format!`), remove `unsafe`, real error types. **Also: port `irc_sprintf`/`snprintf_append` (varargs) here as call sites adopt `format!`.**                                                                                 | Behavior parity maintained via the same L2/L4 suite against the now‑archived reference‑C.                                                                                                                                      |

---

## Progress log

- **2026‑06‑02 — P0a/P0b/P0c merged** (scaffold + Rust‑driven baseline; bindgen full ABI + layout drift‑net; L1 differential harness `ircd-testkit` with the `cref_*` symbol‑renamed oracle archive). See `docs/superpowers/plans/2026-06-01-p0{a,b,c}-*.md`.
- **2026‑06‑02 — P1 (Leaf / pure utils) merged.** New library crate **`ircd-common`** holds the ports (`#[no_mangle] pub extern "C"`, exact `*_ext.h` signatures); `ircd-rs` + `ircd-testkit` depend on it via a `link_anchor()`. Ported & dropped from the C link set: `match.c` (5 fns + 3 tables), `dbuf.c` (8 fns + `poolsize`/`freelist`), and a pure subset of `support.c` (`mystrdup`, `strtoken`, `myctime`, `mybasename`, `inetntop`, `inetpton`, `irc_memcmp`).
  - **Seam wiring (resolves P0c's deferred task):** `ircd-sys/build.rs` now splits `CREF_OBJS` (full set → feeds the `cref_*` oracle) from the link set (`CREF_OBJS \ PORTED`, with `support.o`→`support_link.o` substitution). `support.c` is partially ported via inert `#ifndef PORT_SUPPORT_P1` guards + a second compile, leaving the pristine reference build and the oracle untouched.
  - **Scope corrections vs. original plan:** `irc_sprintf.c` + `snprintf_append` moved to **P8** (variadic `va_arg`, unportable on stable Rust, no post‑format core); `dgets`/`make_isupport`/`ipv6string` left C (not pure leaves). `dbuf_copy` is dead/commented‑out C (not a compiled symbol) → not ported.
  - **Verification:** `cargo build` warning‑free; all L1 + layout tests green; `match`/`dbuf_*`/`mystrdup`/`irc_memcmp` resolve to Rust in `libircd_c.a` while `cref_*` remain in `libcref.a`. Spec: `docs/superpowers/specs/2026-06-02-p1-leaf-utils-design.md`; plan: `docs/superpowers/plans/2026-06-02-p1-leaf-utils.md`.
  - **Faithfulness notes (caught in review):** reproduced `match()`'s always‑active `MAX_ITERATIONS 512` cap; `dbuf_get` mirrors C's `&&` short‑circuit (avoids `dbuf_map`'s `tail` side effect); `dbuf` `poolsize` hard‑codes the compile‑time `(BUFFERPOOL>1500000)?…:1500000` value (correct for this config, pinned by an L1 test); `irc_memcmp` = unsigned byte compare (only the sign is observable); `istat` externed from C, not redefined.
- **2026‑06‑02 — P2 (Data & lookup) merged.** All five modules ported into `ircd-common` (`patricia.rs`, `class.rs`, `whowas.rs`, `hash.rs`, `list.rs`), each with an L1 differential test in `ircd-testkit`. Dropped from the link set: `patricia.o`, `class.o`, `list.o`; substituted: `whowas.o`→`whowas_link.o`, `hash.o`→`hash_link.o`, and `support.o`→`support_link.o` now also `-DPORT_SUPPORT_P2` (so `MyMalloc`/`MyRealloc` come from Rust). Spec: `docs/superpowers/specs/2026-06-02-p2-data-lookup-design.md`; plan: `docs/superpowers/plans/2026-06-02-p2-data-lookup.md`.
  - **Verified config findings that shaped the port:** `ENABLE_CIDR_LIMITS` is **ON** (config.h:738) → `patricia.c` is live, `add_class` carries the trailing `cidrlen_s`, `aClass` has the CIDR fields, `free_class` calls `patricia_destroy`. `USE_HOSTHASH`+`USE_IPHASH` **ON** → `hash.c` has 6 tables. **`MyFree` is a macro** (`sys_def.h:26`), not a symbol → the "trio" is really `MyMalloc`+`MyRealloc`+`outofmemory`; Rust ports call libc `free`.
  - **Partial‑port mechanism (extends P1's `support_link.o`):** `m_whowas` and `m_hash` are command handlers (P5) that format replies through the variadic `sendto_one`/`replies[]` path, so they stay C. The CREF oracle compiles the pristine unguarded `.o`; the link set compiles a `<mod>_link.o` with `-DPORT_<MOD>_P2` that `#ifdef`s out the ported fns + their owning globals and `extern`s the now‑Rust‑owned ones. To make the oracle export the needed `cref_*`, file‑static data (`was`/`locked` in whowas; the tables/counters/`hashtab` in hash) was given external linkage — behaviourally inert.
  - **Determinism preserved (pinned by L1):** bit‑exact hash fns (`hashtab[i]=tolower((char)i)*109`, nick `(<<4)+`, uid `(<<4)^`, channel start‑5/cap‑30/`+(i<<1)`, host/ip `31*+`), head‑prepend insertion, stored‑hashv‑then‑`mycmp` find; whowas ring index advance/wraparound + the `uwas` link storing the **index** not a pointer; `make_client` self/interior pointers + the `CLIENT_REMOTE_SIZE = offset_of!(Client,count)` branch; `make_user`/`make_server` refcnt=1 and `free_user`/`free_server` `-211000/-211001` recursion sentinels.
  - **Also fixed:** a pre‑existing P1 flake — the four `dbuf_diff` tests raced on the shared `poolsize`/`freelist` under parallel `cargo test`; now serialized through a process‑wide mutex.
  - **Verification:** `cargo build` warning‑free; the Rust‑driven `ircd` binary links with `list.o` dropped (all alloc/list logic now Rust); every `*_diff` L1 test zero‑diff vs `cref_*`; layout net green. Deferred to L2/post‑P3: the `m_whowas`/`report_classes` `sendto_*` wire output.
- **2026‑06‑02 — P3 (Transport & format) merged.** Ported into `ircd-common`: `s_err.rs` (the 1000‑entry `replies[]` numeric→format table), `s_id.rs` (UID/SID/CID), `s_numeric.rs` (`do_numeric`), `send.rs` (the non‑variadic delivery core `send_message`/`send_queued`/`flush_connections`/`flush_fdary`). Dropped from the link set: `s_err.o`, `s_id.o`, `s_numeric.o`; substituted: `send.o`→`send_link.o`. Spec: `docs/superpowers/specs/2026-06-02-p3-transport-format-design.md`; plan: `docs/superpowers/plans/2026-06-02-p3-transport-format.md`.
  - **Keystone finding (refines the hazard table):** the entire `sendto_*` (send.c) + `esendto_*` (s_send.c) family is **irreducibly C trampolines** — every one is C‑variadic and consumes `va_list` through a formatter (`vsendprep`/`vsendpreprep`/`build_suffix`), and `vsendpreprep` (send.c:384) consumes `va_arg` **per recipient**, so there is no "format once → Rust core" reduction. The Rust "cores" P3 actually extracts are the **delivery** fns, not the senders. The senders + `vsendto_one`/`sendto_flog`/`logfiles_*`/`setup_svchans` + the static buffers/`anon`/`svchans` stay C; they vanish in **P8** when call sites adopt `format!`. **s_send.c is therefore 100% C in P3 (nothing ported).**
  - **Verified config gates (all OFF):** `USE_SERVICES`, `LOG_SERVER_CHANNELS`, `USE_SYSLOG`, `LOG_OLDFORMAT`, `ZIP_LINKS`, `CLIENTS_CHANNEL`, `JAPANESE`, `POOLSIZE_LIMITED` → the corresponding branches of `send_message`/`send_queued`/`sendto_flog`/`setup_svchans` are omitted from the Rust port. `SCH_MAX`=14.
  - **`send_link.o` partial‑port (extends P1/P2):** `send.c` compiled twice — pristine `send.o` for the oracle; `send_link.o` with `-DPORT_SEND_P3` that `#ifndef`‑guards the 4 ported delivery fns out of the link set **and exposes `dead_link`** (drops `static`) so the Rust core can call that still‑variadic C fn. Built via `make --eval` so the recursive `-DFNAME_USERLOG/CONNLOG/SCH_PREFIX` path vars expand identically for the surviving C `logfiles_open`. Pristine `send.o` (`dead_link` static, `send_message` defined) is unchanged.
  - **Faithfulness notes (caught in review):** `replies[]` is **not** mechanically text‑extractable — it contains `#if/#else/#endif` config branches (WHOIS_SIGNON_TIME/USERS_RFC1459/ENABLE_SUMMON/TXT_NOSTATSK), two bare‑`NULL` entries, and an escaped `\r\n` (entry 384) that Rust source‑level CRLF normalization would silently corrupt → it is **dumped from the compiled `cref_replies` oracle** with byte‑level escaping. `alphabet_id[256]` in s_id.c is partially initialized (249 explicit values) so indices 249..=255 default to **0** (reproduced verbatim). `init_sid` is `#if 0` (not ported). `poolsize *= 1.1` reproduced as `(poolsize as f64 * 1.1) as u_int`.
  - **L1 coverage / L2 deferrals:** L1 zero‑diff vs `cref_` on the `replies[]` table (all 1000 slots), `ltoid`/`idtol` round‑trip + `sid_valid`/`cid_ok`/`check_uid`, `do_numeric`'s early‑out guards, and `send_message` sendQ buffering (bytes + `sendM` + `lastsq`) + `send_queued`'s dead/empty early returns. **Deferred to L2** (need full server state — initialized hash tables, registered `me`, `cref_`'s separate globals, a live fd): the `do_numeric` broadcast wire output and the `send_queued` `deliver_it` write path.
  - **Verification:** `cargo build` warning‑free; Rust‑driven `ircd` links (`send_link.o`'s variadic senders call the Rust `send_message`; the Rust core calls C `dead_link`/`deliver_it`); all `*_diff` L1 tests zero‑diff; layout net green.
- **2026‑06‑02 — P4 (Parser & dispatch) merged.** All of `common/parse.c` ported into `ircd-common/src/parse.rs` and **`parse.o` dropped outright** (added to `PORTED`, like `list.o`/`match.o`) — no `parse_link.o`, because **every** symbol parse.c defined is portable now: the `msgtab[]` table, all 11 `find_*` wrappers, `parse`, `getfield`, the four stub handlers (`m_nop`/`m_nopriv`/`m_unreg`/`m_reg`), and the three file‑statics (`find_sender`/`cancel_clients`/`remove_unknown`). Spec: `docs/superpowers/specs/2026-06-02-p4-parse-dispatch-design.md`; plan: `docs/superpowers/plans/2026-06-02-p4-parse-dispatch.md`.
  - **Refines the hazard table (variadic dispatch):** the ~14 `sendto_*` senders parse.c **calls** stay C, but Rust **calls** them through bindgen variadic declarations (`sendto_one(to, c"…".as_ptr(), …)`) — the exact s_numeric.rs pattern. *Calling* a C variadic is stable Rust; only *defining* one is not. So no trampoline is needed on the call side; the senders themselves still vanish in P8.
  - **`msgtab[]` as a Rust `#[no_mangle] static mut [Message; 66]`:** built with a `row!`/`m!` macro; handler fn‑pointers reference the still‑C `m_*` (and the four Rust stubs) and coerce in the `static` initializer; the mutable `count`/`rcount`/`bytes`/`rbytes` counters mutate in place exactly as C (read by STATS in still‑C `s_serv.o`). **Config‑resolved** for this build: `TKLINE` **on** (TKLINE+UNTKLINE rows), `USE_SERVICES`/`KLINE`/`ENABLE_SIDTRACE` **off** (no SERVSET/KLINE/SIDTRACE rows), `MOTD_UNREG` **off** (MOTD unreg slot = `m_unreg`), `IDLE_FROM_MSG` **on** (idle guard = `fhandler == m_private`), `DEBUGMODE` **off** (every `Debug(())` omitted). The msgtab L1 test compares cmd/min/max/row‑count, **not** handler pointers (Rust rows point at `m_join`, the oracle at `cref_m_join`).
  - **Bug caught by L1:** `IRCDCONF_DELIMITER` is `'|'` (config.h:500), **not** `':'` — the first `getfield` draft used `':'` and the differential failed immediately. (find_mask/find_name L1 also surfaced that the hash tables are null until `inithashtables()`; the test now inits both sets.)
  - **L1 coverage / L2‑L3 deferrals:** L1 zero‑diff vs `cref_` on the `msgtab[]` structure, `getfield` (delimiter/escape/trailing/empty), `find_mask`/`find_name` empty‑hash fallbacks, and `parse()`'s no‑`me`/no‑hash/no‑sender paths (IsDead early‑out, empty message, unknown‑command‑from‑non‑person, 3‑digit numeric, and an `ENCAP→m_nop` dispatch — asserting return code + `cptr.flags` + `ircstp` counter deltas). **Deferred to L3 (parser fuzz) / L2:** the full `parse()` post‑state differential (dispatch into the still‑C `m_*`, mutated `aClient` + handler counters) and hash‑populated `find_*` — both need the populated‑server fixture.
  - **Deliverable:** the per‑handler‑file static‑symbol **cluster map** for P5 (`docs/superpowers/specs/2026-06-02-p5-handler-cluster-map.md`): clusters + entry points + suggested porting order (s_zip/s_debug → s_auth/s_service → s_user → s_misc → channel.c → s_serv.c).
  - **Verification:** `cargo build` warning‑free; the Rust‑driven `ircd` binary links with `parse.o` dropped (`parse`/`msgtab`/`find_*`/`getfield`/`m_nop` resolve to Rust); full `cargo test` green (all prior `*_diff` + `parse_diff` 4/4 + layout); 0 warnings.
- **2026‑06‑02 — P0‑L2 (golden harness) built.** New workspace crate **`ircd-golden`** boots a pristine **reference‑C `ircd`** and the in‑progress **Rust‑driven `ircd`** side‑by‑side, drives a scripted TCP client one action at a time, canonicalizes, and asserts byte‑identical wire output. First scenario — client registration (NICK/USER → welcome banner) — is **byte‑identical** between the two builds (`alice!~alice@127.0.0.1`, 16 lines through `422 MOTD File is missing`). This is the real **P5** gate (command handlers can't be L1‑tested). Spec/plan: `docs/superpowers/plans/2026-06-02-p0-l2-golden-harness.md`.
  - **Reference‑C runnable binary (new):** the L1 oracle only ever produced *renamed objects*, never a runnable C ircd. `ircd-golden/build.rs` now builds one: recompile **only `ircd.c`** with a native `main` (`cbuild/ircd.o` carries `‑Dmain=c_ircd_main`, so it has none) into `ircd_ref.o` via `make ‑‑eval` (so the per‑object path defines `$(IRCDCONF_PATH)`/… expand identically), then link it with the **pristine base objects** (the plain `.o`, not the `_link.o` partial‑port variants) + `version.o` + `‑lz ‑lm ‑lcrypt`. Built into `OUT_DIR`; path exported via `cargo:rustc‑env`.
  - **DNS disabled project‑wide (`NO_DNS_LOOKUP`):** `s_bsd.c add_connection`'s async reverse‑PTR lookup (`gethost_byaddr`→`SetDNS`) both embedded a nondeterministic resolved hostname in the banner **and** held registration across event‑loop iterations. Now `‑DNO_DNS_LOOKUP` (added to `EXTRA_CFLAGS` in `ircd‑sys/build.rs`, so cref oracle + link set + Rust binary all share it) makes the lookup a no‑op: `hostp` stays NULL without `SetDNS`, clients register immediately with their **numeric** host (the same end‑state a no‑PTR lookup reaches; handled at the existing `!cptr->hostp` paths). The `USE_IAUTH` cache‑hit block that derefs `hostp->h_aliases` is compiled out under the same flag. Per the user: resolver behaviour is intentionally **not** maintained.
  - **Keystone finding — DO NOT pin the clock to a constant.** The PLAN's "LD_PRELOAD shim overriding `time()`/`gettimeofday()` to a fixed value" **breaks the server**: a frozen `time()` freezes the global `timeofday` (set every loop at `ircd.c:1250`), so the io_loop's scheduling never advances and the **listen socket is never serviced** — clients are never accepted (confirmed via `strace`: `poll()` only ever watched the resolver fd). The harness uses the **real clock** and relies on the **canonicalizer** to mask the few time‑bearing tokens (`242` uptime, `317` idle/signon, `391` time; `003`/`004` are compile‑time‑stable across both builds). The registration banner embeds no runtime timestamps, so it needs none. (A future advancing‑from‑fixed‑epoch shim could tighten WHOIS‑idle/STATS scenarios.)
  - **Config path redirect (no root):** `CMDLINE_CONFIG` is `#undef` → `‑f` is dead; both binaries `open("/usr/local/etc/ircd.conf")` (`s_conf.c:1395`). A committed LD_PRELOAD shim (`ircd-golden/csrc/l2shim.c`) intercepts `open`/`open64` and rewrites that one path to `$IRCD_TEST_CONFIG`. Boot flags: `‑t` (foreground) `‑s` (`BOOT_NOIAUTH`). Both bind the same fixture port (16667), so the test runs them **sequentially**, never concurrently.
  - **Verification:** `cargo test ‑p ircd‑golden` → `registration_banner_matches_reference` PASS; full `cargo test` green (golden + all 12 `*_diff` L1 suites + layout 4/4); `s_bsd.c` is not L1‑tested so the DNS edit breaks no differential; 0 warnings.
- **2026‑06‑02 — P5a (s_service.c — first command‑handler cluster) merged.** All 6 compiled symbols (`svctop`, `make_service`, `free_service`, `best_service` [static], `m_service`, `m_servlist`, `m_squery`) ported to `ircd-common/src/s_service.rs` and **`s_service.o` dropped outright** (added to `PORTED`, like `parse.o`). Establishes the **P5 handler‑porting mechanism**: a Rust `m_*` reachable through the `msgtab` fn‑pointer seam. Spec/plan: `docs/superpowers/plans/2026-06-02-p5a-s_service.md`.
  - **parse.rs needs no change:** it already imports `m_service`/`m_servlist`/`m_squery`/`svctop` from `ircd_sys::bindings` (it imports *all* symbols there, even already‑ported ones); those declarations resolve to the Rust definitions at link once `s_service.o` is dropped. This is the general seam for every future handler — define the Rust symbol, drop the `.o`, no msgtab edit.
  - **Variadic senders:** Rust **calls** `sendto_one`/`sendto_flag` via a local `extern "C"` block (the s_numeric.rs pattern); fmt must be declared `*mut c_char` (matching the bindgen/other‑module decls) to avoid the `clashing_extern_declarations` lint, so format‑string literals are cast `c"…".as_ptr() as *mut c_char`. `MyFree` = libc `free`; `strncpyzt`/`index`(=`strchr`)/`myncmp` ported.
  - **Config (USE_SERVICES off) shapes the port:** `m_servset`/`check_services_*`/`find_conf_service` + the local‑service branch of `m_service` are not compiled and not ported; a client `SERVICE` reaches the `#ifndef USE_SERVICES` reject (`ERROR :Server doesn't support services`).
  - **msgtab dispatch findings (caught at L2):** a *registered* client's `SERVICE` maps to `m_nop` (msgtab index 1 = registered‑client column is `m_nop`), so it produces no reply — `m_service` is only reached from index 0 (unregistered) / 4 (server→SERVICE). Bare `SQUERY` is rejected by `parse()` with `461` (min‑params) before the handler. So L2 coverage of the ported code is: `m_servlist` (`235`), `m_squery`+`best_service` (`408`, empty list), and `m_service` reject (unregistered client → `ERROR`).
  - **L2 harness hardening:** `ircd-golden` boots now serialize through a process‑wide `PORT_LOCK` held by the `Ircd` guard — `cargo test` runs `#[test]`s on parallel threads and they all bind the one fixture port (16667). (Single‑test `golden_registration` had masked this; the 2‑test `golden_service` surfaced it.)
  - **Verification:** L1 `s_service_diff` zero‑diff vs `cref_` (`make_service` svctop linkage + `cptr->name` self‑pointer + idempotency; `free_service` head/middle unlink + `service=NULL`); L2 `golden_service` 2/2 byte‑identical (registered SERVLIST/SQUERY + unregistered SERVICE reject); the Rust `ircd` links with `s_service.o` dropped (`m_service`/`svctop` resolve to ircd‑common); full `cargo test` green; 0 warnings.
- **2026‑06‑02 — P5b (s_debug.c — first utility/callee TU) merged; s_zip.c dropped.** All 4 compiled symbols (`serveropts` config‑resolved option string, `send_usage`, `send_defines`, `count_memory`) ported to `ircd-common/src/s_debug.rs` and **`s_debug.o` dropped outright**; **`s_zip.o` also added to `PORTED`** (ZIP_LINKS off → its only symbol is a `static rcsid`, nothing to port). The variadic `debug()` is `#ifdef DEBUGMODE` (off) → not compiled, not ported. This is the first **utility TU** (no `msgtab` entry): the three report fns are reached from still‑C `m_stats` (s_serv.c) via STATS letters.
  - **Classification → L2 strategy:** s_debug has no client‑reachable `m_*`; coverage comes from driving the *caller's* command. STATS `d`→`send_defines` (RPL_STATSDEFINE 248, all compile‑time constants — fully deterministic), STATS `z`→`count_memory(.., 0)` (RPL_STATSDEBUG 249 memory counts from `istat`/sizes). Both reachable by a plain registered local client (no oper gate on `d`/`z`). STATS `r`/`R`→`send_usage` is **not** L2‑covered (getrusage CPU/RSS fields are wall‑clock volatile); ported faithfully (HAVE_GETRUSAGE path, `hzz=1`) + links, but exercised only by code review.
  - **Config‑resolved data, flattened (not `#ifdef`‑replicated):** `serveropts` = `"aEFJKMQRTu6"` (HUB off, CLONE_CHECK on, OPER_* on, IDLE_FROM_MSG on; dumped from the oracle `.o` and pinned by L1). `send_defines` bakes ~30 macro constants bindgen drops (MAXSERVERS=1 [HUB off], CLONE_MAX/PERIOD=10/2, ZL=-1, DELAY_CLOSE=15, MINLOCALNICKLEN→1, …) as resolved literals — the STATS d L2 byte‑diff is the safety net for any wrong constant. `BUFFERPOOL` resolved to 222320 (= DBUFSIZ 2032·MAXCONN 50·2 + QUEUELEN 19120·MAXSERVERS 1).
  - **Determinism / canonicalizer:** `count_memory`'s final `:TOTAL: <tot> sbrk(0)-etext: <N>` line carries a volatile heap‑break token (differs between the separately‑built ref‑C and Rust binaries); added one canonicalizer mask (truncate after `sbrk(0)-etext:`). Everything else in STATS z is deterministic — `cres_mem` sends two RPL_STATSDEBUG lines but with the DNS cache empty (NO_DNS) they're all‑zero, and `tot` is config/counter‑derived. `count_memory`'s `if(debug)` [REAL] branches (the client/channel/conf list walks) are ported faithfully but never execute on the STATS z path (`debug=0`).
  - **`libc` for `getrusage`/`rusage`:** sys/resource.h isn't in bindgen's wrapper.h, so `send_usage` uses `libc::{rusage, getrusage, RUSAGE_SELF}` rather than a hand‑rolled struct; `sbrk`/`time`/`strerror` via a local `extern "C"` block. Variadic `sendto_one` called the s_service.rs way (fmt `*mut c_char`, literals cast).
  - **Verification:** L1 `s_debug_diff` zero‑diff vs `cref_serveropts` (+ pins the resolved value); L2 `golden_debug` 2/2 byte‑identical (STATS d `send_defines` block incl. `:HUB:no MS:1`; STATS z `count_memory` block incl. masked `sbrk(0)-etext: <BRK>`); the Rust `ircd` links with `s_debug.o`+`s_zip.o` dropped (`serveropts`/`send_*`/`count_memory` resolve to ircd‑common); full `cargo test` green; 0 warnings.
- **2026‑06‑02 — P5c (s_misc.c — the exit cascade + misc utilities) merged.** All 18 ext‑header symbols + the two file‑statics (`exit_server`/`exit_one_client`) + the 4 exported globals (`ircst`/`ircstp`, `motd`/`motd_mtime`) ported to `ircd-common/src/s_misc.rs`; **`s_misc.o` dropped outright** (every symbol portable → added to `PORTED`, no `_link.o`). This is the largest utility TU so far (1217 LOC): `exit_client`→`exit_server`↔`exit_one_client` is the mutually/self‑recursive client/server exit cascade (the critical network path), plus `date`/`get_client_{name,host,ip}`/`get_sockhost`/`my_name_for_link`/`mark_blind_servers`/`tstats`/`read_motd`/`check_split`/`check_registered*`/`checklist`/`initstats`/`initruntimeconf`/`myrand`/`mysrand`. Spec/plan via the command‑cluster‑port skill.
  - **Config gates that shaped the port (all verified in cbuild/config.h):** `USE_SERVICES`/`UNIXPORT`/`USE_SYSLOG`/`FNAME_USERLOG`/`FNAME_CONNLOG`/`CLIENTS_CHANNEL`/`BETTER_CDELAY`/`BETTER_NDELAY` **off**; `DELAY_CLOSE=15`/`USE_HOSTHASH`/`USE_IPHASH`/`USE_IAUTH`/`CACCEPT_DEFAULT=2` **on**. Crucially the entire `exit_client` per‑client **logging block** (s_misc.c:465‑507) is gated on `(FNAME_USERLOG||FNAME_CONNLOG||USE_SERVICES||(USE_SYSLOG&&…))` — all undefined → omitted, so **`sendto_flog` is never called from s_misc** (one fewer variadic to thread). The `#ifdef USE_SERVICES` `check_services_butone` calls throughout the cascade are likewise not compiled/not ported.
  - **Keystone finding — variadic `%lu`‑from‑`u_int` is a *caller‑side* faithfulness hazard, caught by L2.** `tstats` passes ~30 `u_int` (32‑bit) `istat`/`stats` fields straight to `%lu` (64‑bit) — UB that prints correctly in C only because the SysV ABI zero‑extends 32‑bit args into their 64‑bit slots. A Rust variadic call leaves the upper 32 bits **undefined** → `STATS t` printed garbage (`refused 94888712470528`). Fix: cast every integer arg to *exactly* its conversion width (`%lu`→`u_long`, `%llu`→`c_ulonglong`, `%d`→`c_int`, `%u`→`c_uint`); the decimal value is unchanged (all fields < 2³²) so the wire output is byte‑identical to a correct C run. **This is now a standing rule for any ported fn that calls the C variadic senders with sub‑`long` integers.** (The `golden_s_misc` STATS t diff is what surfaced it — exactly what L2 is for.)
  - **`format!`/byte‑builder instead of libc `sprintf` (per user request):** the buffer‑formatting fns no longer call `sprintf`. `date` (pure ASCII) uses Rust `format!` then copies into its static `[c_char;80]`. `get_client_{name,host}` + `exit_client`'s `comment1` use small byte‑exact `push_cstr`/`push_cstr_prec`/`store` helpers — **not** `format!`, because C's `%.*s` byte‑precision over arbitrary (possibly non‑UTF‑8) C‑string bytes can't pass through a UTF‑8 `String` without corruption. The senders (`sendto_one`/`sendto_flag`/`sendto_serv_butone`/`sendto_common_channels`/`sendto_channel_butserv`) stay C variadics (P8). (Caught en route: `&str::as_ptr()` is **not** NUL‑terminated → an early `sprintf %s` overran into adjacent rodata; the byte‑builder rewrite removes that footgun class.)
  - **Globals owned here:** `ircst`/`ircstp` (`*ircstp = &ircst`) as `#[no_mangle] static mut` — `ircst` zero‑inits via `MaybeUninit::zeroed().assume_init()` (const), `ircstp` via `addr_of_mut!(ircst)`; `motd`=NULL, `motd_mtime`=0. `months`/`weekdays` stay module‑private `&str` tables. All Is*/Got*/strncpyzt macros reproduced inline; `match`/`mycmp` imported from bindings; hash/list/whowas callees (`del_from_*_hash_table`/`remove_client_from_list`/`unregister_server`/`add_history`/`off_history`) resolve to the Rust ports; channel/conf/bsd callees (`remove_user_from_channel`/`del_invite`/`find_conf_name`/`close_connection`/`activate_delayed_listeners`/`is_chan_op`/`remove_server_from_tree`/`report_iauth_stats`/`dgets`) stay C.
  - **Verification:** L1 `s_misc_diff` zero‑diff vs `cref_` (date over 4 epochs; my_name_for_link dot‑walk/guard; get_sockhost `@`‑strip; get_client_ip `::ffff:` inetntop; initstats/initruntimeconf seeded fields; mysrand/myrand determinism). L2 `golden_s_misc` 2/2 byte‑identical: **QUIT** → `exit_client` local path (`ERROR :Closing Link: alice[~alice@127.0.0.1] ("gone fishing")`), **STATS t** → `tstats` (full RPL_STATSDEBUG block; `:time connected` masked as wall‑clock volatile). Added two canonicalizer masks: `:time connected` and a drop of the racy `SPLIT_CONNECT_NOTICE` (register‑time NOTICE whose post‑422 TCP chunking is nondeterministic). Full `cargo test` green; 0 warnings. **Deferred to S2S/multi‑client L2:** the `exit_one_client` channel‑QUIT broadcast, server `SQUIT` propagation, masked‑server paths (ported faithfully, covered by review).
- **2026‑06‑02 — P5d (s_user.c WHO/WHOIS cluster) merged.** The contiguous WHO/WHOIS display cluster (s_user.c:1685‑2315 — `who_one`/`who_channel`/`who_find` [statics], `parse_who_arg` [file‑global], `send_whois` [static], `m_who`/`m_whois` [exported, msgtab]) ported to `ircd-common/src/s_user.rs`. **First in‑TU partial port of P5:** `s_user.o`→`s_user_link.o` (`‑DPORT_USER_P5` `#ifndef`‑guards the cluster out of the link set; the cref oracle keeps the full unguarded `s_user.o`). The rest of s_user.c (register_user/m_nick/m_kill/m_umode/m_oper/m_message/… + the shared statics `buf`/`buf2`/`user_modes`) stays C. `s_user_link.o` built via `make --eval` so the per‑object `FNAME_USERLOG/CONNLOG/OPERLOG` path defines (used by the surviving C register_user/m_oper) expand identically (the send_link.o pattern).
  - **`s_auth.c` deferred (not a P5 cluster):** it has **no `m_*` handlers** — it's the ident/iauth glue (`start_auth`/`send_authports`/`read_authports`/`read_iauth`/`sendto_iauth`), pure socket/pipe I/O touching `adfd`/`highest_fd`/`local[]`/`start_iauth`, and the golden harness boots `‑s` (BOOT_NOIAUTH → `adfd<0`) so almost none of it runs ⇒ **no L2 path**. Belongs to P7 (I/O core) + P‑IAUTH.
  - **Seam (no parse.rs edit):** parse.rs already imports `m_who`/`m_whois` from `ircd_sys::bindings`; once `s_user.o`→`s_user_link.o` drops them, those decls resolve to the Rust defs. `parse_who_arg` is `#[no_mangle]` only so the L1 test can reach it (no header/external C caller). Still‑C callees (`canonize`/`hunt_server`/`next_client`/`send_away`/`is_chan_op`/`has_voice`) + Rust‑ported callees (`find_client`/`find_channel`/`hash_find_client`/`match_`/`strtoken`/`get_client_ip`/`collapse`/`clean_channelname`/`cid_ok`) reached via bindgen decls; `sendto_one` is the C variadic trampoline.
  - **`format!` per user request:** WHOX (354) is built field‑by‑field into a `Vec<u8>` — numeric fields (`%d` hopcount, `%ld` idle) via `format!`, arbitrary C‑string fields (username/host/info/…) copied raw via `push_cstr` (a UTF‑8 String would corrupt non‑ASCII bytes), then truncated to `BUFSIZE‑1` (faithful to C's incremental `snprintf`/`snprintf_append` truncation). `send_whois`'s channel‑list accumulation keeps the byte‑exact pointer/`len` logic on a private `static mut BUF` (the C file‑static `buf` stays with the C remnant). The placeholder `UnknownUser` anUser (unreachable on the IsPerson path) reproduced as a `static mut` via a `const carr()`.
  - **Config:** WHOIS_SIGNON_TIME **on** → 317 carries the extra signon `%ld` (idle+signon masked by the existing canonicalizer). IsMember active def = `find_channel_link(u->user->channel, c)`. IsChannelName uses Rust `cid_ok`.
  - **Verification:** L1 `s_user_who_diff` zero‑diff vs `cref_parse_who_arg` over 12 WHOX arg strings (`o`/`%`flags/`,token` len‑cap/unknown chars). L2 `golden_s_user` 2/2 byte‑identical: **WHOIS alice** (311/312/317‑masked/318) + **WHO alice** (352/315). The Rust `ircd` links with `s_user_link.o` substituted (`m_who`/`m_whois`/`parse_who_arg` resolve to ircd‑common). Full `cargo test` green; 0 warnings. **Deferred to S2S/multi‑client L2:** WHOX, channel WHO/WHOIS, wildcard WHOIS, the `hunt_server` remote path (ported faithfully, covered by review + the L1 differential).
