  - **Faithfulness notes:** `parc > 2 && hunt_server(…, 2, …) != 0` → `return 10` (the forwarding short‑circuit; `HUNTED_ISME`=0 falls through, `PASS`=1/`NOSUCH`=‑1 forward). Bare arg (`BadPtr(parv[1])`): `!sptr->user` → RPL_LISTEND + `return 2`; else opening notice, then loop 1 over `sptr->user->channel` listing only `Secret||Hidden` channels, then loop 2 over the global `channel` list skipping `!users || Secret || Hidden` — each with the `DBufLength > maxsendq` → ERR_TOOMANYMATCHES throttle (loop 1's `goto end_of_list` modelled as `break 'lists`, loop 2's `break` falls through), then the trailing notice. Named arg (`else`): `canonize` then per‑token `strtoken(&p, parv[1], ",")` with `parv[1]=NULL` advance; `find_channel && ShowChannel && sptr->user` → RPL_LIST; `*name=='!'` → `hash_find_channels(name+1, …)` mask loop with the `scr = SecretChannel && !IsMember` users‑`-1`/topic‑`""` masking; both with the `!MyConnect && rlen > CHREPLLEN` break. Tail: `!MyConnect && rlen > CHREPLLEN` → ERR_TOOMANYMATCHES; always RPL_LISTEND; `return 2`.
  - **L2 + L2‑S2S are the gates (no clean L1 data op — `m_list` only reads channel state and sends wire output).** L2 `golden_channel_list` 1/1 byte‑identical (`-p standalone`): bare `LIST` shows two public channels (322) + the ALIS notice + 323, and a `+s` secret channel a non‑member (alice) is not on does **not** appear (the inverse); named `LIST #pub` → just that channel; `LIST #nonexistent` → only 323 (the `find_channel` miss). **S2S `golden_s2s_list`** 1/1 byte‑identical: the `hunt_server` forwarding path the standalone harness can't reach — a local client issues `LIST #chan peer.test` (`parc > 2`, parv[2] = the linked peer) and the peer receives the forwarded `LIST` command byte‑for‑byte under ref‑C and Rust, confirming the Rust `parc > 2` branch invokes `hunt_server` and short‑circuits (`return 10`) exactly as C. `m_list` defined `T` from ircd‑common in the Rust `ircd`; `cargo build` 0 warnings; the six channel goldens (channel/topic/part/modes/can_send/invite) stay green after the `sendto_one` sig unification; no new canonicalizer masks. **Remaining channel.c (C):** the mode cluster (`m_mode`/`set_mode`/`add_modeid`/`del_modeid`/`send_mode_list`/`make_bei`/`free_bei`/`send_channel_*`), join/access (`m_join`/`m_njoin`/`can_join`/`get_channel`/`free_channel`/`reop_channel`/`collect_channel_garbage`/`setup_server_channels`), part/kick (`m_part`/`m_kick`/`find_chasing`/`check_string`), and display (`m_names`/`names_channel`) clusters stay in `channel_link.o`.

- **2026‑06‑03 — P5aa (channel.c `m_part` handler) merged.** `m_part` (channel.c:2952‑3038 — the PART command: leave one or more channels) ported to `ircd-common/src/channel.rs`, extending the existing `channel_link.o` partial port (one `#ifndef PORT_CHANNEL_P5` region around the body). **Eighth channel.c sub‑phase.** Plan: `docs/superpowers/plans/2026-06-03-p5aa-m_part.md`.
  - **First handler in channel.c that calls the file‑static `get_channel`.** `m_part` is a `msgtab` row (`PART = [m_part, m_part, m_part, m_nop, m_unreg]` → unreg/client/server cols, min 1 param). Unlike the P5y/P5z true leaves (which use `find_channel`), it resolves each named channel via `get_channel(sptr, name, 0)` — a `static` in C that **stays C** (the channel‑lifecycle cluster isn't ported yet). So the **P5u/P5x extern‑switch** was applied to `get_channel`: the forward decl (channel.c:69) and the definition storage class (channel.c:2174) both flip `static`→`extern` under `#ifdef PORT_CHANNEL_P5` (linkage‑only; the body stays C and resolves within channel.c, but the symbol is now reachable from the Rust m_part). Every other callee is already Rust — `canonize` (P5m), `strtoken` (P1), `check_channelmask` (P5u), `remove_user_from_channel` (P5x) — or a C variadic sender (`sendto_serv_butone` added to the extern block; `sendto_match_servs`/`sendto_channel_butserv` already there). `get_channelmask(name)` is the JAPANESE‑off macro `rindex(name,':')` = `strrchr`. RED confirmed via the sole undefined `m_part` (get_channel resolved as C).
  - **Config (verified):** `JAPANESE` **off** → the `&& jp_valid(NULL, chptr, NULL)` clause in the broadcast‑batching guard is not compiled, so the condition is just `(!get_channelmask(name) && *chptr->chname != '!')`; the `#ifdef JAPANESE` extern `get_channelmask`/`jp_valid` decls are skipped and the `#else` macro form applies. `USE_NEWMSG` off (no effect on m_part). Numerics: ERR_NOSUCHCHANNEL=403, ERR_NOTONCHANNEL=442. `BUFSIZE`=512, `TOPICLEN`=255 (struct_def.h, bindgen drops the macros → module consts). New helper `my_person` (`MyConnect && IsPerson`, struct_def.h:791).
  - **The shared `static buf[BUFSIZE]` → a module‑private `static mut BUF`.** channel.c's `buf` is shared across the TU, but `m_part` uses it as **pure scratch**: `*buf = '\0'` at entry, `strcat`‑accumulates the comma‑joined non‑local channel names, and flushes (`sendto_serv_butone` + `*buf = '\0'`) within the same call — no state crosses calls or is read by another function. So a private `static mut BUF: [c_char; BUFSIZE]` in the Rust module is faithful (not ABI → no `#[no_mangle]`; accessed via `addr_of_mut!`). The `size = BUFSIZE - strlen(parv[0]) - 10` C size_t arithmetic is mirrored with `wrapping_sub` and the `strlen(buf)+strlen(name)+1 > size` test compares `size as usize` (the exact C int→size_t promotion).
  - **`format!` per user request — N/A, documented in‑module.** `m_part` builds no reply string of its own; the comma‑joined `buf` is assembled with libc `strcat` byte‑for‑byte (arbitrary client channel‑name bytes — a `String` would corrupt non‑ASCII), and every reply/relay goes straight to a C variadic sender. PartFmt (`":%s PART %s :%s"`) is inlined as a `c"…"` literal at each call site.
  - **Faithfulness notes:** `comment = BadPtr(parv[2]) ? "" : parv[2]` reads `parv[2]` directly (the parser NULL‑pads parv past parc, so `parc` is unused — `_parc`, matching C); `strlen(comment) > TOPICLEN` → `comment[TOPICLEN] = '\0'`. Per‑token loop (`strtoken(&p, parv[1], ",")`, `parv[1]=NULL` advance): `get_channel` miss → `MyPerson(sptr)`‑gated 403 + continue; `check_channelmask != 0` → continue; `!IsMember` → 442 + continue. Then the broadcast split: a non‑local (`*name != '&'`) channel whose name has no `:` mask and isn't `!`‑prefixed accumulates into `buf` (flush‑on‑overflow); otherwise `sendto_match_servs` relays it immediately with the per‑channel name; always `sendto_channel_butserv` echoes `:nick PART #chan :comment` to members and `remove_user_from_channel` unlinks the parter. Tail: a non‑empty `buf` gets one final `sendto_serv_butone`. Returns 4.
  - **L2 + L2‑S2S are the gates (no clean L1 — m_part is a socket‑driven handler with no extractable data‑op core, consistent with m_topic/m_list).** L2 `golden_channel_part` (now driving the Rust m_part) stays byte‑identical on the local success path (PART observed by a second member + the NAMES/WHO inverse confirming the parter is gone — `remove_user_from_channel`), **extended** with `part_errors_match_reference`: m_part's own 403 (PART a nonexistent channel) and 442 (PART a channel you're not on) numerics, previously unexercised. **S2S `golden_s2s_membership`** already covers the `IsServer(cptr)`/`MyPerson` remote‑PART branch — a remote user behind the peer PARTs (`:001BAAAAA PART #chan`), routed through the now‑Rust m_part's `sptr->user->uid`‑sourced propagation + the member relay a local observer sees, then NAMES no longer lists the remote user. All byte‑identical ref‑C == Rust; `cargo build` 0 warnings; no new canonicalizer masks. **Remaining channel.c (C):** the mode cluster (`m_mode`/`set_mode`/`add_modeid`/`del_modeid`/`send_mode_list`/`make_bei`/`free_bei`/`send_channel_*`), join/access (`m_join`/`m_njoin`/`can_join`/`get_channel`/`free_channel`/`reop_channel`/`setup_server_channels`), `m_kick`/`find_chasing`/`check_string`, and display (`m_names`/`names_channel`) clusters stay in `channel_link.o`.

- **2026‑06‑03 — P5bb (channel.c `m_kick` handler + file‑static `find_chasing`) merged.** `m_kick` (channel.c:3068‑3221 — the KICK command) and the file‑static `find_chasing` (channel.c:139 — history‑aware nick resolver) ported to `ircd-common/src/channel.rs`, extending the existing `channel_link.o` partial port (`-DPORT_CHANNEL_P5`). **Ninth channel.c sub‑phase.** Plan: `docs/superpowers/plans/2026-06-03-p5bb-channel-m_kick.md`.
  - **`find_chasing` is the cluster's shared static — the P5u/P5x extern‑switch case.** `m_kick` is its caller, but `find_chasing` is also called by the still‑C `set_mode`/`m_mode` (channel.c:~1301), so the C body is guarded out under `#ifndef PORT_CHANNEL_P5` and the `#else` branch adds an `extern aClient *find_chasing(…)` forward decl (it had none — it was defined before both use sites) so the C remnant resolves to the Rust `#[no_mangle]` def at link (linkage‑only, faithful). `check_string` — the other display‑cluster static — is **not** touched: `m_kick` doesn't call it (only `make_bei`/`m_mode` do). RED confirmed via the two undefined symbols `find_chasing`+`m_kick` (every other callee already Rust or a C extern).
  - **Callees, all already resolved:** `get_channel` (C static, extern‑switched by P5aa; called flag `0` = `!CREATE`), `check_channelmask` (P5u), `is_chan_op` (P5t), `remove_user_from_channel` (P5x), `find_uid` (P2), `find_client` (P4), `get_history` (P2 whowas), `mystrdup` (P1), `MyFree`→ libc `free`, `strlen`/`strcat` libc; the C variadic senders `sendto_flag`/`sendto_match_servs_v` added to the local extern block (`sendto_one`/`sendto_channel_butserv` already there). Inline macro helpers all present except a `std::cmp::max` for the C `MAX`. Numerics 403/477(`ERR_NOCHANMODES`)/442/482 + 401 (find_chasing) + 441 (`ERR_USERNOTINCHANNEL`, added to the import). Consts `MAXPENALTY`=10, `UIDLEN`=9, `KILLCHASETIMELIMIT`=90 (config.h:676 macro, bindgen drops it) as module consts.
  - **Faithfulness notes.** `nbuf[BUFSIZE+1]` is a **local auto** array in C (not a file‑static) → a Rust stack `[c_char; BUFSIZE+1]`; no shared `buf`/`modebuf` touched. The C size_t arithmetic is mirrored exactly: `maxlen = BUFSIZE - MAX(strlen(sender),strlen(name)) - strlen(comment) - 10` via `wrapping_sub … as c_int`, and `clen = maxlen - strlen(name) - 1` via `(maxlen as usize).wrapping_sub(…)` (the C int→size_t promotion), with the flush test comparing `… >= clen as usize`. The `!(IsServer(cptr) && (who=find_uid(...))) && !(who=find_chasing(...))` short‑circuit assignment is preserved branch‑for‑branch (find_uid only attempted for a server `cptr`, find_chasing only when that misses). `sender` = SID (server) / UID (person) / name; the relay echoes `sptr->name`/`who->name` to members but the comma‑batched `sendto_match_servs_v(SV_UID,…)` carries UIDs (`who->user->uid`). Returns `penalty`.
  - **`format!` per user request — N/A, documented in‑module.** `find_chasing` returns a pointer (its one 401 goes to the C sender); `m_kick` assembles the comma‑joined victim‑id list `nbuf` with libc `strcat` byte‑for‑byte (arbitrary client nick/UID bytes — a `String` would corrupt non‑ASCII) and routes every wire line through the C variadic senders.
  - **L2 + L2‑S2S are the gates (no L1 — `find_chasing` is file‑static so no `cref_` oracle symbol, cf. P5u/P5x statics; `m_kick` is a socket‑driven handler with no extractable data‑op core, cf. m_topic/m_list/m_part).** New `golden_channel_kick` (L2): chanop alice KICKs member bob → bob sees `:alice KICK #chan bob :…` + the NAMES inverse (bob gone — `remove_user_from_channel`); plus m_kick's own error numerics 441 (find_chasing resolves a non‑member → `!IsMember`), 403, 442, 482 (461 min‑params is the parser's, not asserted). New `golden_s2s_kick` (L2‑S2S): (a) **UID propagation** — a local chanop KICKs a remote member (peer‑NJOINed) → the peer receives `:000AAAAAA KICK #chan 001BAAAAA :…` (`sendto_match_servs_v(SV_UID)`, kicker‑UID source + victim‑UID target); (b) **server‑sourced KICK** — the peer server KICKs a local user by UID (`:001B KICK #chan 000AAAAAA :…`) → the `IsServer(cptr)`+`find_uid` victim resolution + `IsServer(sptr)` SID‑sender/SCH_NOTICE branches relay `:peer.test KICK #chan alice :…` and remove her (NAMES member‑list inverse). All byte‑identical ref‑C == Rust; `cargo build` 0 warnings; no new canonicalizer masks. **Also fixed a pre‑existing timing flake** in `golden_s2s_membership` (`s2s_deop_and_clean_readd`): the re‑NJOIN `:bob JOIN` relay raced the 300 ms drain window — now consumed deterministically via `read_until(" JOIN ")` (mirroring the MODE‑relay barrier already in that test). **Remaining channel.c (C):** the mode cluster (`m_mode`/`set_mode`/`add_modeid`/`del_modeid`/`send_mode_list`/`make_bei`/`free_bei`/`send_channel_*`), join/access (`m_join`/`m_njoin`/`can_join`/`get_channel`/`free_channel`/`reop_channel`/`setup_server_channels`), `check_string`, and display (`m_names`/`names_channel`) stay in `channel_link.o`.
