  - **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).
- **2026‑06‑02 — P5f (s_user.c message‑routing cluster) merged.** `m_message` (the shared PRIVMSG/NOTICE delivery core, s_user.c:1457) + its two `#[no_mangle]` wrappers `m_private`/`m_notice` ported into `ircd-common/src/s_user.rs`, extending the existing `s_user_link.o` partial port (the `PORT_USER_P5` guard now spans m_message **through** the WHO/WHOIS cluster as one contiguous region — the redundant inner `#ifndef` was removed). **Pure handler cluster** (both wrappers are `msgtab` rows: PRIVMSG→`m_private` col 1, NOTICE→`m_notice`): no parse.rs edit (the decls parse.rs imports from `ircd_sys::bindings` resolve to the Rust defs once `s_user.o`→`s_user_link.o` drops them). RED confirmed via undefined `m_private`/`m_notice` (`m_message` is `static` → inlined into both, never a link symbol).
  - **First multi‑client L2.** The user required `#`/`+`/`&` channel relay coverage, which needs ≥2 simultaneous clients (`sendto_channel_butone` delivers to the *other* members, never the sender). Added to `ircd-golden`: a persistent **`Client`** struct (`connect`/`register`/`send`/`read_until`/`drain`) and **`boot_standalone`** (`-p standalone`). New `golden_s_user_message` (7 scenarios): PRIVMSG/NOTICE self‑echo, PRIVMSG no‑such‑nick (401), and alice→bob relay on `#chan`/`+chan`/`&chan` (PRIVMSG) + `#chan` (NOTICE). All byte‑identical vs reference‑C.
  - **Keystone finding — a lone server is *permanently* split‑mode, which blocks `#`‑channel creation.** The fixture M‑line's split thresholds are **floored to SPLIT_SERVERS(10)/SPLIT_USERS(5000)** (s_conf.c:1950), so `check_split()` never leaves split (needs 10 eob‑servers + 5000 users). During split, channel.c:2537 (`IsSplit() && UseModes(name) && !(&||!)`) rejects creating global `#` channels with ERR_UNAVAILRESOURCE — but allows `+` (not `UseModes`) and `&` (excluded). So `#` golden tests must boot **`-p standalone`** (`iconf.split = -1` → `IsSplit()` false), which lets all three prefixes be created uniformly. (This is exactly why `+`/`&` passed but `#` failed on the first run.)
  - **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.)
  - **`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.
  - **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.
  - **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).
- **2026‑06‑02 — P5g (s_user.c AWAY cluster) merged.** `m_away` (the AWAY command handler, s_user.c:2702) + `send_away` (the RPL_AWAY emitter, s_user.c:3547) ported into `ircd-common/src/s_user.rs`, extending the existing `s_user_link.o` partial port (two new `#ifndef PORT_USER_P5` guard regions; the cref oracle keeps the full unguarded `s_user.o`). **Pure handler cluster** (AWAY→`m_away` is a `msgtab` row) — L2 golden is the gate, RED confirmed via undefined `m_away`/`send_away`; no parse.rs edit. `send_away` was previously imported from `ircd_sys::bindings` and called by the already‑ported `m_message`/`m_whois`; **removing it from the import list and defining it locally makes those two call sites resolve to Rust** (it's still `#[no_mangle] pub extern "C"` since it's an `s_user_ext.h` symbol). Plan: `docs/superpowers/plans/2026-06-02-p5g-s_user-away.md`.
  - **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).
  - **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).
  - **Types/`format!`:** `istat.is_away`/`is_awaymem` are `u_long` (cast `(strlen+1)`/`len` to `u_long`); `MyMalloc`/`MyRealloc` (P2 Rust ports) take `usize` and return `*mut c_char` directly (no cast); `MyFree`→libc `free`; `(*user).uid` is `[c_char;10]` → passed via `.as_mut_ptr()`; `TOPICLEN`=255. `format!` is **N/A** — both fns hand every reply to a C variadic sender (`sendto_one`/`sendto_serv_butone`) as a format string + raw pointer/C‑string args (the sender formats per‑recipient); the only literals are the `c"…"` format strings (documented in‑module, matching the P5f note). No integer conversions through the senders → the P5c `%lu`‑width hazard doesn't apply.
  - **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).
