p5.md:14:- **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.
p5.md:15:  - **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.
p5.md:16:  - **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).
p5.md:17:  - **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`).
p5.md:19:  - **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.
p5.md:22:  - **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.)
p5.md:25:  - **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).
p5.md:29:  - **`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()`.
p5.md:31:  - **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).
p5.md:32:- **2026‑06‑02 — P5e (s_user.c USERHOST/ISON cluster) merged.** `m_userhost` (s_user.c:3073) and `m_ison` (s_user.c:3127) ported into `ircd-common/src/s_user.rs`, extending the existing `s_user_link.o` partial port — two more `#ifndef PORT_USER_P5` regions guard them out of the link set; the cref oracle keeps the full unguarded `s_user.o`. **Pure handler cluster** (both are `msgtab` rows, client‑reachable): no parse.rs edit (the `m_userhost`/`m_ison` decls parse.rs imports from `ircd_sys::bindings` resolve to the Rust defs at link). The shared C file‑statics `buf`/`buf2` are replaced by private Rust `Vec<u8>` accumulators — faithful since each is used transiently within a single call (no cross‑call state); the C remnant (`m_umode`/`register_user`) keeps its own `buf`.
p5.md:34:  - **Faithfulness notes:** `m_userhost` walks up to 5 targets across `parv[]` via the `idx` index (`parv[++idx]`), space‑separates entries (the `if(*buf2)` "not the first entry" guard → tracked by `buf2` staying non‑empty), reproduces the `strncat(buf, buf2, sizeof(buf)-len)` cap **and** C's `len += strlen(buf2)` (the full length even when truncated), and the re‑head/flush at `len > BUFSIZE-(NICKLEN+5+HOSTLEN+USERLEN)=419` (effectively dead for ≤5 short targets, ported anyway). `m_ison` strtoken‑splits the **single** trailing param, appends `name` + a trailing space per hit, and breaks at `len+i > BUFSIZE-4` ("leave room for ` \r\n\0`").
p5.md:35:  - **Harness fix (cross‑process boot lock).** Adding a 6th `ircd-golden` test *binary* surfaced a latent flake: `cargo test` runs each test file as its own process and runs several in **parallel**, but the boot serializer was an in‑process `Mutex` — so two binaries (e.g. `golden_debug` + `golden_s_user_userhost`) raced to bind the one fixture port 16667 → `BrokenPipe`/diff failures. Replaced the `Mutex` with a **cross‑process advisory file lock** (`libc::flock(LOCK_EX)` on `fixtures/.port.lock`, held in the `Ircd` guard, released when its fd closes after `child.kill()+wait()`); separate fds contend even within one process, so it also serializes the parallel `#[test]` threads inside a binary. `libc` added as an `ircd-golden` dep; the lock artifact is gitignored.
p5.md:36:  - **Verification:** L2 `golden_s_user_userhost` 2/2 byte‑identical (**USERHOST alice nemo** → 302 with the multi‑target `idx` walk + a `find_person` miss on nemo; **ISON :alice nemo** → 303 with a hit + a miss); both replies fully deterministic (no time tokens → no canonicalizer change). The Rust `ircd` links with `m_userhost`/`m_ison` defined `T` from ircd‑common. Full `cargo test` green and **stable across repeated runs** (flake fixed); 0 warnings. **Deferred to multi‑client L2:** the `>BUFSIZE` batch‑flush paths (need many recipients; ported faithfully, covered by review).
p5.md:40:  - **Harness gotcha — sentinel collision with the split notice.** The `SPLIT_CONNECT_NOTICE` (`NOTICE alice :…split-mode.`) can arrive *after* the 422 the registration drain stops on (TCP‑chunking race) and contains the substring ` NOTICE alice `, which falsely satisfied the NOTICE‑self `read_until(" NOTICE alice ")`. Fixed by draining after register before sending. (PRIVMSG's sentinel ` PRIVMSG alice ` doesn't collide, so only NOTICE‑self failed at first.)
p5.md:41:  - **`format!` per user request — N/A here, documented.** `m_message` is pure routing: every reply is handed to a **C variadic sender** (`sendto_one`/`sendto_prefix_one`/`sendto_channel_butone`/`sendto_match_butone`) as format‑string + raw C‑string args; there is no intermediate string to build, and pre‑formatting would be wrong (a sender rewrites the per‑recipient prefix itself). Senders imported from `ircd_sys::bindings` (bindgen declares `pattern: *mut c_char` → no `clashing_extern_declarations`); `strrchr` (rindex) added to the local extern block.
p5.md:42:  - **Faithfulness notes:** `syntax` is declared **outside** the per‑target loop (an oper's `$$`/`$#` match leaks into later targets — verbatim C); the C for‑loop post‑expr `nick = strtoken(…), penalty++` (which must run even on `continue`) is modeled with a labeled `'target` block where each C `continue` → `break 'target`, followed by the advance; the nick!user@host and user[%host]@server paths NUL‑terminate `nick` in place then restore `!`/`@`/`%`, exactly as C mutates the strtoken slice.
p5.md:43:  - **Verification:** `golden_s_user_message` 7/7 byte‑identical; `m_private`/`m_notice` defined `T` from ircd‑common in the Rust `ircd`; full `cargo test` green (all 12 `*_diff` L1 + layout + all 7 golden suites); 0 warnings. **Deferred to S2S L2:** the server‑sourced/uid (`find_uid`), `@server`‑forwarding, and oper `$$`/`$#`‑mask broadcast paths (need a server link; ported faithfully, covered by review).
p5.md:45:  - **Config that shaped the port (verified in `cbuild/config.h`):** `USE_SERVICES` **off** → `m_away`'s two `#ifdef USE_SERVICES check_services_butone` blocks not compiled/not ported. **`AWAY_MOREINFO` defined** (config.h:580) → `send_away`'s else branch (away == NULL) is the `":%s 301 %s %s :Gone, for more info use WHOIS %s %s"` form, **not** the `#else` RPL_AWAY‑"Gone" — ported the `#ifdef AWAY_MOREINFO` branch. Both callers guard on FLAGS_AWAY ⇒ `away != NULL` in practice, so the live path is the `if (acptr->user->away)` RPL_AWAY‑with‑text branch (the AWAY_MOREINFO else is faithfully ported but effectively dead).
p5.md:46:  - **Faithfulness note — the non‑MyConnect away‑buffer leak reproduced verbatim.** For a remote (`!MyConnect`) `sptr` marking away, C `MyMalloc`s the buffer into the local `away` but only assigns `sptr->user->away = away` (and `strcpy`s) inside the `if (MyConnect(sptr))` block — so the remote‑away allocation is leaked. The Rust port mirrors this exactly (allocate + set `FLAGS_AWAY` + the `+a` server propagation unconditionally; store + strcpy + 306 ack only under `my_connect`). Not "fixed" (P8 territory).
p5.md:48:  - **Verification:** L2 `golden_s_user_away` 3/3 byte‑identical: **AWAY :gone fishing / AWAY** (306 RPL_NOWAWAY then 305 RPL_UNAWAY — both `m_away` arms), **WHOIS alice while away** (`send_away` → `301 alice alice :brb` inside the WHOIS block; 317 idle/signon masked by the existing canonicalizer), **bob PRIVMSG alice while away** (two clients → bob gets `301 bob alice :brb` via `send_away` from the Rust `m_message`). `m_away`/`send_away` defined `T` from ircd‑common in the Rust `ircd`; full `cargo test` green (all 12 `*_diff` L1 + layout + all 8 golden suites); 0 warnings. No new canonicalizer masks needed. **Deferred to S2S L2:** the `:%s MODE %s :±a` server‑burst propagation (no server link under `‑s`/`‑p standalone`; ported faithfully, covered by review).
p5.md:50:  - **`send_umode` is called from three still‑C TUs** (`register_user` in s_user.c, `s_service.c`, `s_serv.c` — all via `send_umode(NULL, …, buf)`); porting it means **those C callers now resolve to the Rust def at link**, so byte‑faithfulness is load‑bearing beyond just `m_umode`. This is why `send_umode` gets its own L1 differential (`s_user_umode_diff`): the +/- mode‑diff string builder with `cptr=NULL` is socket‑free, so it's L1‑testable directly (10 cases over old/current‑flags/sendmask/MyClient combos — pure/grouped/mixed add‑del/`SEND_UMODES` vs `ALL_UMODES` filtering).
p5.md:52:  - **`format!` per user request:** m_umode's **query reply** ("+<modes>", pure ASCII) is built with a Rust `String` + `CString` (the format!‑family path), then handed to the C variadic `sendto_one` as the `replies[RPL_UMODEIS]` `%s`. `send_umode` writes its +/- diff into the **caller‑owned** `*mut c_char` buffer with the add/del grouping algorithm — that cannot be a `String` (the bytes must land in the C buffer, no owned allocation), so it stays a faithful byte‑builder. No integer conversions reach the senders → the P5c `%lu`‑width rule is N/A. Documented in‑module.
p5.md:53:  - **Faithfulness notes:** the 'a' switch case **falls through** to default for server/internal callers (`cptr==NULL || IsServer(cptr)`) — reproduced by the `_ =>` arm doing the 'a'‑specific away‑clear then continuing into the flag‑flip; a local client's `+a` is a no‑op (`continue`, the C switch `break`). The misleading‑indent `return 1;` after the USERSDONTMATCH `else` is **inside** the mismatch‑`if` block (unconditional once the guard trips) — reproduced. Inner loop advances the char pointer **first** so `continue` = C's switch `break`. ClearOper/ClearLocOp also set `status=STAT_CLIENT`; SetClient = `IsAnOper?STAT_OPER:STAT_CLIENT` — reproduced. The private `UMODE_BUF` static replaces the s_user.c file‑static `buf` (transient within send_umode_out; the C remnant keeps its own `buf`). `istat.is_user`/`is_oper`/`is_awaymem` are `u_long`; `servp->usercnt` is `[c_int;3]`.
p5.md:54:  - **Verification:** L1 `s_user_umode_diff` zero‑diff vs `cref_send_umode` (10 cases). L2 `golden_s_user_umode` 2/2 byte‑identical: **MODE alice +i ; MODE alice** (echo `:alice MODE alice :+i` via the ALL_UMODES local echo + `221 alice +i`), **MODE alice +iw-i ; MODE alice** (parse nets to +w / ‑i → echo `:alice MODE alice :+w` + `221 alice +w`, exercising the +/- grouping). `m_umode`/`send_umode`/`send_umode_out` defined `T` from ircd‑common in the Rust `ircd`; full `cargo test` green (all `*_diff` L1 + layout + all 9 golden suites); 0 warnings. No new canonicalizer masks. **Deferred to S2S L2:** the `send_umode_out` server‑broadcast loop (`fdas`/`local[]` → `:uid MODE name :modes`) and the oper/restricted (`det_confs_butmask`, ERR_RESTRICTED) paths — need a server link / oper; ported faithfully, covered by review + the L1 differential.
p5.md:58:  - **Faithfulness notes:** m_ping reassigns the local `origin` (find miss → `cptr->name`) but the else‑branch PONG still echoes the **original** `parv[1]` (verbatim C); `match(destination, ME) != 0` = destination does **not** match the server name. m_pong clears `FLAGS_PINGSENT` (`flags` is `c_long`) before the target lookup; the `&me` no‑destination default uses `addr_of_mut!(me)`.
p5.md:59:  - **Verification:** L2 `golden_s_user_ping` 2/2 byte‑identical: **`PING :hello`** → `:irc.test PONG irc.test :hello` (the find‑miss → else echo path), **`PING alice noserver.invalid`** → `402 ... :No such server` (the `find_server` miss). `m_ping`/`m_pong` defined `T` from ircd‑common in the Rust `ircd`; full `cargo test` green (all `*_diff` L1 + layout + all 10 golden suites); 0 warnings. No new canonicalizer masks. **No L2 path for `m_pong`** (clears `FLAGS_PINGSENT`; only emits on the `!IsMe` forward path → needs a server/target link) — ported faithfully, covered by review.
p5.md:62:  - **Faithfulness notes:** `encr = parv[2]` may be NULL (OPER min‑params is 2) but is only dereferenced past a `find_Oline` hit, so the wrong‑name 491 path is safe; `StrEq` reproduced via libc `strcmp` (same UB as C if a matching O‑line is hit with no password — not exercised). The transient host split `s = index(host,'@'); *s='\0'; …; *s='@'` is reproduced verbatim even though `s` (after‑'@') is unused under NO_OPER_REMOTE‑off (net no‑op; mutates the C‑owned host in place and restores it). `istat.is_oper` is `u_long`; `servp->usercnt` is `[c_int;3]` → `usercnt[2] += 1`. `%d`/`%c` args (the `:%s %d %s :Too many …` lines + the SCH_NOTICE operator‑letter) cast to `c_int` (the P5c width rule).
p5.md:65:  - **Verification:** L2 `golden_s_user_oper` 1/1 byte‑identical over three OPER attempts on one registered client: **`OPER nope wrong`** → `491 ERR_NOOPERHOST` (find_Oline miss), **`OPER alice wrong`** → `464 ERR_PASSWDMISMATCH` (StrEq fails → detach path), **`OPER alice secret`** → success: `:alice MODE alice :+o` (send_umode_out ALL_UMODES local echo) + `381 RPL_YOUREOPER`. `m_oper` defined `T` from ircd‑common in the Rust `ircd`; full `cargo test` green (67 passed, 0 failed: all `*_diff` L1 + layout + all 11 golden suites); 0 warnings. No new canonicalizer masks (no time tokens). **Deferred to S2S L2:** the `send_umode_out` server‑broadcast loop, the LocOp (`o`) path, and the SCH_NOTICE to server‑notice subscribers — need a server link / a subscribed oper; ported faithfully, covered by review.
p5.md:69:  - **`format!` per user request — N/A for the comment (documented).** `m_quit`'s `comment` is built from `parv[1]`, an arbitrary possibly‑non‑UTF‑8 C‑string under C `snprintf` truncation at `TOPICLEN`; per the standing P5c rule that can't pass through a UTF‑8 `String`/`format!` without corruption → it's a faithful byte‑builder (`snprintf_prefix_str` emulates `snprintf(buf,size,"<prefix>%s",s)` truncation; the MyConnect path then `strcat`s the closing `"` exactly as C). `m_post`'s notice is handed straight to the C variadic `sendto_flag` (no intermediate string). No integer conversions reach the senders → the P5c `%lu`‑width rule is N/A.
p5.md:70:  - **Faithfulness notes:** `IsServer(sptr)` → return 0; the MyConnect path is `snprintf(comment, TOPICLEN, "\"%s", parv1)` then `strcat(comment, "\"")` (= `"` + parv1 + `"`), the remote path is `snprintf(comment, TOPICLEN+1, "%s", parv1)` (raw, no quotes — reproduced even though it needs an S2S link to reach); `parv[1]` is `NULL`‑guarded to `""` exactly as C (`(parc>1 && parv[1]) ? parv[1] : ""`).
p5.md:71:  - **Verification:** L2 `golden_s_user_quit` 2/2 byte‑identical: **`QUIT :gone fishing`** (registered) → `ERROR :Closing Link: alice[~alice@127.0.0.1] ("gone fishing")`, **`POST`** (unregistered) → the `020` connection notice + `ERROR :Closing Link: [unknown@127.0.0.1] ("")`. `m_quit`/`m_post` defined `T` from ircd‑common in the Rust `ircd`; full `cargo test` green (all `*_diff` L1 + layout + all 12 golden suites); 0 warnings. No new canonicalizer masks (no time tokens). **Deferred to S2S L2:** the remote (`!MyConnect`) QUIT branch (needs a server link; ported faithfully, covered by review).
p5.md:73:  - **Self‑contained — `buf` touched only transiently.** `m_pass` stores `parv[1]` into `cptr->passwd` (`strncpyzt`, 21) and, when PASS precedes USER/SERVICE with extra params, stages `"<p2> <p3> <p4>"` (byte caps 15/100/5) into the shared C file‑static `buf[BUFSIZE]` then **immediately** copies it out via `cptr->info = mystrdup(buf)` — `buf` is never read across calls, so a **private** module static `PASS_BUF: [c_char; BUFSIZE]` is faithful and the C remnant's `buf` (register_user/m_user/m_userhost…) is untouched. No allocators of its own, no list linkage. `MyFree`→libc `free`; `mystrdup`/`DefInfo` are the P1/P2 Rust ports (resolve via bindings); `strncpy`/`strncat` added to the local extern block; `strncpyzt` reproduced as a helper (`strncpy` + `x[N‑1]='\0'`).
p5.md:74:  - **`format!` per user request — N/A for the staged buffer (documented in‑module).** The staged buffer is built from `parv[2..4]` — arbitrary possibly‑non‑UTF‑8 C‑strings under byte‑precision `strncpyzt`/`strncat` truncation caps; per the standing P5c rule a UTF‑8 `String`/`format!` would corrupt those bytes and can't reproduce strncpy NUL‑padding / strncat caps → it stays a faithful byte‑builder (the only literals are the `" "` separators). `passwd` is likewise a byte‑faithful `strncpyzt`. No integer conversions → the P5c `%lu`‑width rule is N/A. `m_pass` emits **no** wire output (returns 1; no `sendto_*`).
p5.md:75:  - **L1 is the workhorse (m_pass is socket‑free).** `s_user_pass_diff` drives `c_m_pass` (resolves to Rust) and `cref_m_pass` on two parallel fresh clients and asserts identical `passwd[0..21]` bytes + `info` C‑string over 6 cases: passwd‑only (parc=2), full info‑staging (parc=5), passwd 21‑byte truncation, `buf[0]` 15‑cap, the `cptr->user`‑set early return (info stays `DefInfo`), and the strncat 100/5 caps. L2 `golden_s_user_pass` 1/1 byte‑identical: a raw unregistered client `PASS secret ircd‑2.11 +options` then NICK/USER — minimal.conf has no I‑line password so PASS is stored and ignored; the welcome banner stays byte‑identical (the "drive the path, assert no regression" gate, cf. s_debug). The full PASS form also exercises the info‑staging strcat/strncat branch live in both binaries. No new canonicalizer masks.
p5.md:76:  - **Verification:** `m_pass` defined `T` from ircd‑common in the Rust `ircd`; L1 `s_user_pass_diff` 1/1 zero‑diff; full `cargo test` (all `*_diff` L1 + layout) + all 14 golden suites green; 0 warnings. **Deferred to S2S L2:** the server‑link `parv[2..4]` version/options consumption in `m_server` (the staged `info` is read there; ported faithfully on the m_pass side, covered by the L1 staging differential + review).
p5.md:79:  - **Faithful raw‑pointer translation (the L1 differential is the safety net).** The C body is intricate in‑place buffer surgery: the outer `strtoken` NUL‑splits the input; the inner loop walks `cbuf` with `strtoken`, restoring each comma it split (`p2[-1] = ','`) **except** at a dup match (`break`); unique tokens are `strcpy`'d and `cp` advances by `(p - s)` (token length incl. the split NUL). The Rust port mirrors every pointer op verbatim (`p2.offset(-1)`, `cp.offset(p.offset_from(s))`). `mycmp` is IRC case‑insensitive → `ABC,abc` dedups.
p5.md:84:  - **Config that shaped the port (verified `cbuild/config.h`):** `NICKLEN`=`LOCALNICKLEN`=**15** (struct_def.h:49, config.h:726) → the length cap is 15 in both server/client contexts (the conditional kept faithfully). **`MINLOCALNICKLEN` is `#undef`** (config.h:732) → its `#ifdef` short‑nick early‑return block is **not compiled** and is omitted. `isvalidnick(c)` = `char_atribs[(u_char)c] & NVALID` (common_def.h:69; `NVALID`=0x40) → reproduced against the P1 `crate::match_::char_atribs` table; `isdigit(*nick)` reproduced as `(*nick as u8).is_ascii_digit()` (byte‑equivalent for the only true range 0x30‑0x39, and safe for high‑bit bytes that C's `isdigit` would pass UB‑ly).
p5.md:86:  - **Verification:** L1 `s_user_do_nick_name_diff` zero‑diff vs `cref_do_nick_name` over 17 cases (valid nick server/client, leading `-`/digit, `anonymous`/`ANONYMOUS` casefold, embedded space/`.`/`~` truncation, `[`/`]`/`` ` ``/`|`/`}` valid‑char retention, `>15` length cap, empty, single char) — comparing **both** the returned length and the mutated buffer bytes. `do_nick_name` defined `T` from ircd‑common in the Rust `ircd`; full `cargo test` green (all `*_diff` L1 + layout + all golden suites; 0 failed); 0 warnings. No new golden scenario or canonicalizer mask — the existing registration banner already drives `do_nick_name` via the still‑C `m_nick`/`register_user` (a wrong port would diff the echoed nick). **Deferred:** the remaining s_user.c utilities (`next_client`/`hunt_server`/`is_allowed`) + the NICK/registration cluster (`m_nick`/`m_unick`/`m_user`/`register_user`/`save_user`/`m_save`/`m_kill`) stay in `s_user_link.o` (C) for later P5 sub‑phases.
p5.md:95:  - **Config (verified `cbuild/config.h`):** `UNIXPORT` **off** → the `IsUnixSocket(cptr)` inpath branch not compiled → `inpath = cptr->sockhost` unconditionally; `USE_SERVICES` **off** → the `check_services_butone(SERVICE_WANT_KILL,…)` block not compiled/not ported; `USE_SYSLOG`/`SYSLOG_KILL` **off** → the syslog block not compiled. `KILLCHASETIMELIMIT`=90, `TOPICLEN`=255. The service‑target SCH_KILL branch and the `isdigit(sid[0]) ? sid : "2.10"` SID‑vs‑"2.10" fallback are ported faithfully.
p5.md:96:  - **`format!` per user request — N/A for the buffers (documented in‑module).** m_kill's two `sprintf` targets (`buf` = `"%s%s (%s)"` killer+`(L)`?+comment; `buf2` = `"Local Kill by %s (%s)"` / `"Killed (%s)"`) interpolate **arbitrary client‑supplied bytes** (the KILL comment `parv[2]`/`path` and the `index(path,' ')` killer substring), so per the standing P5c/P5l rule a UTF‑8 `String`/`format!` would corrupt non‑ASCII bytes and can't reproduce the byte‑exact `sprintf` assembly → faithful byte‑builders (private module statics `KILL_BUF`/`KILL_BUF2` replace the shared C `buf`/`buf2`, written only transiently within the call → the C remnant's `buf`/`buf2` are untouched). The wire replies go straight to the C variadic senders (no intermediate string). No sub‑`long` integer reaches the senders → the P5c `%lu`‑width rule is N/A.
p5.md:97:  - **Faithfulness notes:** the killer‑substring walk (`while (killer > path && *killer != '!') killer--; if (killer != path) killer++`) reproduced with raw‑pointer `>`/`.offset(-1)`/`.add(1)`; `path[TOPICLEN]='\0'` oper‑overlong truncation; the `chasing && !IsClient(cptr)` echo‑back; `acptr->flags |= FLAGS_KILLED` set only inside the server‑propagation branch; `exitc = EXITC_KILL` only on the local‑oper‑kill path.
p5.md:98:  - **L2 is the gate — no clean L1 data op (m_kill ends in `exit_client`, which closes the connection).** New multi‑client `golden_s_user_kill` 1/1 byte‑identical: oper alice (`kill_grant.conf` `K` flag) `KILL bob :reason` of a registered target → bob's victim transcript = the `:alice!… KILL bob :<inpath>!alice (reason)` notice (`sendto_prefix_one`, MyConnect path) + the `ERROR :Closing Link: bob[…] (Local Kill by alice (reason))` from `exit_client`. Two local clients with no servers ⇒ the `!MyConnect||!IsAnOper` server‑burst branch is skipped (no `sendto_serv_v`/`FLAGS_KILLED`); the SCH_KILL oper notice goes to alice's socket, not bob's. The 401 (no‑such‑nick) grant + 481 deny branches are already covered by `golden_s_user_is_allowed`. Fully deterministic → no new canonicalizer masks. `m_kill` defined `T` from ircd‑common in the Rust `ircd`; full `cargo test` green (45 passed, 0 failed: all `*_diff` L1 + layout + all golden suites incl. the new kill scenario); 0 warnings. **Deferred to S2S L2:** the server‑sourced KILL, the `sendto_serv_v SV_UID` broadcast + `get_history` chase, and the server/service‑target `ERR_CANTKILLSERVER` paths (need a server link; ported faithfully, covered by review).
p5.md:100:  - **L2 is the gate — the registration banner (the project's anchor scenario since P0‑L2) directly drives `register_user` through the still‑C `m_nick`/`m_user`.** No clean L1 data op (side‑effect‑heavy: hash inserts, counters, banner `sendto_one`, `exit_client`; needs a live fd) → like P5e/g/i/k, RED = undefined symbol, GREEN = port, gate = `golden_registration` (`registration_banner_matches_reference` + `pass_registration_banner_matches_reference`) byte‑identical. A wrong port diffs the banner immediately.
p5.md:101:  - **Config‑resolved scope (verified `cbuild/config.h`):** `XLINE`/`RESTRICT_USERNAMES`/`UNIXPORT` **off** → the X‑line `conf` walk, the isvalidusername check, and the IsUnixSocket host branch are not compiled/not ported. `NO_PREFIX` **absent** → the `~`/`^`/`+`/`-`/`=` ident‑prefix logic is **live** (ported). `USE_IAUTH` **on** → the iauth early‑parse/XOPT_REQUIRED/null‑username block (incl. the `static last`/`count` → module statics `IAUTH_LAST`/`IAUTH_COUNT`) is **compiled** and ported faithfully (inert under `‑s` since `iauth_options==0`). `WHOISTLS_NOTICE`/`SPLIT_CONNECT_NOTICE` defined → both NOTICEs ported (the split notice is dropped by the existing canonicalizer). `USE_HOSTHASH`/`USE_IPHASH` **on** → `add_to_hostname/ip_hash_table` ported. `USE_SERVICES`/`CLIENTS_CHANNEL` **off** → the services block (incl. the second `send_umode`+`check_services_num`) and the CONN client‑channel notice omitted.
p5.md:102:  - **Shared‑static hazard resolved.** `register_user` writes the s_user.c file‑statics `buf`/`buf2` only **transiently within the call** (the K‑line reason, the `nick!user@host` welcome string, the `send_umode` output read at the UNICK loop); the still‑C callers all `return register_user(...)` and never read them afterward, and `register_user` never reads a caller‑set value → private module statics `REG_BUF`/`REG_BUF2` are faithful, the C remnant's `buf`/`buf2` untouched (cf. P5l `PASS_BUF`, P5p `KILL_BUF`).
p5.md:103:  - **`format!` per user request — documented in‑module.** The welcome `nick!user@host` string and the `K‑lined: %.80s` reason interpolate arbitrary client‑supplied C‑string bytes (validated nick / ident / resolved host) under byte‑precision copies; per the standing P5c/P5f rule a UTF‑8 `String`/`format!` would corrupt non‑ASCII bytes, so they stay faithful byte‑builders (`push_cstr`/`store_cbuf`). The username‑prefix assembly is byte‑precision `strncpy`. Every wire reply goes straight to a C variadic sender (no intermediate string). The P5c `%lu`‑width rule applied: every sub‑`long` int arg to the senders (the `is_m_users`/`is_myclnt`/`is_l_myclnt` `u_long` counters, `fd`, `flags`) is cast to its exact `%`‑width (`%d`→`c_int`, `%X`→`u_int`). ⇒ `format!` is not "possible" here without losing byte‑faithfulness.
p5.md:104:  - **Faithfulness notes:** the `exit_msg[8]` reject table + its index arithmetic (`if i>-1 {i=-1}; i+=8; if i>8 {i=8}`) reproduced verbatim (valid index for `check_client ∈ [‑8,‑1]`; a contract violation would panic in Rust vs OOB‑read in C — an acceptable divergence for an impossible input); `bzero(passwd)` → `write_bytes`; `aconf = sptr->confs->value.aconf` via the `SLink` union; the leaf‑server UNICK loop walks `fdas.fd[]`/`local[]` (via `local_base()`); the `(*buf)?buf:"+"` UNICK umode arg. `aClient.flags` is `c_long` → all flag masks cast `as c_long`.
p5.md:105:  - **Verification:** `register_user` defined `T` from ircd‑common in the Rust `ircd` (the still‑C `m_nick`/`m_user`/`m_unick` now call the Rust def); `golden_registration` 2/2 byte‑identical; full `cargo test` green (all `*_diff` L1 + layout + all golden suites, 0 failed); `cargo build` 0 warnings. No new canonicalizer masks. **Deferred to S2S L2 / review:** the `!MyConnect` UNICK‑source path, the leaf‑server UNICK feed, the iauth XOPT_REQUIRED/EARLYPARSE branches, and the K‑line/check_client rejection exits (need a server/iauth/matching‑conf link under `‑s`; ported faithfully, covered by review + the banner regression). **Remaining s_user.c (C):** `next_client`/`hunt_server` (hash‑dependent → S2S/L2) + the `m_nick`/`m_unick`/`m_user`/`save_user`/`m_save` handlers stay in `s_user_link.o`.
p5.md:107:  - **L2 is the gate — the registration banner (the P0‑L2 anchor scenario) directly drives `m_user`.** No clean L1 data op (side‑effect heavy: `make_user`, list reorder, `mystrdup`, then `register_user`/`exit_client`; needs a live fd) → like P5q, RED = undefined symbol, GREEN = port, gate = `golden_registration` byte‑identical. Its `USER alice alice localhost :Alice` line exercises `make_user` + the "no flags" umode‑parse path (`'a'` not a digit, not `+/-` → `what==0` → break) + the `register_user` tail‑call. A wrong port diffs the banner immediately.
p5.md:111:  - **Faithfulness notes:** the umode `+/-` token loop reproduces C's `switch{case '+'/'-':…continue; default:break;}` then `if(what==0)break` as a labeled `'outer` loop (the `default` `break` falls through to the `what==0` check, the for‑loop `break` is `break 'outer`); the RFC‑bit branch (`isdigit(*umodes)` → scan rest digits → `UFLAGS & atoi`) ported via `is_ascii_digit`+`atoi`; the new `is_unknown` helper (`status == STAT_UNKNOWN`). `(*me).serv->refcnt += 1`; `find_server_string((*me).serv->snum)`.
p5.md:112:  - **Verification:** `m_user` defined `T` from ircd‑common in the Rust `ircd` (the still‑C `m_reg`/parse path now routes an unregistered USER to the Rust def); `golden_registration` byte‑identical; full `cargo test` green (40 test binaries, 0 failed: all `*_diff` L1 + layout + all golden suites); `cargo build` 0 warnings. No new canonicalizer masks. **Deferred to S2S/review:** the server‑only‑P‑line reject (`find_bounce`+`exit_client`), the TLS P‑line branch, the `462` re‑register reject (server‑introduced client), and the `+/-`/RFC‑bit umode paths beyond the banner's "no flags" case (ported faithfully, covered by review). **Remaining s_user.c (C):** `next_client`/`hunt_server` (hash‑dependent → S2S/L2) + `m_nick`/`m_unick`/`save_user`/`m_save` stay in `s_user_link.o`.
p5.md:114:  - **`goto` control flow refactored into helper fns (faithful).** m_nick has two interior labels reached by `goto`: `badparamcountkills` (the bad‑server‑NICK complaint, returns 0; reached on the server‑parc≠2 fall‑through, the `sptr==cptr` collision, and `IsServer(sptr)` in nickkilldone) and `nickkilldone` (the validated‑nick tail; reached from ~6 sites). Ported as two private fns — `bad_param_count_kills(cptr,sptr,parc,parv)` and `nick_kill_done(cptr,sptr,nick,parc,parv)` (the latter re‑calls the former for its `IsServer(sptr)` arm) — so every C `goto X` becomes `return X(...)`. `nick`'s mutated buffer (possibly rewritten to the UID via `strncpyzt`) is threaded through as a `*mut c_char`; `user`/`host` stay local to m_nick (only the nick‑collision/`do_nick_name`‑fail blocks use them, never nick_kill_done). The `lp` channel‑walk (sets the `return 15` bigger‑penalty signal) lives entirely in nick_kill_done.
p5.md:116:  - **`format!` per user request — documented in‑module.** The nick‑collision `path` strings (m_nick `"(%s@%s[%s](%s) <- %s@%s[%s])"` ×2, m_unick `"(%s@%s)%s <- (%s@%s)%s"`) interpolate **arbitrary client C‑string bytes** (username/host/server names) under C `sprintf` → per the standing P5c/P5f/p rule a UTF‑8 `String`/`format!` would corrupt non‑ASCII bytes, so they're faithful byte‑builders (`push_cstr` into the private static `NICK_PATH_BUF`, which replaces m_nick/m_unick's `char path[BUFSIZE]` and badparamcountkills's `char buf[BUFSIZE]` — written only transiently within the call, so the C remnant's shared `buf`/`buf2` are untouched). Every wire reply goes straight to a C variadic sender. No sub‑`long` int reaches a sender → the P5c `%lu`‑width rule is N/A.
p5.md:117:  - **Faithfulness notes:** m_unick rebuilds a server‑introduced client exactly like m_user (`make_client`→`add_client_to_list`→`make_user`→`servp`/`refcnt`/`server`→`mystrdup(realname)` capped at REALLEN→`strncpyzt` username/host→`reorder_client_in_list`→name/uid/sip set + hash inserts→`m_umode(NULL,acptr,3,pv)` with the stack `pv[4]`→`register_user`); the `(acptr->user)?…:"???"` NULL‑guards on the both‑die nick‑collision report reproduced; `bad_to(acptr->name)` for the ERR_NICKCOLLISION target; `cptr ? cptr->name : ME` and the `cptr ? '!' : ' '` separator in save_user's SAVE propagation; `(*sptr).flags |= FLAGS_KILLED as c_long`. The leading‑`-`/digit/`anonymous` rejects come from the already‑Rust `do_nick_name` (P5n); the SID‑nick‑prefix burn (`strncasecmp(me.serv->sid, nick, SIDLEN)`) and BadNick server‑KILL ported faithfully (S2S).
p5.md:118:  - **Verification:** L2 `golden_s_user_nick` 3/3 byte‑identical: **local nick change** (registered alice `NICK alice2` → `sendto_common_channels` echoes `:alice!~alice@127.0.0.1 NICK :alice2` back to the sender — the nickkilldone "changing" branch: add_history, del/add client hash, strcpy(name)), **erroneous nick** (`NICK -bad` → `do_nick_name`=0 → `432 ERR_ERRONEOUSNICKNAME`), **nick in use** (two clients; bob `NICK alice` → find_client hit, `!IsServer` → `433 ERR_NICKNAMEINUSE`). `m_nick`/`m_unick`/`m_save` defined `T` from ircd‑common in the Rust `ircd`; full `cargo test` green (all `*_diff` L1 + layout + all golden suites incl. the 3 new nick scenarios, 0 failed); `cargo build` 0 warnings. No new canonicalizer masks (no time tokens). **Deferred to S2S L2:** `m_unick` (UNICK arrives only from a server link), `m_save` + `save_user`'s live SAVE emit (server‑sourced), and m_nick's server paths (SID‑nick burn, BadNick KILL, both‑SAVE collision) — ported faithfully, covered by review.
p5.md:120:  - **`channel_link.o` wiring gotcha (cost one RED cycle):** a *partial* port must **NOT** add `channel.o` to `PORTED` — `link_objs()` filters PORTED entries *before* the `channel.o`→`channel_link.o` `.map`, so the substitution never fires and the whole TU goes undefined. The seam is: leave `channel.o` out of `PORTED`, add the `channel.o`→`channel_link.o` arm to `link_objs()`, and a plain `sh -c` compile step (`channel.o`'s recipe carries no per‑object `‑D`, unlike send/s_user, so no `make --eval` needed). After fixing: RED showed exactly the four leaf symbols undefined; GREEN resolves them `T` from ircd‑common.
p5.md:127:  - **`golden_s2s_message` (P5f):** bidirectional PRIVMSG through the ported `m_message`/`m_private` core. remote→local: `:001BAAAAA PRIVMSG alice :…` → alice gets `:bob!~bob@remote.host PRIVMSG alice :…` (sendto_prefix_one full‑mask prefix). local→remote: `PRIVMSG bob` routes out the peer link. **Correction to the scenario brief:** under the negotiated caps the on‑wire form is name‑prefixed (`:alice PRIVMSG bob :…`), **not** UID‑prefixed/UID‑targeted — the test asserts the actual faithful bytes.
p5.md:128:  - **`golden_s2s_away` (P5g `send_away`):** peer sends `:001BAAAAA AWAY :gone fishing`; alice PRIVMSGes + WHOISes bob, hitting `send_away` from the m_message and m_whois remote‑target branches. **Faithful subtlety pinned:** `m_away` stores `user->away` only under `MyConnect(sptr)`, so a *remote* user's away text is never stored here → `send_away` takes its `else` branch and emits the generic `301 alice bob :Gone, for more info use WHOIS bob bob` (NOT the away text). Peer gets no echo of its own AWAY.
p5.md:130:  - **`golden_s2s_quit` (P5k `m_quit` + `exit_one_client`):** local `QUIT :leaving` → peer receives `:000AAAAAA QUIT :"leaving"`. Pins two faithful details: local QUIT reason is wrapped in literal double‑quotes by `m_quit` (the sentinel `exit_one_client` uses to classify a genuine QUIT), and the propagation source is the **UID**, not the nick.
p5.md:142:  - **Keystone mechanism — static function with remaining C callers → extern‑prototype switch.** `check_channelmask` is a file‑`static` whose definition we drop but whose callers mostly stay C. A `static` symbol has internal linkage → the C remnant's call can't resolve to the Rust `#[no_mangle]` def. Fix: guard the **forward prototype** (channel.c:55) `#ifndef PORT_CHANNEL_P5 static int check_channelmask(...) #else extern int check_channelmask(...) #endif` — so under `‑DPORT_CHANNEL_P5` the C remnant references it as an **external** symbol resolved to the Rust def at link. Linkage‑only change, runtime‑faithful. (This is the general recipe for every future channel.c cluster that drops a shared static — e.g. `get_channel`, `find_chasing`.) `del_invite` (already exported) now also resolves the still‑C `free_channel`/`m_kick`/`m_njoin` callers + the already‑Rust `s_misc.rs` exit‑cascade caller to the Rust def (the `canonize`/`send_away` seam). No parse.rs edit (`m_invite` resolves from the bindings import once the C def drops).
p5.md:144:  - **Faithfulness notes:** `del_invite`/`add_invite` reproduce the original's **crossed** istat bookkeeping verbatim — `add_invite` channel‑side does `is_useri++`, client‑side `is_banmem += len`/`is_invite++`; `del_invite` channel‑side `is_invite‑‑`, client‑side `is_banmem ‑= strlen(who)+1`/`is_useri‑‑` (note the original's `+= len` vs `‑= len+1` off‑by‑one is kept). The `who` string (`"<nick>!<user>@<host>"`) is built into a `[u8;91]` (`NICKLEN+USERLEN+HOSTLEN+3`) then `MyMalloc`+`strcpy`'d, byte‑exact. `m_invite` mirrors the two `chptr ? chptr->chname : parv2` ternaries and the `MyConnect(acptr) && chptr && sptr->user && is_chan_op` add_invite guard.
p5.md:147:  - **Verification:** L1 `channel_invite_diff` 2/2 zero‑diff vs `cref_del_invite`. L2 `golden_channel_invite` 4/4 byte‑identical: **success** (alice chanop of #chan `INVITE bob #chan` → `341 RPL_INVITING` to alice + `:alice!~alice@127.0.0.1 INVITE bob :#chan` to bob — the only path that fires the private `add_invite`/`list_length` live), **`401`** (INVITE ghost → find_person miss), **`442`** (alice not a member of bob's #other), **`476`** (`check_channelmask` reject on `#chan:nomatch.zzz` + the MyClient ERR_BADCHANMASK send). `m_invite`/`del_invite`/`check_channelmask` defined `T` from ircd‑common in the Rust `ircd`; full `cargo test` green; `cargo build` 0 warnings. No new canonicalizer masks (no time tokens). **Deferred to S2S L2:** the `!chptr` remote‑INVITE forward (`sendto_prefix_one` to a remote acptr), the `&`‑channel + `!MyClient(acptr)` reject, and `check_channelmask`'s server/`chan:mask` propagation branches (need a server link; ported faithfully, covered by review). **Remaining channel.c (C):** the mode (`m_mode`/`set_mode`/`match_modeid`/…), join (`m_join`/`m_njoin`/`can_join`/`get_channel`/`add_user_to_channel`), part/kick/topic, and display (`m_names`/`m_list`/`names_channel`/`find_chasing`) clusters stay in `channel_link.o`.
p5.md:149:  - **Cluster = a closed connected component over static call edges.** `can_send` (exported, channel_ext.h) calls only `IsMember`/`find_user_link`/`match_modeid`; `match_modeid` (static) calls only `match`/`match_ipmask`/macros — no other channel.c static. `match_modeid`'s *other* callers (`can_join`, `reop_channel`) stay C → handled by the **P5u extern‑prototype switch** (the forward `static`→`extern` at channel.c:75 under the guard, so the C remnant resolves to the Rust `#[no_mangle]` def at link; linkage‑only, faithful). `can_send` is already imported by the Rust `m_message` (P5f, s_user.rs:1155) + the WHO path (s_user.rs:3439) from `ircd_sys::bindings` → those bindgen decls resolve to the Rust def once the C def drops (the `is_chan_op`/`has_voice` P5t seam — **no s_user.rs edit needed**, unlike the plan's tentative Step 6 which would have diverged from established practice). No parse.rs edit (neither is a `msgtab` row).
p5.md:151:  - **Keystone finding — a latent P5u `is_member` faithfulness bug, fixed here.** The `is_member` helper (added in P5u for `m_invite`) cited the **commented‑out** `struct_def.h:773` (`find_user_link(c->members, u)`). The **active** `IsMember` (struct_def.h:775, lines 771‑773 are inside a `/* */` block) is `u && u->user && find_channel_link(u->user->channel, c)` — it walks the **client's** channel list, not the channel's member list. The L1 differential surfaced it immediately (the "+n member" case: Rust found the member via `chptr->members`, the oracle didn't via the empty `user->channel` → `0` vs `256`). Fixed `is_member` to `find_channel_link` (matching the active macro); the P5u `golden_channel`/`golden_channel_invite` + `channel_invite_diff`/`channel_leaf_diff` all still pass (the bug wasn't triggered by the invite scenarios, which link members both ways). The L1 fixture now links a real member into **both** `chptr->members` (→ `lp`) and `who->user->channel` (→ `IsMember`).
p5.md:153:  - **Faithfulness notes:** `match_modeid` reproduces the `for`‑loop post‑`continue` advance (`tmp = tmp->next; continue;` at each `continue` site, the natural advance at the bottom for the flags‑mismatch fall‑through), the `isdigit(nick[0])` UID‑ban fallback, the n!u→host/sockhost/sip/CIDR cascade, and the oper conf‑chain scroll (`acf = acf->next` when `IsAnOper`); arrays (`username`/`uid`/`host`/`sip`/`sockhost`) passed via `addr_of_mut!`, `name`/`alist->nick` are pointers. `can_send` computes `member` + `lp` unconditionally, returns 0 for `!MyConnect`, and the `+be` ban path only when the sender is a non‑op/non‑voice member.
p5.md:155:  - **Verification:** L1 `channel_can_send_diff` zero‑diff vs `cref_can_send` over 8 cases (+m non‑voiced/voiced/chanop member + non‑member, +n member/non‑member, plain‑member empty‑mlist ban path, remote `!MyConnect` bypass). L2 `golden_channel_can_send` 2/2 byte‑identical: **+m moderated** (alice chanop sets +m, bob joins as a plain member → bob PRIVMSG → `404 ERR_CANNOTSENDTOCHAN` via the Rust `m_message`→Rust `can_send` MODE_MODERATED branch) and **+n no‑external** (bob non‑member → MODE_NOPRIVMSGS branch); read‑barriers (`366`/`MODE` echoes) serialize the cross‑connection ordering so +m/+n is in effect before bob sends. `can_send`/`match_modeid` defined `T` from ircd‑common in the Rust `ircd`; full `cargo test` green (0 failed); `cargo build` 0 warnings. No new canonicalizer masks (no time tokens). **Deferred to S2S L2 / review:** `match_modeid`'s populated‑`mlist` `+b`/`+e` match (incl. the UID‑ban and CIDR `match_ipmask` paths) and the remote‑client `can_send` bypass under a server link (ported faithfully). **Remaining channel.c (C):** the mode (`m_mode`/`set_mode`/`add_modeid`/`del_modeid`/`send_mode_list`/`change_chan_flag`/`make_bei`/`free_bei`/`channel_modes`/`send_channel_*`), join (`m_join`/`m_njoin`/`can_join`/`get_channel`/`add_user_to_channel`/`reop_channel`/`free_channel`), part/kick/topic, and display (`m_names`/`m_list`/`names_channel`/`find_chasing`/`check_string`) clusters stay in `channel_link.o`.
p5.md:159:  - **Faithfulness notes:** the `+` prefix then each bit in **exact C order** (s **else** p; m; t; i; n; a; q; r), then `l` (`sprintf(pbuf,"%d ",limit)` if `IsMember||IsServer`), then `k` (`strcat(pbuf, key)` if `IsMember||IsServer`), then the terminator. The C `*mbuf++ = '\0'` post‑advance is dead (mbuf never re‑read) → written in place to avoid a spurious `unused_assignments` warning (behaviorally identical). `IsMember` = the active `find_channel_link(cptr->user->channel, chptr)` (the P5v‑corrected helper); `IsServer` = `is_server`. **`format!` per user request — N/A, documented in‑module:** the `+l`/`+k` params must match C `sprintf("%d ")`/`strcat` byte‑for‑byte into the caller's raw `char*` buffer; `format!` would change framing. Kept as the libc calls.
p5.md:165:  - **Faithfulness notes:** `add_user_to_channel` — `chptr->users++ == 0` is the unconditional post‑increment (`was_zero` capture then `+= 1`); the locked‑channel history reset `bzero(&chptr->mode, sizeof(Mode))` → `ptr::write_bytes(addr_of_mut!((*chptr).mode) as *mut u8, 0, size_of::<Mode>())` gated on `*chname != '!'`; the per‑uplink `clist` link found/created on `who->from` then `flags++`. `remove_user_from_channel` — a leaving `CHFL_CHANOP` arms `chptr->reop = timeofday + LDELAYCHASETIMELIMIT + myrand()%300` (the L1 fixture uses non‑chanop members so this non‑deterministic `myrand()` path is **not** exercised — it would diverge Rust's live `myrand` from the oracle's `cref_myrand`); `if (tmp2 && !--tmp2->flags)` predecrement drops the clist link at 0; `--chptr->users <= 0` → the `is_chan`/`is_chanmem`/`is_hchan`/`is_hchanmem` swap + `free_channel`. `change_chan_flag` — mirrors the flag into both the member link and the client link, `~lp->flags & MODE_FLAGS` = `(!lpflags) & MODE_FLAGS` (unary‑not binds tighter, same as C). **`format!` per user request — N/A, documented in‑module:** the cluster builds no reply string (pure list/counter mutation).
p5.md:166:  - **Verification.** L1 `channel_membership_diff` zero‑diff vs `cref_remove_user_from_channel`: a 2‑member channel with both members linked into `chptr->members` + each `who->user->channel` + a per‑uplink `clist` entry; removing one member asserts identical surviving `members`/`clist`/`user->channel` chains, `chptr->users`, `joined--`, and the `is_userc`/`is_chanusers` deltas — deliberately keeping `users >= 1` so the last‑user `free_channel` teardown (needs the channel hash + global) is **not** L1‑reached (L2/review‑covered). **Inverse/round‑trip invariant** (`add_remove_readd_roundtrip_is_clean`, the skill's required inverse test): drives the now‑Rust `add_user_to_channel` directly (a C static → no `cref_` oracle) to add two members, remove one and assert it is *actually gone* from `members`/`clist`/`user->channel` with the counters decremented, then re‑add and assert no duplicate node + the `is_userc`/`is_chanusers` counters return **exactly** to the two‑member values (a botched removal passes the positive‑only differential but leaks/double‑counts here). `change_chan_flag` is a C static → no `cref_` oracle → covered by L2. L2 (all byte‑identical ref‑C == Rust, 0 new canonicalizer masks): `golden_channel_part` — (1) **PART** with a *second member* (bob) staying on #chan to observe alice's relayed PART, then a follow‑up NAMES + WHO confirming alice is gone from both membership lists (`remove_user_from_channel`); (2) **MODE +o → NAMES** showing `@bob` (`change_chan_flag` set the flag, `is_chan_op` reads it). `golden_channel` (JOIN) still covers `add_user_to_channel`. **S2S `golden_s2s_membership`** — a remote NJOIN (`m_njoin` C → Rust `add_user_to_channel`; the server‑side `get_channel(&me, CREATE)` creates the federated `#chan` a split lone link won't let a local client create) then a local observer JOINs and sees `@bob`, then a remote PART (`m_part` C → Rust `remove_user_from_channel`) the observer sees + a NAMES no longer listing bob. `cargo build` 0 warnings; full `cargo test` green (41 test binaries incl. the new `channel_membership_diff` L1 + `golden_channel_part`/`golden_s2s_membership` L2). **Split‑mode finding:** in `boot_s2s` a single configured uplink keeps the server permanently split (`IsSplit()`), so a local client **cannot create** a federated `#` channel (m_join:2591 `ERR_UNAVAILRESOURCE`) — but **can JOIN an existing one** (the split guard is inside `if(!chptr)`); the NJOIN server path creates it. **Remaining channel.c (C):** the mode cluster (`m_mode`/`set_mode`/`add_modeid`/`del_modeid`/`send_mode_list`/`make_bei`/`free_bei`/`send_channel_*`), join/access (`can_join`/`get_channel`/`free_channel`/`reop_channel`), part/kick/topic, and display (`names_channel`/`find_chasing`/`check_string`/`m_names`/`m_list`) clusters stay in `channel_link.o`.
p5.md:172:  - **Faithfulness notes:** the `strtoken(&p, parv[1], ",")` per‑target loop with `parv[1]=NULL` advance; the `find_channel` miss does `return penalty` (**not** `continue`); `IsChannelName` reject → 442, `!UseModes` → 477, `!IsMember` → 442, `check_channelmask` → continue; query (`topic==NULL`) → empty topic → 331, else 332 + (if `topic_t > 0`) 333 with `IsAnonymous(chptr) ? "anonymous!anonymous@anonymous." : topic_nuh`; set (`parc > 2`) only if `!(mode & MODE_TOPICLIMIT) || is_chan_op` → strncpyzt topic, build topic_nuh, `topic_t = timeofday`, propagate (`sendto_match_servs` UID‑sourced + `sendto_channel_butserv` name‑sourced), `penalty += 2`; else → 482.
p5.md:173:  - **L2 is the gate (no clean L1 data op — m_topic sends wire output via a live channel).** L2 `golden_channel_topic` 2/2 byte‑identical (`-p standalone` so `#chan` can be created): **lifecycle** (query empty → 331; set → echo; query → 332 + 333; **change** → re‑query 332 reflects the *new* value — the round‑trip/inverse invariant a botched set/strncpyzt would break) + **rejects** (442 non‑member, 403 no‑such‑channel, 482 non‑op on +t). **S2S `golden_s2s_topic`** 1/1 byte‑identical (the user‑requested server path): a remote chanop bob (NJOIN'd onto a federated `#chan`) sets the topic → `m_topic` (cptr = peer server, sptr = bob) relays `:bob TOPIC #chan :…` to local member alice via `sendto_channel_butserv` and her query returns 333 carrying **bob's `topic_nuh`** (the remote‑user fields only an S2S link produces); then local alice sets the topic → `sendto_match_servs` propagates `:000AAAAAA TOPIC #chan :…` (UID‑sourced) to the peer. **New canonicalizer mask:** 333 RPL_TOPIC_WHO_TIME's trailing `topic_t` (= `timeofday`) is wall‑clock volatile between the separately‑booted ref‑C/Rust → masked to `<T>`. `m_topic` defined `T` from ircd‑common in the Rust `ircd`; `cargo build` 0 warnings. **Remaining channel.c (C):** the mode cluster (`m_mode`/`set_mode`/`add_modeid`/`del_modeid`/`send_mode_list`/`make_bei`/`free_bei`/`send_channel_*`), join/access (`m_join`/`m_njoin`/`can_join`/`get_channel`/`free_channel`/`reop_channel`), part/kick (`m_part`/`m_kick`/`find_chasing`), and display (`m_names`/`m_list`/`names_channel`/`check_string`) clusters stay in `channel_link.o`.
p5.md:177:  - **The `sendto_one` return type.** `m_list` is the first ported caller that needs `sendto_one`'s return value (`rlen += sendto_one(…)` — the remote‑LIST reply‑length throttle vs `CHREPLLEN`). The ircd‑common module‑local extern decls had declared it `-> ()`; since `clashing_extern_declarations` is crate‑wide, **all five** decls (channel/s_user/s_misc/s_service/s_debug) were unified to the true ABI `-> c_int` (the sibling callers ignore the result — a `c_int` isn't `#[must_use]`, so no warning). The cross‑crate bindings decl already returned `c_int`.
p5.md:180:  - **Faithfulness notes:** `parc > 2 && hunt_server(…, 2, …) != 0` → `return 10` (the forwarding short‑circuit; `HUNTED_ISME`=0 falls through, `PASS`=1/`NOSUCH`=‑1 forward). Bare arg (`BadPtr(parv[1])`): `!sptr->user` → RPL_LISTEND + `return 2`; else opening notice, then loop 1 over `sptr->user->channel` listing only `Secret||Hidden` channels, then loop 2 over the global `channel` list skipping `!users || Secret || Hidden` — each with the `DBufLength > maxsendq` → ERR_TOOMANYMATCHES throttle (loop 1's `goto end_of_list` modelled as `break 'lists`, loop 2's `break` falls through), then the trailing notice. Named arg (`else`): `canonize` then per‑token `strtoken(&p, parv[1], ",")` with `parv[1]=NULL` advance; `find_channel && ShowChannel && sptr->user` → RPL_LIST; `*name=='!'` → `hash_find_channels(name+1, …)` mask loop with the `scr = SecretChannel && !IsMember` users‑`-1`/topic‑`""` masking; both with the `!MyConnect && rlen > CHREPLLEN` break. Tail: `!MyConnect && rlen > CHREPLLEN` → ERR_TOOMANYMATCHES; always RPL_LISTEND; `return 2`.
p5.md:186:  - **The shared `static buf[BUFSIZE]` → a module‑private `static mut BUF`.** channel.c's `buf` is shared across the TU, but `m_part` uses it as **pure scratch**: `*buf = '\0'` at entry, `strcat`‑accumulates the comma‑joined non‑local channel names, and flushes (`sendto_serv_butone` + `*buf = '\0'`) within the same call — no state crosses calls or is read by another function. So a private `static mut BUF: [c_char; BUFSIZE]` in the Rust module is faithful (not ABI → no `#[no_mangle]`; accessed via `addr_of_mut!`). The `size = BUFSIZE - strlen(parv[0]) - 10` C size_t arithmetic is mirrored with `wrapping_sub` and the `strlen(buf)+strlen(name)+1 > size` test compares `size as usize` (the exact C int→size_t promotion).
p5.md:188:  - **Faithfulness notes:** `comment = BadPtr(parv[2]) ? "" : parv[2]` reads `parv[2]` directly (the parser NULL‑pads parv past parc, so `parc` is unused — `_parc`, matching C); `strlen(comment) > TOPICLEN` → `comment[TOPICLEN] = '\0'`. Per‑token loop (`strtoken(&p, parv[1], ",")`, `parv[1]=NULL` advance): `get_channel` miss → `MyPerson(sptr)`‑gated 403 + continue; `check_channelmask != 0` → continue; `!IsMember` → 442 + continue. Then the broadcast split: a non‑local (`*name != '&'`) channel whose name has no `:` mask and isn't `!`‑prefixed accumulates into `buf` (flush‑on‑overflow); otherwise `sendto_match_servs` relays it immediately with the per‑channel name; always `sendto_channel_butserv` echoes `:nick PART #chan :comment` to members and `remove_user_from_channel` unlinks the parter. Tail: a non‑empty `buf` gets one final `sendto_serv_butone`. Returns 4.
p5.md:192:  - **`find_chasing` is the cluster's shared static — the P5u/P5x extern‑switch case.** `m_kick` is its caller, but `find_chasing` is also called by the still‑C `set_mode`/`m_mode` (channel.c:~1301), so the C body is guarded out under `#ifndef PORT_CHANNEL_P5` and the `#else` branch adds an `extern aClient *find_chasing(…)` forward decl (it had none — it was defined before both use sites) so the C remnant resolves to the Rust `#[no_mangle]` def at link (linkage‑only, faithful). `check_string` — the other display‑cluster static — is **not** touched: `m_kick` doesn't call it (only `make_bei`/`m_mode` do). RED confirmed via the two undefined symbols `find_chasing`+`m_kick` (every other callee already Rust or a C extern).
p5.md:194:  - **Faithfulness notes.** `nbuf[BUFSIZE+1]` is a **local auto** array in C (not a file‑static) → a Rust stack `[c_char; BUFSIZE+1]`; no shared `buf`/`modebuf` touched. The C size_t arithmetic is mirrored exactly: `maxlen = BUFSIZE - MAX(strlen(sender),strlen(name)) - strlen(comment) - 10` via `wrapping_sub … as c_int`, and `clen = maxlen - strlen(name) - 1` via `(maxlen as usize).wrapping_sub(…)` (the C int→size_t promotion), with the flush test comparing `… >= clen as usize`. The `!(IsServer(cptr) && (who=find_uid(...))) && !(who=find_chasing(...))` short‑circuit assignment is preserved branch‑for‑branch (find_uid only attempted for a server `cptr`, find_chasing only when that misses). `sender` = SID (server) / UID (person) / name; the relay echoes `sptr->name`/`who->name` to members but the comma‑batched `sendto_match_servs_v(SV_UID,…)` carries UIDs (`who->user->uid`). Returns `penalty`.
p5.md:198:  - **`names_channel` is the cluster's shared static — the P5u/P5x extern‑switch case.** `m_names` is one caller, but `names_channel` is **also called by the still‑C `m_join`** (channel.c:2673, the JOIN echo). So its `static` forward decl (channel.c:105) is switched to `extern` under `#ifndef PORT_CHANNEL_P5`, and its body is guarded out, so the C `m_join` remnant resolves to the Rust `#[no_mangle]` def at link (linkage‑only, faithful). `m_njoin` does **not** call it (verified — the only other channel.c caller besides m_names is m_join). `m_names` is a plain `#ifndef` guard (no extern switch — its decl is imported by parse.rs from `ircd_sys::bindings`, resolving to Rust once `channel_link.o` drops the C def). RED confirmed via the two undefined symbols `names_channel`+`m_names`.
p5.md:200:  - **Shared `buf` — the keystone faithfulness point.** In C, `m_names` and `names_channel` both use the file‑static `char buf[BUFSIZE]` (channel.c:115). They are not reentrant: `names_channel` fills `buf` and flushes it (`sendto_one`) **within each call**, and `m_names` only writes `buf` itself (the third `strcpy(pbuf,"* * :")` remaining‑users section) **after** all its `names_channel` calls — so reusing the existing module `BUF` (introduced for P5aa `m_part`) is faithful, exactly as C shares one `buf`. **Documented residual:** the still‑C `m_join` keeps its own C `buf`; in its `!`‑safe‑channel autocreate branch it does `sprintf(buf,…); name=buf;` and re‑reads `name` after the `names_channel` call (which, in ref‑C, clobbers `buf`). With `names_channel` now writing the *Rust* `BUF`, C `m_join`'s `name` survives intact → the Rust path is in fact *more* correct in that one buggy branch. This affects **only** `!`‑autocreate NJOIN bytes (no golden scenario drives `!` channels; all tests use `#`/`&`/`+` where `name` is the parv token, never `buf`) and disappears when `m_join` itself ports and `buf` becomes Rust‑owned. Noted, not tested.
p5.md:201:  - **Faithfulness notes.** `names_channel`'s `cptr` param is unused in C (only `sptr`/`to`/`chptr`/`sendeon` are read) → `_cptr`. Pointer arithmetic mirrored raw: `*pbuf = ch; pbuf = pbuf.add(1)` for the `@`/`+`/`=`/`*` prefix bytes; the `353` member list assembled with `copy_nonoverlapping` (arbitrary client nick bytes — a `String` would corrupt non‑ASCII); the `maxlen = BUFSIZE - 1 - strlen(ME) - 5 - strlen(to) - 1 - pxlen - 2` budget + the `(pbuf - buf) + nlen >= maxlen` flush‑and‑restart‑after‑prefix exactly as C. The server‑member skip (`strchr(name,'.')`) precedes the `IsInvisible` skip, matching the C order. m_names: the `parc>2` `hunt_server(":%s NAMES %s %s",2,…)` forward short‑circuit → `MAXPENALTY`; the named‑arg branch's `sent` throttle return (`sent<2 ? 2 : (sent*MAXCHANNELSPERUSER)/MAXPENALTY`); the all‑channels branch's three sections (secret‑on‑user, public via `channel`/`nextch`, remaining users via `client`/`next`). The dead `sent=1`/`sent=0` writes in the third section (never read before `return MAXPENALTY`) are omitted — observationally identical (local, unread).
