  - **`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`.
- **2026‑06‑03 — P5ff (channel.c mode‑id list layer) merged.** `check_string` (channel.c:176), `make_bei` (:212), `free_bei` (:193), `add_modeid` (:263), `del_modeid` (:331) — the ban/exception/invite (`+b`/`+e`/`+I`/`+R`) mode‑id list‑item allocators + the `chptr->mlist` add/del — ported to `ircd-common/src/channel.rs`, extending the existing `channel_link.o` partial port (`‑DPORT_CHANNEL_P5`). **Thirteenth channel.c sub‑phase**; the connected component over the file‑static `asterix[2]="*"` var ({check_string, make_bei, free_bei} reference it; add_modeid/del_modeid call make_bei/free_bei). Plan: `docs/superpowers/plans/2026-06-03-p5ff-channel-modeid-list.md`.
  - **All five are file‑`static` in C → the P5u/P5x extern‑prototype switch on each.** The still‑C `set_mode` calls `check_string` (:1295/:1400), `make_bei` (:1786), `add_modeid` (:1934), `del_modeid` (:1937); `set_mode` + the still‑C `free_channel` (:2368) call `free_bei`. So each `static` forward decl flips to `extern` under `#ifdef PORT_CHANNEL_P5` and the body is guarded out, resolving the C remnant to the Rust `#[no_mangle]` def at link (linkage‑only, faithful). `add_modeid`/`del_modeid`/`free_bei`/`make_bei` had forward decls (channel.c:97/98/112/113); `check_string` had **none** (defined before its callers) → a new `extern char *check_string(char *)` added under the guard (cf. P5x `add_user_to_channel`). RED confirmed via exactly the five undefined symbols (`asterix` has no external C reference → no link error, as predicted).
  - **`asterix` is module‑private (`static mut ASTERIX: [c_char;2]`).** C never references `asterix` outside the now‑ported `check_string`/`make_bei`/`free_bei` (verified: channel.c:44/181/190/195/199/203/220/222/231/233/242/244 are all inside the cluster), so the Rust pointer identity is self‑consistent: `make_bei` stores the Rust asterix ptr, `free_bei` compares against it (`!= asterix_ptr()` ⇒ a MyMalloc'd component to `free`), and the still‑C `set_mode`/`free_channel` only ever *pass* the resulting `aListItem` (never compare a component to their own asterix). No cross‑language pointer‑identity mismatch. The C `asterix` def is `#ifndef`‑guarded out.
  - **Callees, all already resolved:** Rust — `collapse` (P1 match_; the `add_modeid` MyClient nick/user/host collapse), `mycmp` (P1; `BanExact`), `make_link`/`free_link` (P2 list), `istat` (P2; `is_bans`/`is_banmem` are `u_long` → `wrapping_add`/`wrapping_sub`, cf. P5u add_invite — a debug `-=` underflow would abort the nounwind extern fn); libc — `MyMalloc`(P2)/`free`/`strncpy`(`strncpyzt_` helper)/`strlen`/`isspace` (added to the extern block; `check_string` passes the char sign‑extended `as c_int` as C does). New imports: `aListItem`/`collapse`/`ERR_BANLISTFULL`/`HOSTLEN`/`MAXBANLENGTH`/`MAXBANS`/`RPL_BANLIST`/`RPL_EXCEPTLIST`/`RPL_INVITELIST`/`RPL_REOPLIST`/`USERLEN`. Inline `ban_len`/`ban_exact` macros + `asterix_ptr`/`strncpyzt_` helpers. The `add_modeid` `(len > MAXBANLENGTH) || (++cnt >= MAXBANS)` short‑circuit modelled as `len > … || { cnt += 1; cnt >= … }` (the `++cnt` block runs only when the left is false — faithful to C's `||`). `bzero(mode, sizeof(Link))` → `ptr::write_bytes(mode,0,1)`.
  - **No L1 — all five are file‑`static` → the `cref_` archive keeps them local (no `cref_add_modeid` oracle, cf. P5v `match_modeid` / P5dd `can_join`).** The data‑op faithfulness is gated at **L2**: ref‑C `set_mode` calls C `add_modeid`/`make_bei`; the Rust build's (still‑C) `set_mode` calls the **Rust** ones — byte‑identical wire output through `set_mode` is the gate.
  - **`format!` per user request — N/A, documented in‑module.** `make_bei` copies arbitrary client‑supplied ban‑mask bytes (`nick`/`user`/`host`, capped NICKLEN/USERLEN/HOSTLEN) via `strncpyzt_` byte copies (a UTF‑8 `String` would corrupt non‑ASCII — the standing P5c/P5f rule); every reject reply (`ERR_BANLISTFULL`, `RPL_BANLIST`/etc.) goes straight to the C variadic `sendto_one`; no sub‑`long` int reaches a sender.
  - **L2 green + L2‑S2S green (the gates).** New `golden_channel_ban` (L2): local chanop alice `+b foo!bar@baz`/`+e *!*@trusted.example`/`+I friend!*@*` add (→ `make_bei` 3‑component split + asterix‑fill + `add_modeid` head‑prepend), the `MODE #chan b`/`e`/`I` query (`367`/`348`/`346` reading the Rust‑built `mlist` + `368`/`349`/`347` terminators), duplicate `+b` (BanExact dedup → re‑`367`, no second node), then `‑b` (→ `del_modeid` + `free_bei`) and the **inverse** re‑query (the ban is gone — mask occurrence count 2→1). New `golden_s2s_ban` (L2‑S2S): a peer **server** sets `:001B MODE #chan +b …` → `set_mode` calls the Rust `add_modeid` with `MyClient(cptr)` **false** (the pure non‑MyClient list‑add arm that skips every `sendto_one`, reachable only over a link), alice's `MODE #chan b` query confirms the ban, then the peer server `‑b` → `del_modeid` + `free_bei` and the re‑query shows it gone. All byte‑identical ref‑C == Rust; full `ircd-golden` suite + the channel L1 diff suite (`channel_leaf/invite/can_send/membership/modes_diff`) green; `cargo build --workspace` 0 warnings; no new canonicalizer masks. **Remaining channel.c (C):** the mode core (`m_mode`/`set_mode`/`send_mode_list`/`send_channel_modes`/`send_channel_members`, sharing the `modebuf`/`parabuf`/`uparabuf` statics) + `reop_channel`/`collect_channel_garbage`/`setup_server_channels`/`free_channel` stay in `channel_link.o`.
- **2026‑06‑03 — P5gg (channel.c mode core — `m_mode`/`set_mode`/`send_mode_list`/`send_channel_modes`/`send_channel_members`) merged.** The MODE command handler (channel.c:1136), the ~860‑line mode parser/applier `set_mode` (:1195), the `+beIR` list emitter `send_mode_list` (:931), and the two server‑burst emitters `send_channel_modes` (:1022) / `send_channel_members` (:1066) ported to `ircd-common/src/channel.rs`, extending the existing `channel_link.o` partial port (`‑DPORT_CHANNEL_P5`). **Fourteenth (final large) channel.c sub‑phase** — the connected component over the shared file‑static scratch buffers `modebuf`/`parabuf`/`uparabuf`. Plan: `docs/superpowers/plans/2026-06-03-p5gg-channel-mode-core.md`.
  - **All five take a plain `#ifndef PORT_CHANNEL_P5` guard — no extern‑prototype switch.** `set_mode`/`send_mode_list` are file‑`static` in C, and after this port have **no remaining C caller** (their only callers — `m_mode` and `send_channel_modes` — port here; `m_njoin`, the only other `modebuf`/`parabuf` user, is already Rust from P5ee) → they become **private Rust `unsafe fn`s** (no `cref_` oracle → L2/L2‑S2S tested). `send_channel_modes`/`send_channel_members` are **exported** (channel_ext.h) and still called by the C `send_server_burst` (s_serv.c:1302‑1311) → `#[no_mangle]` resolves s_serv's calls to the Rust defs at link. `m_mode`'s decl parse.rs imports from `ircd_sys::bindings` resolves once the C def drops. RED confirmed via exactly three undefined symbols (`m_mode`/`send_channel_modes`/`send_channel_members`); the two statics vanished with their sole callers. After the port the C `modebuf`/`parabuf`/`uparabuf` file‑statics have **zero** C users → reuse the existing module‑private `MODEBUF`/`PARABUF` (P5ee in‑call scratch) + new `UPARABUF`.
  - **Callees, all already resolved:** Rust — `canonize` (P5m), `strtoken` (P1), `clean_channelname`/`find_channel`/`is_chan_op` (P5t), `check_channelmask` (P5u), `channel_modes` (P5w), `change_chan_flag` (P5x), `find_chasing` (P5bb), `check_string`/`make_bei`/`free_bei`/`add_modeid`/`del_modeid` (P5ff), `del_invite` (P5u), `find_uid` (P4), `m_umode` (P5h — the non‑channel MODE target route); C extern — the variadic senders (`sendto_one`/`sendto_match_servs_v`/`sendto_channel_butserv`/`sendto_channel_butone`/`sendto_flag`, all already in the block), `myrand` (P2). New imports: `ircstp` (the `is_rreop`/`is_fake` counters), `m_umode`, `SLink__bindgen_ty_1` (the `chops` union init), `ERR_NEEDMOREPARAMS`/`ERR_RESTRICTED`/`ERR_UNKNOWNMODE`, `RPL_CHANNELMODEIS`/`RPL_UNIQOPIS`/the four `RPL_ENDOF{BAN,EXCEPT,INVITE,REOP}LIST`. New `c_int` consts `MODE_DEL`/`MODE_KEY`/`MODE_LIMIT`/`MODE_EXCEPTION`/`MODE_INVITE`/`MODE_REOPLIST`/`KEYLEN`/`MODE_WPARAS` (the imported `MODE_BAN`/… are bindgen `u32` → cast at use; `MODE_UNIQOP`/`MODE_CHANOP`/`MODE_VOICE` == the `CHFL_*` locals). `USE_SERVICES` off (the `check_services_butone` block skipped); `V29PlusOnly` off (the `opcnt >= MAXMODEPARAMS+1` inner guards compile); `JAPANESE` off (no `jp_valid`).
  - **`chops`/`flags[]` + the tri‑state `whatt`.** `set_mode`'s `static Link chops[MAXMODEPARAMS+3]` (in‑call scratch) → a module‑private `static mut CHOPS: [Link; 6]` (const‑init via the `SLink__bindgen_ty_1{cptr: null}` union); `static int flags[]` (the simple‑mode bit↔char table) → a `const SIMPLE_FLAGS: &[(c_int, u8)]` walked in the exact order. `whatt` (C `u_int`, used as both `MODE_ADD`/`MODE_DEL` and the `1`/`-1`/`0` +/- sign tracker across the emission loops) is kept `c_int` — every value (`0x40000000`/`0x20000000`/`0`/`1`/`-1`) compares identically. `mode->mode` is `u_int` → the working copy `new_` is `c_int` (matching C's `int new`), cast on read/write (all bits < 0x40000, no sign issue). The C switch `break`s (break the switch → continue to `curr++`) became a labeled `'sw` block with `break 'sw`; the `'O'`→`'o'`/`'v'` fall‑through is a `proceed`‑bool computed in an `'ob:` labeled block; the `opcnt` reconstruction's `if (tmplen == -1) break;` (breaks the **for** loop) is `break 'recon`, while the per‑item `MODE_KEY` skips are a nested `'emit_key:` block.
  - **`format!` per user request — N/A, documented in‑module.** The whole cluster builds protocol‑byte strings into the `modebuf`/`parabuf`/`uparabuf` scratch (`*mbuf++`, `strcat`, the `+l` `sprintf("%-15d")`) byte‑for‑byte as C (arbitrary client nick/ban‑mask bytes — a UTF‑8 `String` would misframe), then hands them to the C variadic senders; there is no owned ASCII reply to `format!`.
  - **Faithfulness notes.** `set_mode` tail: the sender cascade `s = IsServer(sptr) ? serv->sid : sptr->user ? user->uid : sptr->name`; `sendto_match_servs_v(chptr, cptr, SV_UID, …mbuf, upbuf)` (the UID‑form params in `upbuf`, name‑form in `pbuf`); the fake‑vs‑real branch (`IsServer(cptr) && !IsServer(sptr) && !ischop` → `sendto_flag(SCH_CHAN, "Fake:…")` + `ircstp->is_fake++`, else `sendto_channel_butserv(…pbuf)`); `return ischop ? count : -count`. The `k` key sanitizer (`,`→`.`, the `>0x7f`/`>0xa0` high‑byte mask, KEYLEN truncation) ported on `*mut u8` exactly. `reop` scheduling (`timeofday + LDELAYCHASETIMELIMIT + myrand()%300` for chanop self‑deop / server `+R`; `+r` from server sets `chptr->reop`) writes channel state (not wire — `myrand` non‑determinism is invisible to L2). parseNUH splits `nick!user@host` in place via `strchr`/`strrchr`, `make_bei`, restores the separators.
  - **Bug caught by the burst S2S test (the inverse paid off).** `aListItem.nick`/`user`/`host` are `*mut c_char` **pointers**, not arrays — the first port took `addr_of!((*al).nick)` (address of the pointer field) and `%s` printed the pointer's bytes as garbage (`:000A MODE #chan +b ``…`). The `golden_s2s_mode` relink‑burst diff caught it immediately; fixed to pass `(*al).nick` directly at all three sites (`send_mode_list`, the `beIR` list query, the reconstruction `nuh`). The simpler L2/outbound tests passed *without* this fix (they never serialize a stored `mlist` entry) — only the burst path re‑emits the list, so the S2S coverage was load‑bearing.
  - **L2 + L2‑S2S are the gates (no fresh L1 — set_mode's state mutation is already L1‑covered via the P5x membership + P5ff ban diffs through it).** New `golden_channel_mode_set` (L2): a chanop voices a second member (`+v bob`) — the relay reaches every member via `sendto_channel_butserv`, observed by bob; NAMES shows `+bob`; the **inverse** `-v bob` → NAMES shows plain `bob` (`change_chan_flag` MODE_DEL clears CHFL_VOICE). The 2‑client harness (the per‑host clone limit is 2, the P5cc finding) makes bob both target and observer. New `golden_s2s_mode` (L2‑S2S, 2 scenarios): **outbound** — a local chanop (the peer ops alice on a remote‑NJOIN'd #chan, since a split local client can't create one) sets `+m` then `+l 5`, and the peer receives the UID‑sourced `:000AAAAAA MODE #chan +m` / `+l 5` (`set_mode`'s `sendto_match_servs_v(SV_UID)` tail, `upbuf` form); **link burst** — a populated #chan, then the peer is dropped and **relinks** (the only way to burst a `#` channel: it can't exist before the single harness peer links), so the second burst replays `:000A NJOIN #chan :000AAAAAA` (`send_channel_members`) + `:000A MODE #chan +tnl 10` / `+b *!*@evil.host` (`send_channel_modes` → `channel_modes` + `send_mode_list`). Regression: `golden_channel_modes`/`ban`/`part` (L2) + `golden_s2s_ban`/`membership` (inbound server MODE through the Rust `set_mode`) stay byte‑identical ref‑C == Rust; full `ircd-golden` + `ircd-testkit` (L1) suites green; `cargo build` 0 warnings; no new canonicalizer masks. **Remaining channel.c (C):** only the **lifecycle group** — `reop_channel`/`collect_channel_garbage`/`setup_server_channels`/`free_channel`/`get_channel` — stays in `channel_link.o`. The channel.c mode + handler clusters are now fully Rust.