- **2026‑06‑02 — P5h (s_user.c UMODE cluster) merged.** `m_umode` (s_user.c:3164), `send_umode` (:3358), `send_umode_out` (:3409) + the `user_modes[]` table (:38) ported into `ircd-common/src/s_user.rs`, extending the existing `s_user_link.o` partial port (one new `#ifndef PORT_USER_P5` region spans the three fns; the `user_modes[]` static is guarded separately since only this cluster uses it). **Handler cluster** (`UMODE`→`m_umode` is a msgtab row; `MODE <nick>` routes through still‑C `m_mode` (channel.c) → Rust `m_umode` once the target isn't a channel) — L2 golden is the gate. RED confirmed via undefined `m_umode`/`send_umode`/`send_umode_out`; no parse.rs edit. Plan: `docs/superpowers/plans/2026-06-02-p5h-s_user-umode.md`.
  - **`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).
  - **Config (verified `cbuild/config.h`):** `USE_SERVICES` **off** → m_umode's three `check_services_butone` blocks (away‑clear, oper‑±o, umode propagation) and send_umode_out's trailing block are not compiled/not ported. The active `MODE_*` values for s_user are `MODE_NULL=0`/`MODE_ADD=0x40000000`/`MODE_DEL=0x20000000` (struct_def.h:876‑878; the 750‑758 block is commented out) — bindgen agrees. `user_modes[]` resolves to `{(OPER,'o'),(LOCOP,'O'),(INVISIBLE,'i'),(WALLOP,'w'),(RESTRICT,'r'),(AWAY,'a')}`; `SEND_UMODES=61`, `ALL_UMODES=63`.
  - **`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.
  - **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]`.
  - **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.
- **2026‑06‑02 — P5i (s_user.c PING/PONG cluster) merged.** `m_ping` (s_user.c:2781) + `m_pong` (s_user.c:2818) ported into `ircd-common/src/s_user.rs`, extending the existing `s_user_link.o` partial port (one new `#ifndef PORT_USER_P5` region spans both; the cref oracle keeps the full unguarded `s_user.o`). **Pure handler cluster** (PING→`m_ping`, PONG→`m_pong` are both msgtab rows, col 1) — L2 golden is the gate. RED confirmed via undefined `m_ping`/`m_pong`; no parse.rs edit. Plan: `docs/superpowers/plans/2026-06-02-p5i-s_user-ping.md`.
  - **Cleanest cluster yet — pure routing, no statics.** Both handlers call only external lookup/send fns (`find_client`/`find_server`/`find_target`/`match`/`mycmp`/`sendto_one`); **no file‑statics, no allocators, no list linkage**, so the shared C remnant (`buf`/`buf2`/`user_modes`) is untouched and there are **no socket‑free data ops** → **no L1** (like P5e/P5g). New bindings imports: `find_target`/`FLAGS_PINGSENT`/`ERR_NOSUCHSERVER`/`ERR_NOORIGIN`; reused the existing `is_me` helper (s_user.rs:905).
  - **`format!` per user request — N/A, documented.** Both fns hand every reply to a C variadic sender (`sendto_one`) as a format string + raw pointer/C‑string args (no intermediate string to build); the only literals are the `c"…"` format strings / `replies[]` numerics. No integer conversions reach the senders → the P5c `%lu`‑width rule is N/A.
  - **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)`.
  - **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.
- **2026‑06‑02 — P5j (s_user.c OPER cluster) merged.** `m_oper` (s_user.c:2866) ported into `ircd-common/src/s_user.rs`, extending the existing `s_user_link.o` partial port (one new `#ifndef PORT_USER_P5` region around `m_oper`; the cref oracle keeps the full unguarded `s_user.o`). **Handler cluster** (OPER→`m_oper` is a msgtab row, client column) — L2 golden is the gate. RED confirmed via undefined `m_oper` (no other symbol; it has no file‑statics of its own under this config). No parse.rs edit. **Reuses the already‑Rust `send_umode_out`** (P5h) for the grant echo. Plan: `docs/superpowers/plans/2026-06-02-p5j-s_user-oper.md`.
  - **Config that shaped the port (verified `cbuild/config.h`):** `CRYPT_OPER_PASSWORD` **off** (#undef:311 → `encr = password`, no crypt), `FNAME_OPERLOG`/`USE_SYSLOG`/`FAILED_OPERLOG` (#undef:300)/`UNIXPORT`/`USE_SERVICES` **off**, `NO_OPER_REMOTE` **off** (#undef:555 → the `#ifndef` arm: `if (aconf->flags & ACL_LOCOP) SetLocOp else SetOper`). ⇒ the trailing `logstring`/syslog/logfile block is an **empty block → omitted entirely**; the resolved body is ~40 lines. New helpers added: `set_oper`/`set_locop` (flags |= FLAGS_OPER/LOCOP; status = STAT_OPER), mirroring the existing `clear_oper`/`clear_locop`. Via bindings: `find_Oline`/`attach_conf`/`detach_conf`/`aConfItem`/`CONF_OPS`/`ACL_LOCOP`/`RPL_YOUREOPER`/`ERR_NOOPERHOST`/`ERR_PASSWDMISMATCH`/`ServerChannels_SCH_NOTICE`; `strcmp` (StrEq) + `sendto_flag` added to the local extern block.
  - **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).
  - **`format!` per user request — N/A, documented.** Every reply is handed to a C variadic sender (`sendto_one`/`sendto_flag`) as a format string + raw pointer/`%d`/`%c` args (the sender formats per‑recipient); there is no intermediate string to build under this config (the only `sprintf(buf,…)` lives in the compiled‑out FNAME_OPERLOG block). Documented in‑module, matching the P5f/g/i notes.
  - **L2 harness gained `boot_conf(binary, args, conf)`** (refactor of `boot_args`, which now calls it with `minimal.conf`) so a test can use a dedicated config; new **`oper.conf`** = minimal.conf + a full‑operator O‑line `O|*@*|secret|alice|0|10|` (uppercase O → no ACL_LOCOP → SetOper; class 10 exists), isolated from every other golden test.
  - **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.
