  - **`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`.

- **2026‑06‑03 — P5x (channel.c membership cluster) merged.** `add_user_to_channel` (channel.c:424), `remove_user_from_channel` (:493, exported), and `change_chan_flag` (:564) ported to `ircd-common/src/channel.rs`, extending the existing `channel_link.o` partial port (`‑DPORT_CHANNEL_P5`). **Fifth channel.c sub‑phase** — the `members`/`clist`/`user->channel` list manipulators + the `istat` channel counters. Plan: `docs/superpowers/plans/2026-06-03-p5x-channel-membership.md`.
  - **Cluster shape & the linkage moves.** The three touch **no** shared `buf`/`modebuf`/`parabuf`/`uparabuf` static (unlike the mode/display clusters) — their only remaining‑C edge is the static `free_channel`, which `remove_user_from_channel` calls when the last user leaves. Two `static`s (`add_user_to_channel`, `change_chan_flag`) have **no forward decl** in C (defined before their callers), so guarding their bodies out would leave the C remnant (`setup_server_channels`/`m_join`/`m_njoin`; `set_mode`/`reop_channel`) calling an undeclared symbol → added `extern` forward decls under the guard. `free_channel` is **kept C** but switched `static`→`extern` under `#ifdef PORT_CHANNEL_P5` (forward decl :71 + definition storage class) — a **new variant** of the P5u mechanism (P5u/P5v switched a C static so the *Rust* def wins; here the body stays C and only its linkage opens so the *Rust* caller can reach it). `remove_user_from_channel` is exported and already called from the Rust `s_misc.rs` exit cascade (P5c, extern decl s_misc.rs:65) → resolves to the Rust def once the C def drops. No parse.rs / build.rs edit.
  - **Config findings (verified):** `nm cbuild/channel.o` → `t add_user_to_channel`, `t change_chan_flag`, `T remove_user_from_channel`, `t free_channel`, `t free_bei` (confirms the static/export split). `USE_SERVICES` **off** → the `check_services_butone` calls inside add/remove (channel.c:460‑470, :540‑550) are `#ifdef`'d out (skipped). Active `MODE_ADD = 0x40000000` (struct_def.h:877; the :756‑757 pair is commented out), `MODE_FLAGS = 0x3ffff`, `LDELAYCHASETIMELIMIT = 5400`, `CHFL_UNIQOP/CHANOP/VOICE = 0x1/0x2/0x4`. RED build confirmed exactly the three undefined symbols (free_channel resolved as C).
  - **Faithfulness notes:** `add_user_to_channel` — `chptr->users++ == 0` is the unconditional post‑increment (`was_zero` capture then `+= 1`); the locked‑channel history reset `bzero(&chptr->mode, sizeof(Mode))` → `ptr::write_bytes(addr_of_mut!((*chptr).mode) as *mut u8, 0, size_of::<Mode>())` gated on `*chname != '!'`; the per‑uplink `clist` link found/created on `who->from` then `flags++`. `remove_user_from_channel` — a leaving `CHFL_CHANOP` arms `chptr->reop = timeofday + LDELAYCHASETIMELIMIT + myrand()%300` (the L1 fixture uses non‑chanop members so this non‑deterministic `myrand()` path is **not** exercised — it would diverge Rust's live `myrand` from the oracle's `cref_myrand`); `if (tmp2 && !--tmp2->flags)` predecrement drops the clist link at 0; `--chptr->users <= 0` → the `is_chan`/`is_chanmem`/`is_hchan`/`is_hchanmem` swap + `free_channel`. `change_chan_flag` — mirrors the flag into both the member link and the client link, `~lp->flags & MODE_FLAGS` = `(!lpflags) & MODE_FLAGS` (unary‑not binds tighter, same as C). **`format!` per user request — N/A, documented in‑module:** the cluster builds no reply string (pure list/counter mutation).
  - **Verification.** L1 `channel_membership_diff` zero‑diff vs `cref_remove_user_from_channel`: a 2‑member channel with both members linked into `chptr->members` + each `who->user->channel` + a per‑uplink `clist` entry; removing one member asserts identical surviving `members`/`clist`/`user->channel` chains, `chptr->users`, `joined--`, and the `is_userc`/`is_chanusers` deltas — deliberately keeping `users >= 1` so the last‑user `free_channel` teardown (needs the channel hash + global) is **not** L1‑reached (L2/review‑covered). **Inverse/round‑trip invariant** (`add_remove_readd_roundtrip_is_clean`, the skill's required inverse test): drives the now‑Rust `add_user_to_channel` directly (a C static → no `cref_` oracle) to add two members, remove one and assert it is *actually gone* from `members`/`clist`/`user->channel` with the counters decremented, then re‑add and assert no duplicate node + the `is_userc`/`is_chanusers` counters return **exactly** to the two‑member values (a botched removal passes the positive‑only differential but leaks/double‑counts here). `change_chan_flag` is a C static → no `cref_` oracle → covered by L2. L2 (all byte‑identical ref‑C == Rust, 0 new canonicalizer masks): `golden_channel_part` — (1) **PART** with a *second member* (bob) staying on #chan to observe alice's relayed PART, then a follow‑up NAMES + WHO confirming alice is gone from both membership lists (`remove_user_from_channel`); (2) **MODE +o → NAMES** showing `@bob` (`change_chan_flag` set the flag, `is_chan_op` reads it). `golden_channel` (JOIN) still covers `add_user_to_channel`. **S2S `golden_s2s_membership`** — a remote NJOIN (`m_njoin` C → Rust `add_user_to_channel`; the server‑side `get_channel(&me, CREATE)` creates the federated `#chan` a split lone link won't let a local client create) then a local observer JOINs and sees `@bob`, then a remote PART (`m_part` C → Rust `remove_user_from_channel`) the observer sees + a NAMES no longer listing bob. `cargo build` 0 warnings; full `cargo test` green (41 test binaries incl. the new `channel_membership_diff` L1 + `golden_channel_part`/`golden_s2s_membership` L2). **Split‑mode finding:** in `boot_s2s` a single configured uplink keeps the server permanently split (`IsSplit()`), so a local client **cannot create** a federated `#` channel (m_join:2591 `ERR_UNAVAILRESOURCE`) — but **can JOIN an existing one** (the split guard is inside `if(!chptr)`); the NJOIN server path creates it. **Remaining channel.c (C):** the mode cluster (`m_mode`/`set_mode`/`add_modeid`/`del_modeid`/`send_mode_list`/`make_bei`/`free_bei`/`send_channel_*`), join/access (`can_join`/`get_channel`/`free_channel`/`reop_channel`), part/kick/topic, and display (`names_channel`/`find_chasing`/`check_string`/`m_names`/`m_list`) clusters stay in `channel_link.o`.

- **2026‑06‑03 — P5y (channel.c `m_topic` handler) merged.** `m_topic` (channel.c:3208‑3300 — the TOPIC command: per‑channel query [331/332/333] or set [propagate + 482 reject]) ported to `ircd-common/src/channel.rs`, extending the existing `channel_link.o` partial port (one `#ifndef PORT_CHANNEL_P5` region around the body; the cref oracle keeps the full unguarded `channel.o`). **Sixth channel.c sub‑phase.** Plan: `docs/superpowers/plans/2026-06-03-p5y-channel-m_topic.md`.
  - **The cleanest channel.c handler — no linkage juggling.** `m_topic` is a `msgtab` row (`TOPIC = [m_nop, m_topic, m_topic, m_nop, m_unreg]` → registered client col 1, server col 0/2) and has **no file‑static of its own** (it uses `find_channel`, **not** `get_channel`, so the get_channel linkage problem the rest of channel.c carries doesn't apply). Every callee is already Rust — `canonize` (P5m), `strtoken` (P1), `find_channel` (P5t), `check_channelmask` (P5u), `is_chan_op` (P5t) — or a C variadic sender. So a plain `#ifndef` guard drops the C def and the `m_topic` decl parse.rs already imports from `ircd_sys::bindings` resolves to the Rust def at link. **No extern‑prototype switch, no parse.rs/build.rs edit.** RED confirmed via the sole undefined `m_topic`.
  - **Config (verified `cbuild/config.h`):** `TOPIC_WHO_TIME` **defined** (config.h:522) → the `topic_t`/`topic_nuh` blocks (the 333 RPL_TOPIC_WHO_TIME reply on query, the `topic_nuh = "%s!%s@%s"` build + `topic_t = timeofday` on set) **are compiled and ported**. `USE_SERVICES` **off** → the `check_services_butone(SERVICE_WANT_TOPIC, …)` block not compiled/not ported. `JAPANESE` off (no effect). Numerics: RPL_NOTOPIC=331, RPL_TOPIC=332, RPL_TOPIC_WHO_TIME=333, ERR_NOSUCHCHANNEL=403, ERR_NOTONCHANNEL=442, ERR_NOCHANMODES=477, ERR_CHANOPRIVSNEEDED=482. Fields (bindgen): `topic[256]`, `topic_nuh[92]`, `topic_t` (time_t). New helpers: `is_channel_name` (cid_ok, CHIDLEN=5), `use_modes`, `is_anonymous`, `strncpyzt`; new bindings imports `canonize`/`strtoken`/`cid_ok`/`RPL_NOTOPIC`/`RPL_TOPIC`/`RPL_TOPIC_WHO_TIME`/`ERR_NOCHANMODES`/`ERR_NOSUCHCHANNEL`; new externs `sendto_match_servs`/`sendto_channel_butserv`/`strncpy`.
  - **`format!` per user request — N/A, documented in‑module.** `topic_nuh` (`"%s!%s@%s"` of the setter's arbitrary client `name`/`username`/`host` C‑strings) is a **byte‑builder** (`build_nuh`, mirroring the `add_invite` `who` builder) — not `sprintf`/`format!` (a UTF‑8 `String` would corrupt non‑ASCII bytes; the standing P5c/P5f rule). `topic` is a byte‑precision `strncpyzt`. Every wire reply (the TOPIC echo, 332/333, the rejects) goes straight to a C variadic sender. The 333 `topic_t` (`time_t`) passed to `%lu` is cast to `u_long` (the P5c width rule).
  - **Faithfulness notes:** the `strtoken(&p, parv[1], ",")` per‑target loop with `parv[1]=NULL` advance; the `find_channel` miss does `return penalty` (**not** `continue`); `IsChannelName` reject → 442, `!UseModes` → 477, `!IsMember` → 442, `check_channelmask` → continue; query (`topic==NULL`) → empty topic → 331, else 332 + (if `topic_t > 0`) 333 with `IsAnonymous(chptr) ? "anonymous!anonymous@anonymous." : topic_nuh`; set (`parc > 2`) only if `!(mode & MODE_TOPICLIMIT) || is_chan_op` → strncpyzt topic, build topic_nuh, `topic_t = timeofday`, propagate (`sendto_match_servs` UID‑sourced + `sendto_channel_butserv` name‑sourced), `penalty += 2`; else → 482.
  - **L2 is the gate (no clean L1 data op — m_topic sends wire output via a live channel).** L2 `golden_channel_topic` 2/2 byte‑identical (`-p standalone` so `#chan` can be created): **lifecycle** (query empty → 331; set → echo; query → 332 + 333; **change** → re‑query 332 reflects the *new* value — the round‑trip/inverse invariant a botched set/strncpyzt would break) + **rejects** (442 non‑member, 403 no‑such‑channel, 482 non‑op on +t). **S2S `golden_s2s_topic`** 1/1 byte‑identical (the user‑requested server path): a remote chanop bob (NJOIN'd onto a federated `#chan`) sets the topic → `m_topic` (cptr = peer server, sptr = bob) relays `:bob TOPIC #chan :…` to local member alice via `sendto_channel_butserv` and her query returns 333 carrying **bob's `topic_nuh`** (the remote‑user fields only an S2S link produces); then local alice sets the topic → `sendto_match_servs` propagates `:000AAAAAA TOPIC #chan :…` (UID‑sourced) to the peer. **New canonicalizer mask:** 333 RPL_TOPIC_WHO_TIME's trailing `topic_t` (= `timeofday`) is wall‑clock volatile between the separately‑booted ref‑C/Rust → masked to `<T>`. `m_topic` defined `T` from ircd‑common in the Rust `ircd`; `cargo build` 0 warnings. **Remaining channel.c (C):** the mode cluster (`m_mode`/`set_mode`/`add_modeid`/`del_modeid`/`send_mode_list`/`make_bei`/`free_bei`/`send_channel_*`), join/access (`m_join`/`m_njoin`/`can_join`/`get_channel`/`free_channel`/`reop_channel`), part/kick (`m_part`/`m_kick`/`find_chasing`), and display (`m_names`/`m_list`/`names_channel`/`check_string`) clusters stay in `channel_link.o`.

- **2026‑06‑03 — P5z (channel.c `m_list` handler) merged.** `m_list` (channel.c:3402‑3542 — the LIST command: bare lists all visible channels, or per‑name/`!`‑mask forms) ported to `ircd-common/src/channel.rs`, extending the existing `channel_link.o` partial port (one `#ifndef PORT_CHANNEL_P5` region around the body; the cref oracle keeps the full unguarded `channel.o`). **Seventh channel.c sub‑phase.** Plan: `docs/superpowers/plans/2026-06-03-p5z-channel-m_list.md`.
  - **A second true‑leaf handler (like P5y).** `m_list` is a `msgtab` row (`LIST = [m_list, m_list, m_list, m_list, m_unreg]` → registered client + server) with **no file‑static of its own**. Every callee is already Rust — `canonize` (P5m), `strtoken` (P1), `find_channel` (P5t), `hash_find_channels` (P2), `get_sendq` (P2/class) — or a C extern (`hunt_server` still C; the variadic `sendto_one`). So a plain `#ifndef` guard drops the C def and the `m_list` decl parse.rs already imports resolves to the Rust def at link. **No extern‑prototype switch, no parse.rs/build.rs edit.** RED confirmed via the sole undefined `m_list`.
  - **The `sendto_one` return type.** `m_list` is the first ported caller that needs `sendto_one`'s return value (`rlen += sendto_one(…)` — the remote‑LIST reply‑length throttle vs `CHREPLLEN`). The ircd‑common module‑local extern decls had declared it `-> ()`; since `clashing_extern_declarations` is crate‑wide, **all five** decls (channel/s_user/s_misc/s_service/s_debug) were unified to the true ABI `-> c_int` (the sibling callers ignore the result — a `c_int` isn't `#[must_use]`, so no warning). The cross‑crate bindings decl already returned `c_int`.
  - **Config (verified `cbuild/config.h`):** `LIST_ALIS_NOTE` **defined** (config.h:516) → **both** `#ifdef LIST_ALIS_NOTE` NOTICE blocks compile and are ported (the opening notice when `MyConnect(sptr)` in the bare branch, and the trailing notice when `MyConnect && listedchannels > 24`); the note string is reproduced as a byte‑exact `c"…"` const (incl. the embedded quotes around `/squery alis help`). `CHREPLLEN`=8192 (config.h:363). Numerics: RPL_LIST=322 (`:%s 322 %s %s %d :%s`), RPL_LISTEND=323, ERR_TOOMANYMATCHES=416. New helpers: `secret_channel`/`hidden_channel`/`pub_channel`/`show_channel`/`dbuf_length`; new bindings imports `channel`(as `channel_list`)/`get_sendq`/`hunt_server`/`RPL_LIST`/`RPL_LISTEND`/`ERR_TOOMANYMATCHES`.
  - **`format!` per user request — N/A, documented in‑module.** `m_list` builds **no** reply string of its own — every line goes straight to the C variadic `sendto_one` (the RPL_LIST `chname`/`topic` are arbitrary client bytes). The `maxsendq = (int)((float)get_sendq(sptr,0) * (float)0.9)` truncating double→float→int chain is mirrored as `(get_sendq(sptr,0) as f32 * 0.9f32) as c_int`; the `DBufLength > maxsendq` test mirrors C's unsigned comparison (`maxsendq as u_int`).
  - **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`.