- **2026‑06‑03 — P5cc (channel.c display cluster — `m_names` handler + file‑static `names_channel`) merged.** `m_names` (channel.c:3730 — the NAMES command) and the file‑static `names_channel` (channel.c:3577 — the per‑channel `353 RPL_NAMREPLY`/`366 RPL_ENDOFNAMES` emitter) ported to `ircd-common/src/channel.rs`, extending the existing `channel_link.o` partial port (`-DPORT_CHANNEL_P5`). **Tenth channel.c sub‑phase.** Plan: `docs/superpowers/plans/2026-06-03-p5cc-channel-m_names.md`.
  - **`names_channel` is the cluster's shared static — the P5u/P5x extern‑switch case.** `m_names` is one caller, but `names_channel` is **also called by the still‑C `m_join`** (channel.c:2673, the JOIN echo). So its `static` forward decl (channel.c:105) is switched to `extern` under `#ifndef PORT_CHANNEL_P5`, and its body is guarded out, so the C `m_join` remnant resolves to the Rust `#[no_mangle]` def at link (linkage‑only, faithful). `m_njoin` does **not** call it (verified — the only other channel.c caller besides m_names is m_join). `m_names` is a plain `#ifndef` guard (no extern switch — its decl is imported by parse.rs from `ircd_sys::bindings`, resolving to Rust once `channel_link.o` drops the C def). RED confirmed via the two undefined symbols `names_channel`+`m_names`.
  - **Callees, all already resolved:** `find_channel_link` (P2), `find_channel`/`clean_channelname` (P5t), `strtoken` (P1), `hunt_server` (still C — bindings decl), the variadic `sendto_one` (P8 C trampoline). Inline macro helpers all present except a new `is_invisible` (`x->user->flags & FLAGS_INVISIBLE`, mirroring s_user.rs:144); globals `client`/`channel`/`me` + numerics `RPL_NAMREPLY`=353/`RPL_ENDOFNAMES`=366 + `FLAGS_INVISIBLE` added to the import. `BUFSIZE`/`MAXCHANNELSPERUSER`/`MAXPENALTY` already module consts.
  - **Shared `buf` — the keystone faithfulness point.** In C, `m_names` and `names_channel` both use the file‑static `char buf[BUFSIZE]` (channel.c:115). They are not reentrant: `names_channel` fills `buf` and flushes it (`sendto_one`) **within each call**, and `m_names` only writes `buf` itself (the third `strcpy(pbuf,"* * :")` remaining‑users section) **after** all its `names_channel` calls — so reusing the existing module `BUF` (introduced for P5aa `m_part`) is faithful, exactly as C shares one `buf`. **Documented residual:** the still‑C `m_join` keeps its own C `buf`; in its `!`‑safe‑channel autocreate branch it does `sprintf(buf,…); name=buf;` and re‑reads `name` after the `names_channel` call (which, in ref‑C, clobbers `buf`). With `names_channel` now writing the *Rust* `BUF`, C `m_join`'s `name` survives intact → the Rust path is in fact *more* correct in that one buggy branch. This affects **only** `!`‑autocreate NJOIN bytes (no golden scenario drives `!` channels; all tests use `#`/`&`/`+` where `name` is the parv token, never `buf`) and disappears when `m_join` itself ports and `buf` becomes Rust‑owned. Noted, not tested.
  - **Faithfulness notes.** `names_channel`'s `cptr` param is unused in C (only `sptr`/`to`/`chptr`/`sendeon` are read) → `_cptr`. Pointer arithmetic mirrored raw: `*pbuf = ch; pbuf = pbuf.add(1)` for the `@`/`+`/`=`/`*` prefix bytes; the `353` member list assembled with `copy_nonoverlapping` (arbitrary client nick bytes — a `String` would corrupt non‑ASCII); the `maxlen = BUFSIZE - 1 - strlen(ME) - 5 - strlen(to) - 1 - pxlen - 2` budget + the `(pbuf - buf) + nlen >= maxlen` flush‑and‑restart‑after‑prefix exactly as C. The server‑member skip (`strchr(name,'.')`) precedes the `IsInvisible` skip, matching the C order. m_names: the `parc>2` `hunt_server(":%s NAMES %s %s",2,…)` forward short‑circuit → `MAXPENALTY`; the named‑arg branch's `sent` throttle return (`sent<2 ? 2 : (sent*MAXCHANNELSPERUSER)/MAXPENALTY`); the all‑channels branch's three sections (secret‑on‑user, public via `channel`/`nextch`, remaining users via `client`/`next`). The dead `sent=1`/`sent=0` writes in the third section (never read before `return MAXPENALTY`) are omitted — observationally identical (local, unread).
  - **`format!` per user request — N/A, documented in‑module.** Neither builds an ASCII reply string in Rust; the `353` member list is assembled into `BUF` byte‑for‑byte (`copy_nonoverlapping` + `@`/`+`/space prefixes), and every wire line goes to the C variadic `sendto_one`.
  - **L2 + L2‑S2S are the gates (no L1 — read‑only + wire, no extractable socket‑free data‑op core, cf. m_topic/m_list/m_part).** New `golden_channel_names` (L2): `NAMES #pub` from a member → `= #pub :@alice bob` 353 + 366 (the `=` public marker, `@` op prefix); a `+s` secret channel's member view → the `@ #secret` 353 marker; the **inverse** — a non‑member's `NAMES #secret` yields only 366 (`showusers=0`, members hidden); bare `NAMES` → all‑visible‑channels + the `* * :` remaining‑users third section ending `366 … *`. JOIN‑echo regression covered by the existing `golden_channel` (its 353/366 now come from the Rust `names_channel` via the still‑C `m_join`). New `golden_s2s_names` (L2‑S2S): the `parc>2` `hunt_server` forwarding short‑circuit (`NAMES #chan peer.test` → the peer receives the byte‑identical forwarded `NAMES` line). All byte‑identical ref‑C == Rust; `cargo build` 0 warnings; no new canonicalizer masks. **Test note:** the L2 test uses **2** concurrent clients (alice op of #pub, bob member + #secret owner) — a 3‑client variant tripped the per‑host clone limit (BrokenPipe), so the inverse is driven by alice (non‑member of #secret) rather than a third nick. **Remaining channel.c (C):** the mode cluster (`m_mode`/`set_mode`/`add_modeid`/`del_modeid`/`send_mode_list`/`make_bei`/`free_bei`/`send_channel_*`), join/access (`m_join`/`m_njoin`/`can_join`/`get_channel`/`free_channel`/`reop_channel`/`setup_server_channels`), and `check_string` stay in `channel_link.o`.

- **2026‑06‑03 — P5dd (channel.c JOIN cluster — `m_join` handler + file‑static `can_join`) merged.** `m_join` (channel.c:2395‑2710 — the JOIN command) and `can_join` (channel.c:2036‑2119 — the JOIN access‑control predicate) ported to `ircd-common/src/channel.rs`, extending the existing `channel_link.o` partial port (`‑DPORT_CHANNEL_P5`). **Eleventh channel.c sub‑phase.** Plan: `docs/superpowers/plans/2026-06-03-p5dd-channel-m_join.md`.
  - **`can_join` is a private Rust `unsafe fn`, not an export.** It is file‑static in C and its **only** caller (`m_join`) ports here too → no C caller remains, so both its forward decl (channel.c:53) and body are plainly `#ifndef PORT_CHANNEL_P5`‑guarded out (no extern‑prototype switch needed, unlike the P5u/P5x shared statics). With no `cref_can_join` oracle symbol it has no L1 — every return branch is L2‑observed through `m_join`'s reject numerics. `m_join` is a plain `#ifndef` guard (msgtab `JOIN`=`[m_nop,m_join,m_join,m_nop,m_unreg]` → client (col 1) + server (col 2, the `IsServer(cptr)` JOIN‑0 head); its decl parse.rs imports from `ircd_sys::bindings` resolves to the Rust def once `channel_link.o` drops the C def). RED confirmed via the sole undefined `m_join` (`can_join` had no remaining C reference).
  - **Callees, all already resolved:** Rust — `strtoken` (P1), `check_channelmask` (P5u), `clean_channelname`/`find_channel`/`is_chan_op` (P5t), `hash_find_channels` (P2), `check_chid`/`get_chid` (P3 s_id), `del_invite` (P5u), `add_user_to_channel`/`remove_user_from_channel` (P5x), `names_channel` (P5cc), `exit_client` (P5c), `match_modeid` (P5v), `mycmp` (P1 match_); C extern — `get_channel` (channel.c file‑static, extern‑switched by P5aa; called both `0`=!CREATE and `CREATE`), the variadic senders (`sendto_serv_v` + `sendto_channel_butone` added to the local extern block; `sendto_one`/`sendto_channel_butserv`/`sendto_match_servs`/`sendto_match_servs_v`/`sendto_flag` already there). New helper `is_split` (`iconf.split > 0`, struct_def.h:805). New consts/imports: `bootopt`/`iconf`/`CREATE`/`BOOT_PROT`/`CHFL_INVITE`/`CHFL_REOPLIST`/`EXITC_VIRUS`/`CHIDLEN`(local)/the JOIN ERR_* numerics. `CLIENTS_CHANNEL` off (no `&CLIENTS` branch), `USE_IAUTH` on (`&AUTH` in can_join's oper‑channel set), `JAPANESE` off (no `jp_valid`), `TOPIC_WHO_TIME` defined (the topic‑on‑join 333 block ported).
  - **`jbuf` vs the shared `buf` — the faithfulness point.** `m_join` uses **two** distinct buffers: its function‑`static char jbuf[BUFSIZE]` (the validated, comma‑joined channel‑list accumulator) → a new module‑private `static mut JBUF`; and the file‑static `buf[BUFSIZE]` (the `!`‑channel‑id `sprintf(buf,"!%.*s%s",CHIDLEN,get_chid(),name+2)` scratch) → the existing module `BUF`. Both are pure in‑call scratch (never read across calls), so private Rust statics are faithful. Porting `m_join` also **resolves the P5cc‑documented residual**: the `!`‑autocreate `name=buf` aliasing is now wholly within the Rust module's `BUF` (no cross‑language `buf` clobber).
  - **`format!` per user request — N/A, documented in‑module.** Neither builds an ASCII reply string of its own — every wire line goes to a C variadic sender; the one `sprintf(buf,"!%.*s%s",…)` `!`‑id builder is a byte build into `BUF` (arbitrary client bytes — a `String` would misframe), faithful to C (cf. P5y `topic_nuh`).
  - **Faithfulness notes.** `IsServer(cptr)` head (the only JOIN over a link): `parv[1]=="0"` → walk `sptr->user->channel`, PART each (the `PartFmt` `:%s PART %s :%s` literal + the now‑Rust `remove_user_from_channel`), then `sendto_match_servs(NULL,cptr,":%s JOIN 0 :%s",uid,parv[0])`; a non‑zero server JOIN is silently ignored. First pass: per‑token `check_channelmask`/`"0"`‑literal/`clean_channelname`/the `!`‑channel block (`!!`/`!#` new‑id build + `hash_find_channels`/`check_chid` duplicate rejects; short `!chan` resolves via find_channel→hash_find_channels with the `&&`‑short‑circuit preserved)/`IsChannelName` gate/jbuf‑overflow `break`. Second pass: parallel `key` strtoken on parv[2]; `JOIN 0` part‑all; `get_channel(!CREATE)`; `IsMember` skip; `MyConnect && joined>=MAXCHANNELSPERUSER` → 405; the 10‑byte virus‑string `strncmp` → `exit_client`; the split‑mode create gate → 437; `get_channel(CREATE)`; `can_join` → `replies[i]`; the chanop‑on‑create flag decision; `add_user_to_channel`; the JOIN echo; `del_invite`; topic 332/333; `names_channel`; anonymous NOTICEs; the NJOIN propagation (`get_channelmask(name)||*chname=='!'` → `sendto_match_servs_v(SV_UID)` else `*chname!='&'` → `sendto_serv_v(SV_UID)`, both `me.serv->sid`‑sourced with the `@@`/`@`/`""` flag prefix). Returns 2.
  - **L2 + L2‑S2S are the gates (no L1 — `m_join` only manipulates membership via the already‑L1'd `add_user_to_channel`/`remove_user_from_channel` and emits wire; `can_join` is a file‑static, cf. m_names/m_list/m_topic).** New `golden_channel_join` (L2, `boot_standalone`, 5 scenarios incl. inverses): `+k` wrong‑key 475 then the keyed JOIN succeeds (the key round‑trip); `+i` 473 then alice's INVITE overrides can_join's invite gate; `+l 1` full 471; a duplicate JOIN is a silent no‑op (the `IsMember` skip — assert no second echo); `JOIN 0` PARTs every channel then the NAMES inverse (she is gone, the channel destructed). The existing `golden_channel` (basic JOIN + JOIN→PART→re‑JOIN round‑trip) now drives the Rust m_join as a regression. New `golden_s2s_join` (L2‑S2S, `boot_s2s`, 2 scenarios): (a) a local client's JOIN of a federated `#chan` (peer‑created via NJOIN) propagates `:000A NJOIN #chan :000AAAAAA` to the peer — the `sendto_serv_v` SV_UID tail formatting the LOCAL SID + the joiner's UID, fields the client‑facing echo never carries; (b) the `IsServer(cptr)` head — a peer‑sourced `:001BAAAAA JOIN 0` relays bob's `:bob!~bob@remote.host PART #chan` to a local co‑member. All byte‑identical ref‑C == Rust; the 10 channel goldens + 20 s2s goldens + the channel L1 diffs stay green; `cargo build` 0 warnings; no new canonicalizer masks. **Remaining channel.c (C):** the mode cluster (`m_mode`/`set_mode`/`add_modeid`/`del_modeid`/`send_mode_list`/`make_bei`/`free_bei`/`send_channel_*`), `m_njoin`, `reop_channel`/`collect_channel_garbage`/`setup_server_channels`, `free_channel`, and `check_string` stay in `channel_link.o`.

- **2026‑06‑03 — P5ee (channel.c `m_njoin` handler — server NJOIN burst) merged.** `m_njoin` (channel.c:2724‑2974 — the server‑to‑server channel‑membership burst command, carrying `[@@+]uid,…` member lists) ported to `ircd-common/src/channel.rs`, extending the existing `channel_link.o` partial port (`‑DPORT_CHANNEL_P5`). **Twelfth channel.c sub‑phase.** Plan: `docs/superpowers/plans/2026-06-03-p5ee-channel-m_njoin.md`.
  - **Server‑only → L2‑S2S is the only gate.** msgtab `NJOIN`=`[m_njoin, m_nop, m_nop, m_nop, m_unreg]` → **col 0 only** (reachable from a linked server; a registered client hits `m_nop`). No standalone client path → no L2, and (a socket‑driven wire handler) no extractable L1 data op. A plain `#ifndef PORT_CHANNEL_P5` guard drops the C def; the `m_njoin` decl parse.rs imports from `ircd_sys::bindings` resolves to the Rust def at link (m_njoin is not anyone's static callee → no extern‑prototype switch). Split from the P5dd m_join cluster: m_njoin shares no static with m_join (only the global scratch buffers) and is server‑only. RED confirmed via the sole undefined `m_njoin`.
  - **Callees, all already resolved:** Rust — `check_channelmask` (P5u), `find_person`/`find_uid` (P2/P4), `add_user_to_channel` (P5x), `get_client_name` (P5c s_misc); C extern — `get_channel` (channel.c file‑static, extern‑switched by P5aa; called with `CREATE`), the variadic senders `sendto_one`/`sendto_flag`/`sendto_channel_butserv`/`sendto_match_servs_v` (all already in the block). New helper `is_bursting` (`!(flags & FLAGS_EOB)`, struct_def.h:222; `flags` is `c_long`). New imports: `get_client_name`/`DELAYCHASETIMELIMIT`/`FLAGS_EOB`/`MAXMODEPARAMS`/`NICKLEN`/`ServerChannels_SCH_CHAN`/`ServerChannels_SCH_DEBUG`; module const `MODEBUFLEN`=200. `JAPANESE` off → the `IsServer(sptr) ||` clause in the JOIN‑relay `:`‑prefix test is dropped (just `IsBursting(sptr)`).
  - **Shared `modebuf`/`parabuf` file‑statics → module‑private `MODEBUF`/`PARABUF`.** These are shared with the still‑C `set_mode`, but `m_njoin` uses them as **pure in‑call scratch** (zeroed at the first mode via the `cnt==0` reset, flushed via `sendto_channel_butserv` before return — no state crosses calls or is read by another fn), so private Rust statics are faithful (cf. P5aa's `BUF`). `uidbuf[BUFSIZE]` and `mbuf[3]` are C **autos** → Rust stack arrays.
  - **`format!` per user request — N/A, documented in‑module.** m_njoin builds no ASCII reply of its own; the `uidbuf` NJOIN‑relay buffer (the re‑emitted UID member list) and the `modebuf`/`parabuf` synthetic‑MODE scratch are assembled byte‑for‑byte (arbitrary client UID/nick bytes — a `String` would misframe non‑ASCII); every wire line goes to a C variadic sender.
  - **Faithfulness notes.** The mode‑prefix parse (`@`/`@@`/`@@+`/`@+`/`+` → `mbuf` `o`/`ov`/`v` + `chop` CHFL bits, `name` advanced past the prefix, `target` left at the prefix for the verbatim uidbuf copy) is branch‑for‑branch. The MODE‑burst accumulator (the `cnt` switch): case 0 falls through to case 1 (`strcat modebuf/parabuf`, the `mbuf[1]` double‑name for a `+ov` user), case 2 flushes `:%s MODE %s +%s%c %s %s` (the `mbuf[0]` `%c` promoted `as c_int`), `cnt == MAXMODEPARAMS` flushes `:%s MODE %s +%s %s` and resets — modelled as `if cnt==0||cnt==1 {…} else if cnt==2 {…}` (the C fall‑through). The size_t/ptrdiff math (`maxlen = BUFSIZE - 17 - strlen(parv0) - strlen(parv1) - NICKLEN`; the `u.offset_from(u_base) >= maxlen` flush test) mirrored as signed `isize`. Empty‑channel lock (`parv[2]=="."` → `history = timeofday + (L)DELAYCHASETIMELIMIT`, `istat.is_hchan*` bumps, return 0) ported (no observable wire → review‑covered). JOIN relay `:%s JOIN %s%s` with the `:`‑prefix‑unless‑bursting netjoin‑discriminator trick. Already‑on‑channel error class (`IsBursting` → SCH_ERROR notice + `ERROR` line to cptr; else SCH_CHAN "Fake:" notice). Source SID = `sptr->serv->sid` (the NJOIN origin server). Returns 0.
  - **L2‑S2S green (the only gate).** New `golden_s2s_njoin`: the `@+` (op+voice) MODE‑burst path the existing `golden_s2s_membership` (plain add/remove) doesn't cover — a peer NJOINs carol `@+001BBBBBB` onto a `#chan` a LOCAL member (alice) is already on → alice observes the relayed `:carol… JOIN :#chan` + the synthetic `:peer.test MODE #chan +ov carol carol` burst (exercising the `mbuf[1]` double‑param path). `golden_s2s_membership` (remote NJOIN add → `add_user_to_channel`, remote PART remove, the deop/clean‑readd inverse) now drives the **Rust** m_njoin as a regression; the other NJOIN‑driving goldens (`golden_s2s_join`/`golden_s2s_kick`/`golden_s2s_topic`) stay byte‑identical ref‑C == Rust. `cargo build` 0 warnings; no new canonicalizer masks. **Remaining channel.c (C):** the mode cluster (`m_mode`/`set_mode`/`add_modeid`/`del_modeid`/`send_mode_list`/`make_bei`/`free_bei`/`send_channel_*`), `reop_channel`/`collect_channel_garbage`/`setup_server_channels`, `free_channel`, and `check_string` stay in `channel_link.o`.