- **2026‑06‑02 — P5k (s_user.c QUIT/POST exit cluster) merged.** `m_quit` (s_user.c:2479) + `m_post` (s_user.c:2467, the http‑proxy‑abuse alias) ported into `ircd-common/src/s_user.rs`, extending the existing `s_user_link.o` partial port (one new `#ifndef PORT_USER_P5` region around s_user.c:2466‑2498; the cref oracle keeps the full unguarded `s_user.o`). **Pure handler cluster** (QUIT→`m_quit`, POST→`m_post` are both msgtab rows) — L2 golden is the gate. RED confirmed via undefined `m_quit`/`m_post`; no parse.rs edit. Both handlers terminate in the **already‑Rust `exit_client`** (P5c). Plan: `docs/superpowers/plans/2026-06-02-p5k-s_user-quit.md`.
  - **Cleanest cluster after PING/PONG — self‑contained, one tiny static.** `m_quit`'s only state is its function‑local `static char comment[TOPICLEN+1]`, reproduced as the private module static `QUIT_COMMENT` (not ABI); `m_post` has no state. They share **no** file‑statics with the C remnant (`buf`/`buf2`/`user_modes` untouched). `m_post` tail‑calls `m_quit` after a `SCH_LOCAL` server‑notice. New bindings imports: `exit_client`/`get_client_host`/`ServerChannels_SCH_LOCAL`; reused `is_server`/`my_connect` helpers.
  - **Columns `[server, client, oper, service, unreg]` decide reachability:** QUIT = `[m_quit ×5]` → a **registered** client (col 1) hits `m_quit`; POST = `[m_nop,m_nop,m_nop,m_nop,m_post]` → only col 4 (**unregistered**) hits `m_post`. So the two L2 scenarios use different client states (registered alice QUIT; raw unregistered connection POST). No clean socket‑free data op (both end in `exit_client`, which closes the connection) → **no L1**, like P5e/g/i.
  - **`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.
  - **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] : ""`).
  - **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).
- **2026‑06‑03 — P5l (s_user.c PASS cluster) merged.** `m_pass` (s_user.c:3043) ported into `ircd-common/src/s_user.rs`, extending the existing `s_user_link.o` partial port (one new `#ifndef PORT_USER_P5` region around s_user.c:3043‑3076; the cref oracle keeps the full unguarded `s_user.o`). **Handler cluster** (`PASS`→`m_pass`, msgtab row `[m_nop, m_reg, m_reg, m_nop, m_pass]` → only the **unregistered** column hits `m_pass`, i.e. a registration‑time command on a raw connection). RED confirmed via undefined `m_pass` (the single symbol the new guard drops); no parse.rs edit. Plan: `docs/superpowers/plans/2026-06-02-p5l-s_user-pass.md`.
  - **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'`).
  - **`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_*`).
  - **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.
  - **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).
- **2026‑06‑03 — P5m (s_user.c canonize utility) merged.** `canonize` (s_user.c:283 — the O(n²) comma‑list deduplicator) ported into `ircd-common/src/s_user.rs`, extending the existing `s_user_link.o` partial port (one new `#ifndef PORT_USER_P5` guard region around s_user.c:277‑319; the cref oracle keeps the full unguarded `s_user.o`). **Utility / callee fragment** (no `msgtab` row) — it is called *from* `m_mode`/`m_part`/`m_topic` (channel.c), `m_whowas` (whowas.c), and the already‑Rust WHO cluster (s_user.rs:521/797/960). RED confirmed via the three Rust call sites failing to find `canonize` once it was removed from the `bindings` import; **defining it locally resolves those Rust callers + all the still‑C callers to the Rust def at link** (the `send_away`/`send_umode` pattern from P5g/P5h). Plan: `docs/superpowers/plans/2026-06-03-p5m-s_user-canonize.md`.
  - **Self‑contained — its own static, no shared state.** `canonize`'s only state is its function‑local `static char cbuf[BUFSIZ]` (BUFSIZ = 8192 on this glibc, verified via `cpp -dM`), reproduced as the module‑private `CANON_BUF: [c_char; 8192]` (not ABI). It shares **no** file‑statics with the C remnant (`buf`/`buf2`/`user_modes` untouched); it calls only `strtoken`+`mycmp` (P1 Rust ports) + libc `strcpy`.
  - **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.
  - **`format!` per user request — N/A, documented in‑module.** `canonize` copies arbitrary client‑supplied token bytes (channel names / nicks) verbatim via `strcpy` and mutates the buffers in place — there is nothing to format, and a UTF‑8 `String`/`format!` could neither reproduce `strcpy` of non‑ASCII bytes nor the in‑place `strtoken`/comma‑restore surgery. `format!` stays reserved for the ASCII/numeric reply cases (cf. `m_umode` query reply, `who_one` numeric fields).
  - **Verification:** L1 `s_user_canonize_diff` zero‑diff vs `cref_canonize` over 14 inputs (empty, single, adjacent/interior/casefold dups, channel names, trailing/leading/empty tokens, a longer list exercising the `cp += (p‑s)` advance). L2 `golden_s_user_canonize` 1/1 byte‑identical: a registered client's `WHOWAS alice,alice` → `m_whowas` canonizes to `alice` → **exactly one** `406 ERR_WASNOSUCHNICK` + `369 RPL_ENDOFWHOWAS` (the dedup‑count assertion proves the branch fired live in both binaries). `canonize` defined `T` from ircd‑common in the Rust `ircd`; full `cargo test` green (all `*_diff` L1 + layout + all 15 golden suites); 0 warnings. No new canonicalizer masks (no time tokens). **Deferred:** the other s_user.c utilities (`next_client`/`hunt_server`/`is_allowed`) + the registration/nick cluster remain in `s_user_link.o` (C) for later P5 sub‑phases.
