  - **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`.
- **2026‑06‑03 — P5w (channel.c channel_modes leaf) merged.** `channel_modes` (channel.c:831 — the "simple" channel mode‑string serializer: writes the `+`‑flags into the caller's `mbuf` and the `+l`/`+k` parameters into `pbuf`) ported to `ircd-common/src/channel.rs`, extending the existing `channel_link.o` partial port (one new `#ifndef PORT_CHANNEL_P5` region around the body; the cref oracle keeps the full unguarded `channel.o`). **Fourth channel.c sub‑phase.** Plan: `docs/superpowers/plans/2026-06-03-p5w-channel-channel_modes.md`.
  - **Classification: exported true‑leaf callee (no `msgtab` row).** `channel_modes` touches **no file‑static** — it writes into the caller‑provided `mbuf`/`pbuf` — and calls only the `IsMember`/`IsServer` macros + libc `sprintf`/`strcat`. So a plain `#ifndef` guard drops the exported def and the still‑C callers resolve to the Rust def at link: the **`m_mode` query path** (channel.c:1112, `MODE #chan` with `parc < 3` → `channel_modes` then `replies[RPL_CHANNELMODEIS]`), **`set_mode`** (mode‑change echo), and **`send_channel_modes`** (the S2S burst — the symbol that referenced `channel_modes` at link, confirming RED). The `#ifdef USE_SERVICES` m_join call (channel.c:1987) is not compiled. No parse.rs / build.rs edit.
  - **Config findings (verified):** the body (831‑866) has **no `#ifdef`** inside it — the full bit cascade compiles verbatim. `nm cbuild/channel.o` shows `T channel_modes` (exported). `JAPANESE`/`USE_SERVICES` off — neither affects this fn. `SMode { mode: u_int, limit: c_int, key: [c_char;24] }`; mode consts present in bindings (`MODE_SECRET`=16/`MODE_PRIVATE`=8/`MODE_MODERATED`=32/`MODE_TOPICLIMIT`=64/`MODE_INVITEONLY`=128/`MODE_NOPRIVMSGS`=256/`MODE_ANONYMOUS`=4096/`MODE_QUIET`=8192/`MODE_REOP`=65536) — added the six missing ones + `strcat`/variadic `sprintf` externs to channel.rs.
  - **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.
  - **Verification:** L1 `channel_modes_diff` zero‑diff vs `cref_channel_modes` over 17 cases (empty `+`, every single bit, secret‑wins‑over‑private precedence, `+ntl`/`+nk`/`+tlk` params for a server cptr, the same `+tlk` for a **non‑member** [params suppressed → empty `pbuf`], and the all‑bits+l+k full house) — both the `mbuf` and `pbuf` byte buffers asserted identical. L2 `golden_channel_modes` 1/1 byte‑identical: alice (chanop → member) sets `+tnl 5` then `+k secret`, then the `MODE #chan` query → `324 RPL_CHANNELMODEIS` carrying the mode string + the `+l 5` (`sprintf`) and `+k secret` (`strcat`) params. `channel_modes` defined `T` from ircd‑common in the Rust `ircd`; `cargo build` 0 warnings; no new canonicalizer masks (no time tokens). **Remaining channel.c (C):** the rest of the mode cluster (`m_mode`/`set_mode`/`add_modeid`/`del_modeid`/`send_mode_list`/`change_chan_flag`/`make_bei`/`free_bei`/`send_channel_*`), join, part/kick/topic, and display clusters stay in `channel_link.o`.