- **2026‑06‑03 — P5n (s_user.c do_nick_name utility) merged.** `do_nick_name` (s_user.c:245 — the nickname validator/canonicalizer) ported into `ircd-common/src/s_user.rs`, extending the existing `s_user_link.o` partial port (one new `#ifndef PORT_USER_P5` guard region around s_user.c:245‑274; the cref oracle keeps the full unguarded `s_user.o`). **Utility / callee fragment** (no `msgtab` row) — it is called *from* still‑C `m_nick`/`m_user`/`register_user`; defining it `#[no_mangle]` resolves those C callers to the Rust def at link (the `canonize`/`send_away` pattern). RED confirmed via undefined `do_nick_name` once the guard dropped it. Plan: `docs/superpowers/plans/2026-06-03-p5n-s_user-do_nick_name.md`.
  - **Cleanest leaf yet — pure string surgery, no statics, no allocators.** Drops the nick (return 0) if it leads with `-`, leads with a digit in client context, or equals `"anonymous"` (case‑insensitive); else truncates in place at the first invalid char or the length cap and returns the final length. Shares **no** file‑statics with the C remnant (`buf`/`buf2`/`user_modes` untouched). Calls only libc `strcasecmp` + the P1 Rust `char_atribs` table.
  - **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).
  - **`format!` per user request — N/A, documented in‑module.** `do_nick_name` mutates the caller's nick buffer in place (`*ch = '\0'`) — there is nothing to format and a UTF‑8 `String` could not reproduce the in‑place byte truncation. `format!` stays reserved for the ASCII/numeric reply cases.
  - **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.
- **2026‑06‑03 — P5o (s_user.c is_allowed ACL gate) merged.** `is_allowed` (s_user.c:3528 — the per‑client ACL gate: given an O‑line/service privilege bit return 1/0) ported into `ircd-common/src/s_user.rs`, extending the existing `s_user_link.o` partial port (one new `#ifndef PORT_USER_P5` guard region around s_user.c:3523‑3564; the cref oracle keeps the full unguarded `s_user.o`). **Utility / callee fragment** (no `msgtab` row). RED confirmed via undefined `is_allowed` (the single symbol the new guard drops). Plan: `docs/superpowers/plans/2026-06-03-p5o-s_user-is_allowed.md`.
  - **Cleanest L1‑differentiable callee — a pure function of its argument.** `is_allowed` reads **only `cptr`** (`fd`/`status`/`service->wants`/the `confs` Link chain) and touches **no globals and no hash tables**, so the same constructed client is handed to both `c_is_allowed` (→ Rust) and `cref_is_allowed` (→ oracle) and equal returns are asserted — unlike `next_client`/`hunt_server` (the other remaining s_user utilities), whose lookups need a populated hash (separate‑globals problem deferred by P4 to S2S/L2). It shares **no** file‑statics with the C remnant (`buf`/`buf2`/`user_modes` untouched).
  - **Seam — resolves the still‑C *and* the ported‑Rust callers.** `is_allowed` is called from ported `parse.rs` (penalty checks at parse.rs:698/779) *and* still‑C `m_kill`/`s_serv.c`/`s_conf.c`/`channel.c`/`res.c`/`hash.c`/`s_bsd.c`. Removing it from parse.rs's `ircd_sys::bindings` import and `use crate::s_user::is_allowed;` makes the Rust caller resolve to the new local def; the C callers resolve at link once `s_user.o`→`s_user_link.o` drops the symbol (the `canonize`/`send_away` pattern). `IsService`/`IsServer`/`MyConnect` reproduced via the existing `is_service_cl`/`is_server_cl`/`my_connect` helpers; `CONF_OPERATOR`/`ACL_TKLINE`/`ACL_KLINE`/`SERVICE_WANT_TKLINE`/`SERVICE_WANT_KLINE` imported from bindings.
  - **`format!` per user request — N/A, documented in‑module.** `is_allowed` emits no wire output (returns 1/0); there is nothing to format.
  - **L2 reachability — only oper KILL reaches it.** The KILL msgtab row is `[m_kill, m_nopriv, m_kill, m_nop, m_unreg]`, so a plain registered client (col 1) hits `m_nopriv` directly (parse sends 481 itself, `is_allowed` never runs); only an **oper** (col 2) reaches still‑C `m_kill` → `is_allowed(sptr, ACL_KILL)`. The O‑line flags `is_allowed` walks select the branch: two oper boots with different O‑line grants (`O|*@*|secret|alice|0|10|<flags>|` — flags is field 7, after port+class) drive different numerics. (`OPER_KILL`/`OPER_KILL_REMOTE` defined → the `K` flag survives `oline_flags_parse`'s gating.)
  - **Verification:** L1 `s_user_is_allowed_diff` zero‑diff vs `cref_is_allowed` over 11 cases (server, non‑MyConnect, service wants‑TKLINE/KLINE/none, no‑conf, non‑oper‑conf, CONF_OPERATOR grant‑all/grant‑some/grant‑none, oper‑second‑in‑chain). L2 `golden_s_user_is_allowed` 2/2 byte‑identical: **grant** (`kill_grant.conf` `K` flag → `is_allowed`=1 → oper `KILL ghost` proceeds → `401 ERR_NOSUCHNICK`) and **deny** (`kill_deny.conf` `T` flag, no ACL_KILL → `is_allowed`=0 → `m_nopriv` → `481 Permission Denied`). `is_allowed` 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 canonicalizer masks (no time tokens). **Deferred:** `next_client`/`hunt_server` (hash‑dependent → S2S/L2) + the NICK/registration handler cluster stay in `s_user_link.o` (C).
- **2026‑06‑03 — P5p (s_user.c KILL handler) merged.** `m_kill` (s_user.c:2512 — the KILL command) ported into `ircd-common/src/s_user.rs`, extending the existing `s_user_link.o` partial port (one new `#ifndef PORT_USER_P5` region around s_user.c:2512‑2692; the cref oracle keeps the full unguarded `s_user.o`). **Handler cluster** (msgtab row `[m_kill, m_nopriv, m_kill, m_nop, m_unreg]` → reached by an **oper** (col 2) or a **server** (col 0); a plain client (col 1) hits `m_nopriv`). RED confirmed via undefined `m_kill` (the single symbol the guard drops); no parse.rs edit. Plan: `docs/superpowers/plans/2026-06-03-p5p-s_user-m_kill.md`.
  - **Every callee already resolved → clean extraction.** `m_kill` gates on the Rust `is_allowed` (P5o), resolves the victim via Rust `find_uid`/`find_client`/`get_history` (hash/whowas P2), and terminates in the Rust `exit_client` (P5c); `m_nopriv` is the Rust parse.rs stub (P4). Only the variadic senders (`sendto_one`/`sendto_flag`/`sendto_serv_v`/`sendto_serv_butone`/`sendto_prefix_one`) stay C. New bindings imports: `get_history`/`m_nopriv`/`sendto_serv_v` + consts `ACL_KILL`(6)/`ACL_KILLREMOTE`(4)/`EXITC_KILL`(75)/`SV_UID`(1)/`ServerChannels_SCH_ERROR`/`SCH_KILL`/`FLAGS_KILLED`/`ERR_CANTKILLSERVER`. Reuses the `is_an_oper`/`is_oper`/`is_server`/`is_me`/`is_service_cl`/`my_connect`/`is_client`/`bad_to` helpers.
  - **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.
  - **`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.
  - **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.
  - **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).
- **2026‑06‑03 — P5q (s_user.c register_user — the registration core) merged.** `register_user` (s_user.c:332‑846 — the central client‑registration routine: ident‑prefix username canonicalization, `check_client`/`find_kill` gating, STAT_CLIENT flip, user‑counter bumps, UID allocation, the welcome banner [001/002/003/004/005…/RPL_YOURID + LUSERS + MOTD], `send_umode`, and the leaf‑server UNICK feed) ported into `ircd-common/src/s_user.rs`, extending the existing `s_user_link.o` partial port (one new `#ifndef PORT_USER_P5` region around s_user.c:332‑846; the cref oracle keeps the full unguarded `s_user.o`). **Utility / callee** (no `msgtab` row) — called from still‑C `m_nick` (1213)/`m_user` (2461)/`m_unick` (1446); defining it `#[no_mangle]` resolves all three C callers to the Rust def at link (the `canonize`/`send_away`/`is_allowed` seam). RED confirmed via the sole undefined `register_user`; no parse.rs edit. Plan: `docs/superpowers/plans/2026-06-03-p5q-s_user-register_user.md`.
  - **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.
  - **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.
  - **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`).
  - **`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.
  - **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`.
  - **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`.
- **2026‑06‑03 — P5r (s_user.c m_user — the USER registration handler) merged.** `m_user` (s_user.c:2337‑2469) ported into `ircd-common/src/s_user.rs`, extending the existing `s_user_link.o` partial port (one new `#ifndef PORT_USER_P5` region around s_user.c:2337‑2470; the cref oracle keeps the full unguarded `s_user.o`). **Handler cluster** (msgtab row `USER`=`[m_nop, m_reg, m_reg, m_nop, m_user]` → only the **unregistered** column (4) reaches `m_user`; a registered client's USER hits the parse stub `m_reg`). RED confirmed via the sole undefined `m_user`; no parse.rs edit. Both wrappers it calls (`make_user` P2, `register_user` P5q, `mystrdup`/`inetntop` P1, `get_client_host`/`exit_client` P5c) already resolve to Rust. Plan: `docs/superpowers/plans/2026-06-03-p5r-s_user-m_user.md`.
  - **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.
  - **Config‑resolved scope (verified `cbuild/config.h`):** `DEFAULT_INVISIBLE` **off** (config.h:103 `#undef`) → no `SetInvisible`; `XLINE` **off** (config.h:460 `#undef`) → no `user2`/`user3`; `REALLEN`=50 (real‑name truncation live); `USERLEN`=10 (`strncpyzt(user->username,…,USERLEN+1)`); `UFLAGS`=`FLAGS_INVISIBLE|FLAGS_WALLOP|FLAGS_RESTRICT`=28; the function‑local `umodes_arr[]`=`{('i',INVISIBLE),('r',RESTRICT),('w',WALLOP)}`. `aConfItem.flags` is `c_long` → `PFLAG_SERVERONLY`/`PFLAG_TLS` cast `as c_long`; `DefInfo` is `*const c_char` → compared `as *mut c_char`.
  - **No shared‑static hazard.** `m_user` has **no** file‑statics of its own (`umodes_arr` function‑local; `ipbuf` a stack `[c_char; BUFSIZE]`) → the C remnant's shared `buf`/`buf2`/`user_modes` are untouched.
  - **`format!` per user request — N/A, documented in‑module.** There is no ASCII/numeric reply assembled in a buffer: the only string ops copy **arbitrary client‑supplied C‑string bytes** (username `@`‑truncation via `strchr`, real‑name `info` via `mystrdup`, `username` via `strncpyzt`) under byte‑precision truncation → direct libc copies, not `format!` (a UTF‑8 String would corrupt non‑ASCII bytes — the standing P5c/P5f/k rule); `ipbuf` is numeric ASCII from `inetntop` `strcpy`'d into `user->sip`; both wire emissions (the `SCH_LOCAL` server‑only notice via `sendto_flag`, the `462` reply via `sendto_one`) go straight to the C variadic senders.
  - **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)`.
  - **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`.
- **2026‑06‑03 — P5s (s_user.c NICK cluster) merged.** `m_nick` (s_user.c:855), `m_unick` (:1261), the file‑static `save_user` (:3470, the SAVE emitter), and `m_save` (:3506) ported into `ircd-common/src/s_user.rs`, extending the existing `s_user_link.o` partial port (two new `#ifndef PORT_USER_P5` regions: m_nick+m_unick at :850‑1450, save_user+m_save at :3462‑3530; the cref oracle keeps the full unguarded `s_user.o`). **Handler cluster** — `m_nick`/`m_unick`/`m_save` are msgtab rows; they form one connected component because all three call `save_user`. RED confirmed via undefined `m_nick`/`m_unick`/`m_save` (`save_user` is C `static` → inlined, never a link symbol). No parse.rs edit. **This leaves only `next_client`/`hunt_server` in `s_user_link.o`** (hash‑dependent → S2S/L2 per P4). Plan: `docs/superpowers/plans/2026-06-03-p5s-s_user-nick.md`.
  - **`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.
  - **Config that shaped the port (verified `cbuild/config.h`):** `USE_SERVICES` **off** → every `check_services_butone` block (m_nick nick‑change, save_user) omitted; `CLIENTS_CHANNEL` **off** → the `SCH_CLIENT` `%s … NICK %s` blocks in m_nick/save_user omitted; `DISABLE_NICK0_REGISTRATION`/`DISABLE_NICKCHANGE_WHEN_BANNED` **absent** → the `#else` (goto nickkilldone) / no‑op (no ERR_CANNOTSENDTOCHAN reject) arms taken. New consts: `SIDLEN=4`, `DELAYCHASETIMELIMIT=1800`, `MODE_QUIET=0x2000`, `FLUSH_BUFFER=-2`; new helpers `my_person`/`is_conf_serveronly`/`is_quiet`; `strncmp`/`strncasecmp` added to the local extern block; `sendto_common_channels` added as a variadic extern. New bindings imports: `add_client_to_list`/`add_history`/`bootopt`/`check_uid`/`del_from_client_hash_table`/`find_history`/`make_client`/`BOOT_PROT`/`ERR_ERRONEOUSNICKNAME`/`ERR_NICKCOLLISION`/`ERR_NICKNAMEINUSE`/`ERR_UNAVAILRESOURCE`/`RPL_SAVENICK`/`ServerChannels_SCH_SAVE`.
  - **`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.
  - **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).
  - **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.
- **2026‑06‑03 — P5t (channel.c leaf‑predicate foundation cluster) merged.** `is_chan_op` (channel.c:577), `has_voice` (:593), `find_channel` (:716), `clean_channelname` (:2062) ported to `ircd-common/src/channel.rs`. **First channel.c sub‑phase — establishes the `channel_link.o` partial‑port mechanism** (`‑DPORT_CHANNEL_P5` `#ifndef`‑guards the four C defs out of the link set; the cref oracle keeps the full unguarded `channel.o`). These are the only **truly‑leaf** channel.c exports (no file‑static, no shared `buf`/`modebuf`/`parabuf`/`uparabuf` use). Plan: `docs/superpowers/plans/2026-06-03-p5t-channel-leaf-predicates.md`.
  - **`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.
  - **Classification & seam:** `is_chan_op`/`has_voice` are callee predicates, `find_channel`/`clean_channelname` callee utilities (none a msgtab row). All four are already imported by still‑C channel.c callers *and* the already‑Rust `s_user.rs` (`clean_channelname`/`find_channel`) + `s_misc.rs` (`is_chan_op`); defining them `#[no_mangle]` resolves every caller (C and Rust) to the Rust def at link (the `canonize`/`send_away` pattern). No parse.rs edit. `find_user_link`/`hash_find_channel` are the P2 Rust ports; `my_connect`/`is_person`/`is_restricted` inline helpers mirror s_user.rs. **`can_send` deferred** to the mode‑manipulation cluster (it calls the file‑static `match_modeid`). **JAPANESE off** → `clean_channelname`'s comma‑`flag` block isn't compiled (`flag` always 0); `jp_*`/`get_channelmask` absent.
  - **`format!` per user request — N/A, documented in‑module.** None of the four builds an ASCII/numeric reply string: `clean_channelname` mutates the caller's buffer in place, `find_channel` returns a pointer, `is_chan_op`/`has_voice` return ints. `format!` stays reserved for the reply‑building clusters.
  - **Verification:** L1 `channel_leaf_diff` zero‑diff vs `cref_` (4 tests: is_chan_op over op/uniqop/voiced/non‑member/restricted‑on‑`#`‑vs‑`&`/remote + null‑chan; has_voice; find_channel empty‑name→arg + hash‑miss→NULL after `inithashtables`; clean_channelname over 10 strings comparing return + mutated bytes). L2 `golden_channel` 1/1 byte‑identical (registered alice `JOIN #chan` under `‑p standalone` → JOIN echo + `353` with `@alice` [is_chan_op marks the creator chanop] + `366`). Full `cargo test` green (0 failed); `cargo build` 0 warnings. No new canonicalizer masks. **Deferred:** `has_voice` has no JOIN/NAMES path under `‑s` (L1‑only); the rest of channel.c (mode/join/display clusters, 5 components over the shared mode buffers) stays C in `channel_link.o`.
- **2026‑06‑03 — P5 S2S golden coverage (the deferred server‑path harness) merged.** Every P5 cluster above deferred its `IsServer(cptr)`/server‑reachable paths to "an S2S scenario" because the standalone (`‑s`) client harness can never present a linked peer. This adds that peer. New `ircd-golden` primitives (committed first, then a 12‑agent `ultracode` workflow authored one test per cluster, each probing both binaries before writing): `boot_s2s` + a `Peer` fake‑server that completes the IRCnet 2.11 server handshake (`PASS linkpass 0211030000 IRC|aHmM` / `SERVER peer.test 1 001B …` → drains the burst through `EOB`) and then speaks the SID/UID‑prefixed server protocol; the `s2s.conf` fixture (M/A/P/Y/I **+ C/N/H lines** authorizing an inbound link from `127.0.0.1`, name `peer.test`, pass `linkpass`). **13 `golden_s2s_*` scenarios, all byte‑identical ref‑C == Rust after `canonicalize()`** (full golden suite 51/51, 0 warnings).
  - **Foundation findings (verified empirically, both binaries):** the handshake reply + burst is byte‑identical; **local UIDs are deterministic & sequential** (first local user = `000AAAAAA`; `curr_cid` starts at 0, **not** wall‑clock) so **no UID masking is needed**; SID format is 4 chars, first a digit (`sid_valid`, s_id.c) — peer SID `001B`, server SID `000A`; `‑p standalone` refuses all links ("Running in standalone mode") so S2S boots use plain `‑t ‑s` (server starts in split‑mode until the peer links, which `canonicalize()` already handles via the split‑mode NOTICE drop). No new canonicalizer masks were required by any scenario.
  - **`golden_s2s_link` (P5s `m_unick` + P5d `m_whois`):** peer links, a local client rides the burst as `:000A UNICK alice 000AAAAAA …`, then the peer introduces remote `bob` (`001BAAAAA`) via UNICK and the local client `WHOIS bob` → `311 … peer.test` / `312` / `318`. The foundation proof.
  - **`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.
  - **`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.
  - **`golden_s2s_umode` (P5h `send_umode_out`/`send_umode`):** alice (in the burst) sends `MODE alice +i` → client echo `:alice MODE alice :+i` (ALL_UMODES local branch) **and** link propagation `:000AAAAAA MODE alice :+i` (SEND_UMODES, source=uid/target=name). Order‑sensitive: alice must register before `Peer::link()`.
  - **`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.
  - **`golden_s2s_kill` (P5p `m_kill` `IsServer(cptr)` branch):** peer sends `:001B KILL 000AAAAAA :peer.test (spam)` → victim sees `:peer.test KILL alice :peer.test!peer.test (spam)` then `ERROR :Closing Link: alice[~alice@127.0.0.1] peer.test (Killed (peer.test (spam)))` (the `Killed (<killer>)` branch, not `Local Kill by`); socket close confirmed on both binaries.
  - **`golden_s2s_squit` (P5c `exit_client`→`exit_server`):** introduce remote `bob`, confirm live WHOIS, **drop the peer TCP socket** (netsplit) → after settle, `WHOIS bob` → `401 … :No such nick/channel` / `318` on both binaries (the recursive reap of dependents behind the dead link).
  - **`golden_s2s_nick` (P5s `m_nick` server branch):** peer introduces `bob` then sends `:001BAAAAA NICK :newbob`; WHOIS `newbob` → `311 … peer.test`, WHOIS `bob` → `401` (the `sendto_serv_butone(":%s NICK :%s")` propagation + del/add client‑hash retarget).
  - **`golden_s2s_save` (P5s `m_save`/`save_user`):** peer sends `:001B SAVE 000AAAAAA :savepath` → alice gets `:peer.test 043 alice 000AAAAAA :nickname collision, forcing nick change to your unique ID.` then `:alice!~alice@127.0.0.1 NICK :000AAAAAA` (renamed to her UID). Deterministic — UID stable, SAVE `path` a fixed literal.
  - **`golden_s2s_userhost` (P5e):** remote `bob` → `USERHOST bob` → `302 alice :bob=+~bob@remote.host` (`+` not away, no `*` not oper); `ISON alice bob` → `303 alice :alice bob ` (trailing space per the C loop).
  - **`golden_s2s_who` (P5d `who_one` classic‑352):** `WHO bob` → `:irc.test 352 alice * ~bob remote.host peer.test bob H :1 001B Bob Remote` (the server/hop/sid trailer that only differs for a remote target) then `315 … bob :End of WHO list.` (mask keyed on the literal `bob`, not `*`).
  - **`golden_s2s_register_feed` (P5q):** peer links and drains the burst **before any local client exists** (burst has no UNICK), then a fresh local `carol` registers → peer receives `:000A UNICK carol 000AAAAAA ~carol 127.0.0.1 127.0.0.1 + :carol` (the `register_user` leaf‑server feed loop, unreachable by the standalone harness).
  - **`golden_s2s_service` (P5a `m_service`/`m_squery`):** peer introduces a remote service via `:001B SERVICE chanserv@peer.test * 0 :Channel Service` (the `IsServer(cptr)` introduce branch; USE_SERVICES off so the client SERVICE path is rejected — only the common tail runs), then local `SQUERY chanserv@peer.test :hello there` routes out the link as `:alice SQUERY chanserv@peer.test :hello there` (remote service → not MyConnect → final routing branch).
  - **Verification:** full `cargo test -p ircd-golden` = 51 passed / 0 failed (the 13 S2S scenarios + all prior golden + L1/layout suites); `cargo build` 0 warnings. The S2S harness lives entirely in `ircd-golden/src/lib.rs` (`Peer`, `boot_s2s`) + `ircd-golden/fixtures/s2s.conf` — no change to any ported `ircd-common` source (these tests *characterize* the already‑merged ports, they don't re‑port). **Still S2S‑deferred (no test yet):** multi‑hop server‑burst diffing through a TS‑aware peer, channel NJOIN/MODE burst (channel.c still mostly C), and `m_ping`/`m_pong` server‑col paths (P5i) — left for the channel mode‑cluster + a future burst harness.
- **2026‑06‑03 — P5u (channel.c invite cluster) merged.** `m_invite` (channel.c:3260), the file‑statics `add_invite` (:2176) + `list_length` (:85), `check_channelmask` (:2100), and the exported `del_invite` (:2233) ported to `ircd-common/src/channel.rs`, extending the existing `channel_link.o` partial port (five new `#ifndef PORT_CHANNEL_P5` regions; the cref oracle keeps the full unguarded `channel.o`). **Second channel.c sub‑phase.** Plan: `docs/superpowers/plans/2026-06-03-p5u-channel-invite.md`.
  - **Cluster = a closed connected component over static‑function call edges.** Coupling in channel.c is *only* via static functions called across the C/Rust boundary (static *variables* like `buf`/`modebuf` used transiently can be duplicated — established P5 practice). Verified edges: `list_length`→only `add_invite`; `add_invite`→only `m_invite`; `del_invite` non‑static (no linkage issue); `check_channelmask`→**10 C callers** (m_join/m_part/m_kick/m_topic/m_njoin/set_mode/send_channel_modes/send_channel_members + m_invite; the JAPANESE site is `#if 0`). So the cluster is `{m_invite, add_invite, list_length, del_invite, check_channelmask}` — every static is called only by cluster members **except** `check_channelmask`, handled by the extern‑prototype switch below.
  - **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).
  - **Config (verified `cbuild/config.h`):** `JAPANESE` **off** → `m_invite`'s `#ifdef JAPANESE jp_valid` block not compiled/not ported; `get_channelmask(x)` is the macro `rindex(x, ':')` → in `check_channelmask` it is just libc `strrchr`, no static dragged in. `USE_SERVICES` off (no `check_services_*` in this cluster). `MAXCHANNELSPERUSER`=**21** (config.h:505) — bindgen drops the macro → hardcoded as a resolved literal in `add_invite`.
  - **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.
  - **`format!` per user request — N/A, documented in‑module.** `add_invite`'s `who` interpolates arbitrary client‑supplied C‑string bytes → byte‑builder, not `format!` (a UTF‑8 String would corrupt non‑ASCII bytes); every reply goes straight to a C variadic sender (`sendto_one`/`sendto_prefix_one`); no sub‑`long` int reaches a sender → the P5c `%lu`‑width rule is N/A.
  - **L1 vs L2 split — statics aren't L1‑differentiable.** `add_invite`/`list_length`/`check_channelmask` are C file‑`static`s → the `cref_` prefix‑rename keeps them **local**, so there is no `cref_*` oracle symbol to diff against (the first P5u attempt hit `undefined symbol: cref_check_channelmask` at link). Per the established practice they are covered by **L2** instead. `del_invite` (exported) is the lone L1 target: `channel_invite_diff` builds parallel `chptr->invites` (Link) + `cptr->user->invited` (invLink, with libc‑malloc'd `who`) chains, removes the middle element on the Rust port (`c_del_invite`) and the oracle (`cref_del_invite`), and asserts identical chain relink (marker sequence) + identical `is_invite`/`is_useri`/`is_banmem` deltas (the two tests serialize on a `Mutex` since they mutate the process‑global `istat`/`cref_istat`).
  - **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`.
- **2026‑06‑03 — P5v (channel.c can_send + match_modeid cluster) merged.** `can_send` (channel.c:615 — the channel send‑permission predicate, +m/+n/+b/+e) and `match_modeid` (channel.c:314 — the ban/exception `mlist` matcher) ported to `ircd-common/src/channel.rs`, extending the existing `channel_link.o` partial port (three new `#ifndef PORT_CHANNEL_P5` regions: the `match_modeid` forward proto switch at :75, the `match_modeid` body, the `can_send` body; the cref oracle keeps the full unguarded `channel.o`). **Third channel.c sub‑phase.** Completes the P5t deferral ("`can_send` deferred to the mode cluster — it calls the static `match_modeid`"). Plan: `docs/superpowers/plans/2026-06-03-p5v-channel-can_send.md`.
  - **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).
  - **Classification → L1/L2 split.** `can_send` is exported → `cref_can_send` exists → **L1‑differentiable** (the workhorse). `match_modeid` is a C file‑`static` → the `cref_` prefix rename keeps it local (no `cref_match_modeid` oracle symbol, cf. P5u `check_channelmask`) → **not L1‑differentiable in isolation**; its empty‑`mlist` NULL‑return is exercised via `can_send`'s ban path in L1, and the populated `+b` match is L2/review‑covered.
  - **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`).
  - **Config (verified `cbuild/config.h`):** `JAPANESE` **off** → `can_send` has no JP branch. `MODE_MODERATED`=32/`MODE_NOPRIVMSGS`=256/`MODE_BAN`=1024/`CHFL_BAN`=8/`CHFL_EXCEPTION`=16 (bindgen consts); `(*chptr).mode.mode` is `u_int`; `aConfItem.flags` is `c_long` (cast `CFLAG_NORESOLVEMATCH as c_long`). New helpers `is_an_oper`/`is_conf_no_res_match` mirror s_user.rs; `match_ipmask`/`find_channel_link`/`aConfItem`/`Link` + the mode/flag consts added to the bindings import; `strchr` added to the local extern block.
  - **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.
  - **`format!` per user request — N/A, documented in‑module.** Both return ints; no string is assembled. `format!` stays reserved for the reply‑building clusters.
  - **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`.
