Proof of concept mechanical port of ircnet/ircd to Rust as part of a bit about C being insecure for network services
0

Configure Feed

Select the types of activity you want to include in your feed.

ircd.rs / PLAN.md
83 kB 135 lines
1# Mechanically rewrite IRCnet ircd 2.11 from C to Rust (strangler fig) 2 3## Context 4 5`ircd.rs` is the IRCnet ircd 2.11 IRC daemon: ~51k LOC of C across `common/`, `ircd/`, and `iauth/`, 6producing 5 binaries (`ircd`, `iauth`, `chkconf`, `ircdwatch`, `mkpasswd`). It is a single‑threaded 7`select()/poll()` server with a table‑driven command dispatcher, a bundled BIND DNS resolver, pervasive 8fixed‑size buffers, and ~230 `strcpy/sprintf`‑family calls — i.e. exactly the memory‑safety profile a 9Rust rewrite is meant to eliminate. There are **no existing tests** (only a clang‑format CI check). 10 11Goal: convert the codebase to Rust **module‑by‑module**, keeping the program building and running at 12every step, with **every important part fully tested** against the original C as a reference oracle. 13 14This plan reflects four locked decisions from the user: 15 161. **Faithful mechanical port now**, then **dedicated idiomatic‑Rust passes at the very end** (after C is gone). ⚠️ **Superseded (2026‑06‑08):** decision 1 originally meant an *in‑place* idiomatic transformation of the mechanical port (the old P9–P12). The project instead forked a **parallel greenfield idiomatic daemon, `leveva`**, tested differentially against the now‑complete mechanical port. So the idiomatic end‑state is reached by *growing `leveva` to parity and deleting the mechanical port*, not by polishing it in place. See the reframed P9–P12 below. 172. **Rust‑driven build from day one**: `fn main()` is Rust; remaining C compiles as a `*-sys` crate; we strangle _inward_. 183. **iauth rewritten first**, standalone greenfield Rust binary speaking the identical pipe protocol. 194. **Full 4‑layer testing** (differential unit, golden, fuzz, conformance) + a **permanent C reference build** in CI. 20 21The leverage point: every `foo.c` already ships `foo_ext.h` (its public `extern` API) — **41 headers / ~417 symbols** that form a ready‑made per‑module C ABI contract. Dispatch is a function‑pointer table (`msgtab[]` in `common/parse.c`). These two facts make the strangler seam tractable. 22 23--- 24 25## The migration model (the seam) 26 27**Workspace.** A cargo workspace at repo root, coexisting with the untouched autoconf tree: 28 29- `ircd-sys/``build.rs` runs `support/configure` to generate `setup.h`/`config.h`, then `cc::Build` compiles the C objects (mirroring `IRCD_COMMON_OBJS`+`IRCD_OBJS` from `support/Makefile.in:144‑149`) **minus** whatever has been ported, plus `bindgen` over a `wrapper.h` that `#include`s `common/struct_def.h` + all `*_ext.h`/`*_def.h`. 30- `ircd-rs/` — the binary crate; `src/main.rs` owns the real `fn main()`. Ported modules live here / in leaf sub‑crates. 31- `iauth-rs/` — standalone greenfield `iauth` binary (no `ircd-sys` dep). 32- `ircd-testkit/` — the L1–L4 harness. `ircd-fuzz/``cargo-fuzz` targets. 33 34**Runnable baseline (day one).** Compile the C `main` (`ircd/ircd.c:766`) as `c_ircd_main` via `cc::Build::define("main", Some("c_ircd_main"))`. Rust `main()` marshals argc/argv and calls it. The product is byte‑for‑byte the C ircd, just entered from Rust. (Also generate `version.c` from `ircd/version.c.SH.in` in `build.rs` and compile it — the C link needs `version.o`.) 35 36**Strangling inward (per module).** To port `foo.c`: read `foo_ext.h` for the exact ABI; write Rust `#[no_mangle] pub extern "C" fn …` (and `#[no_mangle] pub static …` for exported data) matching the **exact** signatures (note: C uses mutable `char*`, not `const`); **remove `foo.c` from `build.rs`'s source list**. Because the `.c` is _dropped_ (not overridden), exactly one definition of each symbol exists at all times → no duplicate‑symbol/LTO games, program always links. Cross‑language LTO stays **off** during migration. CI builds + boots the server after every move. 37 38**Single source of truth for compile defines.** `build.rs` must mirror `Makefile.in` **per‑object** defines (e.g. `-DFNAME_USERLOG=…`, `-DIRCDCONF_PATH=…`, `CLIENT_COMPILE`), and feed the **identical** `-include config.h/setup.h` + `-D` set to `cc::Build`, to `bindgen`, **and** to the reference‑archive build. A divergence here silently corrupts `aClient` layout. Default config to lock: `USE_IAUTH` on, `XLINE` off, `ZIP_LINKS` off, `AFINET=AF_INET6` (so `IN_ADDR` = libc `in6_addr`). 39 40**Non‑ircd binaries are out of strangler scope.** `ircdwatch`, `chkconf`, `mkpasswd` and the `cl*.o` duplicate‑compiled common objects (`-DCLIENT_COMPILE`) stay in the **permanent C reference tree only**; they are never routed through `ircd-sys`. `chkconf` gets a Rust port opportunistically in P6. 41 42--- 43 44## Keystone hazards & how we neutralize them (all verified in‑tree) 45 46| Hazard | Reality (verified) | Neutralization | 47| -------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | 48| **C‑variadic exports** | ~14 in `send_ext.h` (`sendto_one`, `sendto_flag`, …) + `irc_sprintf`; stable Rust _cannot define_ an `extern "C"` variadic fn. Each already has a `va_list` sibling (`vsendto_one`, `irc_vsprintf`). | Keep a **thin C trampoline** per variadic symbol (cc‑compiled): `va_start` → format via `irc_vsprintf` into a `BUFSIZE` buffer → call a **non‑variadic** Rust core `rs_sendto_one_str(to, msg)`. Rust never sees `va_list`. These ~25 trampolines persist through the faithful port and are deleted in **P8** (call sites switch to Rust `format!`). ⇒ "100% Rust" is a P8 goal, not P7. | 49| **`aClient` interior self‑pointers** | `make_client` sets `cptr->name = cptr->namebuf` (`list.c:121,226`); `me` is a by‑value global with `client = &me`. | **Invariant:** never bind/move/copy an `aClient` by value in Rust. All access through stable raw pointers; `name` set via `addr_of_mut!((*c).namebuf)`. | 50| **`CLIENT_REMOTE_SIZE` trick** | Remote clients allocated `offsetof(aClient,count)` bytes; local‑only tail must never be touched when `from != self`. | Port `make_client` to branch on `offset_of!(aClient,count)` vs `size_of::<aClient>()`; build‑test asserts both equal the C `offsetof/sizeof`, **plus** `offset_of!` of every local‑tail field (`count,buffer,sendQ,recvQ,ip,hostp,…`). | 51| **`repr(C)` layout drift** | Layout gated by `USE_IAUTH/XLINE/ZIP_LINKS/INET6/TKLINE/…`. | bindgen + cc share one define set (above); compile‑time `static_assert`s on size/offset; incomplete‑array externs (`local[]`, `tolowertab[]`) get element counts from **bindgen constants** (`MAXCONNECTIONS`) with a C `static_assert` that `sizeof(arr)/sizeof(arr[0])` matches the Rust length. | 52| **Mutable dispatch table** | `msgtab[]` entries are `struct Cmd{handler;count;rcount;bytes;rbytes}` **mutated every dispatch**, read by STATS. | Rust `static mut msgtab: [Message; …]` of the bindgen type, exact field order; handlers point at still‑C `m_*` until ported; L1 test asserts identical `count/bytes` after N dispatches. | 53| **Function‑like macros** | bindgen drops `IsServer`, `MyConnect`, `MyFree`, `MyMalloc`, `DBufLength`, `SetXxx`, … | Hand‑audited `macros.rs` of inline equivalents, each cross‑checked by an L1 test (C macro via tiny accessor vs Rust inline). | 54| **Intrusive lists + manual refcounts** | `next/prev/hnext`, `Link` union, `anUser.refcnt`/`aServer.refcnt`. | **Invariant during faithful port:** lists stay raw‑pointer intrusive lists — **no `Box`/`Drop`/Rust ownership**; refcnt inc/dec byte‑for‑byte as C. Enum‑`Link` and real ownership are **P8 only**. | 55| **`find_*` ownership** | `find_client/uid/server/name/person` live in **`parse.c:144‑417`** (not hash.c), wrapping `hash_find_*`. | Port these with parse (**P4**); `hash_find_*` with hash (**P2**). | 56 57--- 58 59## The 4‑layer test harness (built in P0, used every phase) 60 61- **Permanent C reference build.** The pristine C tree stays in‑repo and buildable for the project's life; CI builds it every run as the oracle. 62- **L1 — differential unit.** Build the reference `.c` into a **separate static archive** whose symbols are bulk‑renamed (`objcopy --redefine-syms` / `--prefix-symbols=cref_`, applied over the **full transitive** symbol set so internal callers follow the rename), exporting `cref_match`, `cref_MyMalloc`, … Link that archive beside the Rust crate in the test binary and assert Rust‑vs‑`cref_` identical outputs (and, for data structures, identical bytes/pointer‑walk order/counters). This is the workhorse for leaf/data/format tiers. 63- **L2 — golden / characterization.** Run reference‑C ircd and the in‑progress Rust ircd side‑by‑side, diff wire output. Determinism is load‑bearing: 64 - **Clock pinning:** `LD_PRELOAD` shim overriding `time()`/`gettimeofday()` to a fixed value in _both_ processes (the loop reads `time(NULL)` directly every iteration — a tune/faketime file is insufficient). 65 - **Output canonicalizer:** mask known‑volatile token positions (WHOISIDLE idle/signon, STATSUPTIME, RPL_TIME/RPL_CREATED, fd‑derived ids). Gate = byte‑identical _after_ clock‑pin + canonicalization. 66 - **Strict serialization:** drive scripted clients **one action at a time with barriers** — hash buckets prepend at head, so WHO/NAMES/LINKS/LIST order follows arrival order. No concurrent clients. 67 - **S2S:** server‑burst diffing uses a **TS‑aware linking peer** (or link‑against‑reference‑C‑and‑observe), not canned replay. 68- **L3 — differential fuzzing.** Feed identical bytes **through the real reassembly path** (`dbuf_getmsg` in `packet.c`, to hit 512‑byte truncation/framing) to C `parse()` vs Rust; diff the **full post‑state** — mutated `aClient` bytes, `ircstp` counters, and selected handler identity — not just `parv[]`. 69- **L4 — conformance.** Rescoped: (1) extract the numeric→format table from `s_err.c` `replies[]` and assert the Rust port reproduces it (an L1 test); (2) curated scripted‑client assertions on numeric **code + arg arity/semantics** from `doc/whox.md`, `doc/whoistls.txt`, `rfc2812.txt`. (There is no machine‑readable RFC oracle; `replies[]` _is_ the truth.) 70 71--- 72 73## Phases 74 75> Through P7, each phase keeps the program building + L2/L4 green ("Drop" = remove the `.c` from `build.rs`). P8 retires the oracle harness after a final green run. 76> 77> **⚠️ Roadmap pivot (2026‑06‑08).** P9–P12 below are **reframed**. The original plan transformed the mechanical port (`ircd-common`) into idiomatic Rust *in place*. Instead the project forked a **greenfield idiomatic daemon, `leveva`** (its own crate: async tokio, `rustls` TLS, KDL config, typed identifier strings — already ~6.3k LOC of foundations). The strangler pattern now repeats one level up: **`ircd-common` (the verified‑faithful mechanical port) is the behavioral oracle for `leveva`**, exactly as the C tree was the oracle for `ircd-common`. `leveva-integration` holds the cross‑crate differential tests. The idiomatic end‑state is reached by growing `leveva` to feature parity, differentially pinned against `ircd-common`, then **deleting the mechanical port** (`ircd-common`/`ircd-rs` + the C‑era `iauth-rs` scaffolding). An oracle only needs to keep building + passing its tests — it does **not** need to be idiomatic, so the in‑place cleanup (old P9) is retired as throwaway work. 78 79| Phase | Modules | Approach & gotchas | Test gate (exit) | 80| ------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | 81| **P0 — Scaffold + baseline + harness 🔶 PARTIAL** | `ircd-sys` (cc+bindgen, `main``c_ircd_main`, `version.c.SH`), `ircd-rs/main.rs`, `ircd-testkit`, `ircd-fuzz`, frozen C reference build | Establish the Rust‑driven build; lock the configure‑define single source of truth; emit layout `static_assert`s; stand up all 4 layers. **Done as P0a (baseline), P0b (bindgen + layout drift-net), P0c (L1 differential harness), P0‑L2 (golden harness `ircd-golden`: reference‑C binary + scripted‑client diff; registration scenario byte‑identical).** **Not yet built:** L3 fuzz (`ircd-fuzz`), L4 conformance, `iauth-rs`. | `cargo build` boots an ircd that links iauth and **diffs byte‑identical (post‑canonicalization) vs reference‑C** across the full scripted suite; layout asserts pass. **(L1 + layout MET; L2/L4 gate pending those layers.)** | 82| **P‑IAUTH — greenfield iauth (parallel, starts at P0) 🔶 ALL 5 MODULES DONE** | `iauth/*``iauth-rs` | Idiomatic Rust allowed (separate process, narrow text ABI). Reimplement event loop (mio), `cldata` fd‑slab, the `<id> <category> <info>` parser, the 8‑fn `aModule` vtable as a trait with 5 impls (`dnsbl/socks/webproxy/rfc931/pipe`); replace dlopen DSM with a static registry from `iauth.conf`. | Drive C‑iauth vs iauth‑rs with an **identical scripted `<id>` sequence** (ids are fixed in input, since `<id>`=ircd fd); byte‑identical replies per category + per‑module fixtures; end‑to‑end ircd+iauth‑rs golden identical. | 83| **P1 — Leaf / pure utils ✅ DONE** | `common/match.c` (+`tolowertab/touppertab/char_atribs`), `dbuf.c`, **pure** subset of `support.c` (`mystrdup/strtoken/myctime/mybasename/inetntop/inetpton/irc_memcmp`). ~~`irc_sprintf.c`~~**deferred to P8** (variadic `va_arg` engine; stable Rust can't consume `va_list`, and it has no post-format core to hand a string to Rust → stays C, no trampoline). `snprintf_append`, `dgets`, `make_isupport`, `ipv6string` also stay C for now. | Faithful; exact `char*` signatures; tables `#[no_mangle] static [u8;256]` (byte-equality asserted); `dbuf` owns `poolsize/freelist`. **`support.c` partial port** via inert `#ifndef PORT_SUPPORT_P1` guards + a `support_link.o` second-compile substituted into the link set; `MyMalloc/MyRealloc/MyFree` + `outofmemory()` **defer to P2**. | **MET:** L1 zero-diff over all corpora (match incl. tables + `MAX_ITERATIONS` cap; dbuf incl. `getmsg` framing + over-poolsize rollback; support incl. 8-case inet round-trip). Rust-driven `ircd` binary links; layout net green; 0 warnings. (L3 fuzz / L2/L4 not built yet — deferred from P0.) | 84| **P2 — Data & lookup ✅ DONE** | `patricia.c`, `class.c`, `whowas.c` (ring; `m_whowas` stays C), `hash.c` (6 tables; `m_hash` stays C), `list.c` (allocators, `make_client`, `svrtop/numclients`) + `MyMalloc`/`MyRealloc` + `outofmemory` | Faithful; preserved intrusive pointers + refcounts (raw‑pointer invariant); replicated `CLIENT_REMOTE_SIZE` branch + hash **iteration order** exactly. **Verified findings:** `ENABLE_CIDR_LIMITS` ON (patricia live, `add_class` carries `cidrlen_s`); `USE_HOSTHASH`+`USE_IPHASH` ON (6 tables); **`MyFree` is a macro** (trio = `MyMalloc`+`MyRealloc`+`outofmemory`). `m_whowas`/`m_hash` kept in C via `whowas_link.o`/`hash_link.o` partial‑ports (`-DPORT_*_P2`). | **MET:** L1 zero‑diff over all corpora (patricia tree walk; class list + CIDR; whowas ring/index + uwas; hash client/channel/uid bucket chains + counters + stored hashv; `make_client` local/remote self‑pointers). Rust‑driven `ircd` binary links with `list.o` dropped; layout net green; 0 warnings. **whowas/class notification (`m_whowas`/`report_classes`) `sendto_*` path deferred to post‑P3 (L2).** | 85| **P3 — Transport & format ✅ DONE** | `s_err.c` (`replies[]`), `s_id.c` (UID/SID/CID), `s_numeric.c` (`do_numeric`), `send.c` **delivery core** (`send_message`/`send_queued`/`flush_*`). **Finding:** the whole `sendto_*`/`esendto_*` family is irreducibly C trampolines (variadic; `vsendpreprep` consumes `va_arg` per‑recipient) → no non‑variadic cores extractable; senders + `sendto_flog`/`logfiles`/`setup_svchans` stay C (move to P7/P8). | Faithful; `replies[]` as Rust static (dumped from the oracle so config‑gated `#if` entries resolve); exact UID/SID base‑N; `send.c` partial‑ported via `send_link.o` (`-DPORT_SEND_P3`, `dead_link` exposed). ZIP/USE_SERVICES/LOG_SERVER_CHANNELS/etc. all OFF. | **MET (L1):** zero‑diff vs `cref_` on `replies[]` (all 1000), `ltoid`/`idtol`/`sid_valid`/`cid_ok`/`check_uid`, `do_numeric` guards, `send_message` sendQ buffering + `send_queued` early returns. **L2 (post‑P3):** `do_numeric`/sender wire broadcasts + `deliver_it` write path. | 86| **P4 — Parser & dispatch ✅ DONE** | `common/parse.c` (tokenizer, `msgtab[]`, all 11 `find_*` wrappers, `getfield`, `m_nop`/`m_nopriv`/`m_unreg`/`m_reg`, `find_sender`/`cancel_clients`/`remove_unknown`) | Faithful; `msgtab` as `#[no_mangle] static mut [Message; 66]` (config‑resolved: TKLINE on, SERVSET/KLINE/SIDTRACE off, MOTD→m_unreg; mutable counters); the ~60 real `m_*` handlers stay C and are referenced as fn pointers; **`parse.o` dropped outright** (every symbol ported — no `parse_link.o`). Rust **calls** the C variadic senders (`sendto_one`/`sendto_flag`/`sendto_serv_butone`) via bindgen decls (the s_numeric.rs pattern). Exact tokenize semantics (512 trunc, leading/trailing colon, empty trailing param, `MPAR` cap). **Deliverable:** per‑handler‑file static‑symbol cluster map (`specs/2026-06-02-p5-handler-cluster-map.md`). | **MET (L1):** zero‑diff vs `cref_` on the `msgtab[]` structure (cmd/min/max per row + row count), `getfield` (delimiter/escape/trailing/empty), `find_mask`/`find_name` fallbacks, and `parse()`'s no‑server paths (IsDead, empty, unknown‑cmd, numeric, ENCAP→m_nop dispatch — return code + `cptr.flags` + `ircstp` deltas). **Full `parse()` post‑state + hash‑populated `find_*` = L3/L2** (need the server fixture). | 87| **P5 — Command handlers (bulk) ✅ DONE (all `m_*` handlers; `s_auth.c` socket/ident/iauth‑pipe I/O → P7)** | `channel.c`, `s_user.c`+`s_auth.c`, `s_serv.c`+`s_service.c`+`s_zip.c` (zlib→flate2, **bit‑exact**), `s_misc.c` (`exit_client`), `s_debug.c`. <br>**Per‑cluster sub‑phase progress log (P5a–P5whowas, 56 clusters): see [`PLAN-P5-progress.md`](PLAN-P5-progress.md).** **`channel.c` (P5hh), `s_serv.c` (P5bbb) and `s_user.c` (P5ccc) are now FULLY ported → all three `.o`s dropped.** The P2‑deferred `m_whowas` handler is also ported (P5whowas) → **`whowas.o` dropped, whowas.c fully Rust**; the P2‑deferred `m_hash` (HAZH) handler is ported (P5hash) → **`hash.o` dropped, the `hash_link.o` partial‑compile gone**. Of the P5 TUs, only `s_auth.c` remains C (no `m_*` handlers; pure socket/ident/iauth‑pipe I/O → deferred to P7/P‑IAUTH). | Faithful, but **cluster‑based, not per‑function**: `channel.c` has 37 file‑statics (`set_mode`, `can_join`, `get_channel`, …); migrate each **connected component over shared statics** as a unit via the `msgtab` fn‑pointer seam. **s_zip.o is empty (ZIP_LINKS off) → drop for free, no port.** | Per‑cluster L2 golden + L4 conformance byte‑identical incl. WHO/WHOIS/WHOX, full server‑burst/netsplit; zlib bytes match. | 88| **P6 — Config & DNS ✅ DONE** | `s_conf.c`+`config_read.c`, `chkconf.c`, `res.c`+`res_comp.c`+`res_init.c`+`res_mkquery.c`. <br>**Per‑cluster sub‑phase progress log (P6a–P6hh): see [`PLAN-P6-progress.md`](PLAN-P6-progress.md)** (one‑line summaries) and [`docs/progress-log/p6.md`](docs/progress-log/p6.md) (full entries). **Outcome:** `s_conf.c` (config grammar + `rehash` + the `conf`/`kconf`/`networkname` globals) and the whole resolver (`res.c` + the `res_comp.c`/`res_init.c`/`res_mkquery.c` leaves) are now FULLY Rust → all `.o`s dropped; **every `msgtab` handler is Rust**; `chkconf.c` is a standalone `chkconf-rs` binary. → **P6 COMPLETE.** | Faithful config grammar + rehash. Resolver: faithful port **or** hickory‑dns behind `res_ext.h`, but golden runs use a **fixture nameserver** for determinism. | Config‑parse + chkconf L1/L3 zero‑diff on fixtures; resolver encode/decode identical; L2/L4 identical with fixture DNS. | 89| **P7 — I/O core + event loop + glue (last) ✅ DONE (all C *logic* ported)** | `s_bsd.c` (select/poll loop, `local[]`, `highest_fd`, `timeofday`, `FdAry`), `packet.c`, `bsd.c`, `ircd.c` (main, io_loop, signals, rehash/restart, tune file), `s_auth.c` (socket/ident/iauth‑pipe I/O, deferred from P5). <br>**Per‑cluster sub‑phase progress log (P7a–P7oo): see [`PLAN-P7-progress.md`](PLAN-P7-progress.md)** (one‑line summaries) and [`docs/progress-log/p7.md`](docs/progress-log/p7.md) (full entries). Ported bottom‑up (leaf‑first), as in P6. **Done:** `bsd.c` (`deliver_it`) + `packet.c` (`dopacket`) dropped outright; `s_bsd.c` fully drained of *logic* (P7d–P7ff: socket/file leaves → fd/connection teardown → listeners → `add_connection`/`connect_server``start_iauth`/`daemonize` → the `read_message` select/poll event loop) — **only its data globals remain**; `s_auth.c` ident/iauth‑pipe I/O ported (P7u–P7y) — **only the variadic `sendto_iauth`/`vsendto_iauth` remain**; `ircd.c` fully drained of *logic* (P7c/P7t/P7gg–P7oo: `ircd_writetune`/`ircd_readtune`/`calculate_preference`/`try_connections`/`check_pings`/`delayed_kills`/`setup_me` + the signal‑handler/`restart`/`server_reboot` cluster + `setup_signals` + `io_loop` + the `bad_command`/`open_debugfile` CLI helpers + **`c_ircd_main` the boot spine** (P7oo) via `ircd_link.o`) — **only its data globals remain**. **ALL C *logic* is now Rust**`ircd_link.o`/`s_bsd_link.o`/`s_auth_link.o`/`send_link.o`/`support_link.o` hold only data globals + the ~25 variadic sender trampolines. | Faithful: keep **single‑threaded select/poll via mio** (not tokio); preserve per‑client state machine; globals become Rust‑owned statics; signals via signal‑hook/nix + atomics. Removes `c_ircd_main`. | All C _logic_ gone (only variadic trampolines remain); full L2+L3+L4 + soak byte‑identical under connect storms, partial reads, sendQ backpressure, netsplit, rehash, restart. | 90| **P8 — Delete the C + retire the oracle harness ✅ DONE** (per-sub-phase: [`PLAN-P8-progress.md`](PLAN-P8-progress.md) + [`docs/progress-log/p8.md`](docs/progress-log/p8.md)) | remaining C (the ~25 variadic sender trampolines + residual data globals) + `ircd-sys` C build + `ircd-golden` (L2) + `ircd-testkit`/`cref_*` archives (L1) + the frozen reference‑C tree | With all C _logic_ already Rust (P7 exit), delete the last C: replace the variadic sender trampolines with a non‑variadic Rust sender API at every call site (faithful `wire!`/`reply_one!`/`format!` first; leveva typed messages adopted alongside the IRC senders), drop the residual data globals via the data‑symbol seam (bindgen `extern` decls resolve to `#[no_mangle]` Rust defs at link), then drop **all** C compilation from `ircd-sys` (no more `cc::Build`), retire the differential harness (`cref_*`/L1 `ircd-testkit`/`ircd-golden` L2 can no longer be built once the oracle is gone), and **migrate the load‑bearing tests into `ircd-common`** as self‑contained Rust tests (own fixtures, no `cref_` oracle). **Progress (P8a–P8r — full entries in the progress logs):** **every variadic sender trampoline is now Rust** (P8a–P8m, incl. the keystones `sendto_one`/`sendto_flag`/`esendto_*`), and the five data‑global `.o`s are dropped outright (`s_auth.o` iauth globals P8n, `send.o` P8o, `s_bsd.o` `local[]`/`highest_fd`/… P8p, `ircd.o` `me`/`client`/timers/… P8q). **P8r dropped the last C‑logic TU `support.o`**`dgets`/`make_isupport` ported to Rust + the `ipv6string`/`minus_one` data globals defined as `#[no_mangle]` statics (`snprintf_append` is dead in Rust — WHOX is field‑by‑field — so it died with the `.o`; `irc_sprintf` is deleted not ported, in P11). **Every ircd C TU is now Rust:** the link set holds only the generated `version.o` + the L1 harness's `ctruth.o`. **P8s migrated all 115 L1 `cref_` differentials into self‑contained `ircd-common` `insta` snapshot tests** (drive only the Rust port via `link_anchor()`, no oracle; soundness via the capture chain) — the suite is green + 0 warnings. **P8t ported the last *generated* C TU `version.c` to Rust** (`ircd-common/src/version.rs`): the data globals `generation`/`creation`/`pass_version`/`infotext`/`isupport` are now `#[no_mangle]` statics via the data‑symbol seam — `generation`/`creation` sourced from crate metadata at build time (`CARGO_PKG_VERSION` + an `ircd-common/build.rs` UTC build stamp), `pass_version`/`infotext` copied verbatim. `version.c.SH` generation + the `version.o` compile/archive are gone from `ircd-sys/build.rs`; `libircd_c.a` now holds **only** the L1 harness `ctruth.o`. (`creation` is now genuinely build‑time volatile, so the golden canonicalizer masks RPL_CREATED 003, matching the existing 371 Birth‑Date rule.) **P8u (FINAL) retired the oracle.** Ran the differential suite a last time green — L1 `ircd-testkit` all pass; L2 `ircd-golden` 86 pass + only the documented reference‑C‑garbage `s_serv_stats` flake (where Rust is the *correct* side) — then **dropped all C compilation** from `ircd-sys/build.rs`: the `make` object build, the `cref_*`/`ctruth.o`/`res.o` recompiles, the `libircd_c.a` archive, and the whole‑archive link + `-lz`/`-lm`/`-lcrypt` (none needed by Rust — `pow` resolves from glibc 2.29+). `build.rs` now only runs `configure` + bindgen over the C *headers* (the struct view `ircd-common` still uses until P9–P12) and expands the install‑path Makefile vars. `ircd-testkit` (L1) + `ircd-golden` (L2) are `exclude`d from the workspace (mothballed, git‑recoverable); the `ctruth.c`/`layout.rs` drift‑net is deleted. **The workspace is now 100% Rust — zero C TUs compiled.** **P8v (literal end of C) deleted the C *source tree* itself + retired `ircd-sys`:** froze the generated `bindings.rs` into a committed `ircd-common/src/bindings.rs` (a self‑alias `extern crate self as ircd_sys;` keeps every `ircd_sys::bindings::*` path resolving, zero src churn) + the install‑path consts as literals, then deleted `ircd-sys`, the mothballed `ircd-testkit`/`ircd-golden` oracle crates, the `iauth-rs` C‑iauth differential, and **all** C source (`common/ ircd/ iauth/ support/ contrib/ cbuild/ configure .clang-format* clangformat.yml`). `ircd-rs` calls `ircd_common::ircd::c_ircd_main` directly. **Not one line of C remains in the repo.** | **MET. Final differential run green; oracle mothballed; `cargo build --workspace` 0 warnings links pure Rust; `cargo test --workspace` 695 pass / 0 fail (P8v; was 698 before deleting the C‑oracle tests); the `ircd-common` snapshot tests carry the reference‑C correctness forward. C source tree deleted — repo is 100% Rust at the file level.** | 91| **P9 — std‑library cleanup ❌ RETIRED (2026‑06‑08)** | — (was: whole Rust tree) | **Skipped by decision — superseded by the `leveva` greenfield.** P9 assumed an *in‑place* idiomatic transformation of the mechanical port (`MyMalloc`/`MyFree``Box`/`Vec`, `dbuf` freelist→owned, raw `libc``std`/`nix`, `[c_char;N]``[u8;N]`). Because the idiomatic end‑state is now a separate greenfield crate (`leveva`), `ircd-common` is throwaway code (the oracle, deleted at parity), so polishing it earns nothing. Nothing was implemented; the row is kept for provenance. | n/a — retired. | 92| **P10 — `leveva` greenfield foundations 🔶 IN PROGRESS** ([`docs/progress-log/p10.md`](docs/progress-log/p10.md)) | new `leveva` crate (lib) + `leveva-integration` differential tests | The idiomatic‑Rust product, built from scratch (not transformed from the port). **Done so far:** RFC 1459 case‑folding (`casemap`); typed identifier strings (`IrcStr`/`IrcString` + `Nick`/`ChanName`/`Uid`/`Sid`/`Cid`/`ServerName`/`UserName`/`HostName` validating newtypes via `ident_newtype!`); `Message`/`MessageBuilder`; the full `Numeric` reply/error enum (184 codes, discriminant = wire code); a generic safe `patricia` trie; glob `matching`; `mode`; KDL `config` (model/parse/password/privilege); `rustls`/`tokio` `tls` endpoint with hot reload; `ident`. The async boot path (`main.rs`) binds listeners + watches certs but currently drops accepted connections — no protocol layer yet. Idiomatic Rust allowed everywhere; pulls real crates (tokio/rustls/kdl/nom/clap). | `cargo test -p leveva` green + clippy clean per module; `leveva-integration` pins each greenfield reimplementation against `ircd-common` (the oracle) within their shared domain (documented divergences asserted explicitly). | 93| **P11 — `leveva` protocol layer → feature parity 🔶 IN PROGRESS** ([`docs/progress-log/p11.md`](docs/progress-log/p11.md)) | `leveva` (client/server state machine + handlers) | The bulk of the remaining work: the connection + registration state machine, the post‑registration command handlers (JOIN/PRIVMSG/MODE/KICK/TOPIC/WHO/WHOIS/…), channels + channel/user modes, the S2S link/burst protocol (UID‑based: UNICK/NJOIN/SAVE/EOB), and `leveva-iauth` (native async auth — dnsbl/socks/webproxy/pipe/ident). Each slice is built idiomatically (owned per‑connection state, typed identifiers end‑to‑end, `Result`/`thiserror`, `format!`/typed builders — **no** `sprintf`/global `buf`), unit‑tested + boot‑golden + proptest‑fuzzed, and **differentially pinned against `ircd-common`** (or `iauth-rs` for auth) wherever a pure oracle entry point exists — structural skeleton + documented divergences, since leveva emits a clean modern burst rather than a byte copy. **Done: 309 slices** (309 = channel mode `+M` (operpeace) — a port of charybdis `extensions/chm_operpeace.c`: a `+M` channel flag that prohibits a **non-operator** from kicking an IRC **operator** out of the channel (the kick-shaped sibling of `+Y` immunity / elemental rank protection); pure fuzz seam `channel::operpeace_blocks(operpeace_set, kicker_is_oper, victim_is_oper)` = faithful charybdis `if (IsOper(source_p)) return;` + `+M && receive_immunity`, with `operpeace_enabled` the read accessor and the shared `command::{operpeace_protects, operpeace_refusal}` resolving live state (victim oper-status from the registry mirror → works for remote victims); enforced in `command/kick.rs` **and** `command/remove.rs` (a blocked kick → `482 :Cannot kick IRC operators from that channel` + a `+s` audit snotice, victim untouched) — **OKICK is unaffected** (oper-gated, always bypasses); new `ChanMode::OperPeace` letter `M` bit `0x2_0000_0000`, **oper-only to set** (like `+P`/`+L` via `permanent_set_allowed`, a non-oper `MODE +M` → 481); divergences = leveva-native (no oracle), refusal reuses `482` not charybdis's `484 ERR_ISCHANSERVICE` (leveva's 484 = ERR_RESTRICTED), oper-bit immunity not per-privilege, mode change shown to all members (no `ONLY_OPERS`); 10 lib units + `operpeace_proptest` (3 props) + `golden_operpeace`, with the isupport/registration asserts + 10 CHANMODES/MYINFO snapshots regenerated, `help/CHANNEL_MODES.md` gains the `+M` row; 308 = roleplay system — a port of charybdis `extensions/m_roleplay.c`: a `+N` (roleplay) channel flag enabling `NPC`/`NPCA`/`SCENE`(+`AMBIANCE` alias)/`FSAY`/`FACTION` commands that speak to the channel from a **fake** nickname (source `<fake>!<author>@npc.fakeuser.invalid`, the body suffixed `(<author>)` and the ident = the real author, so authorship is always recoverable); `NPC`/`NPCA` underline the fake nick, `SCENE` narrates under a fixed `=Scene=`, `FSAY`/`FACTION` give opers a clean nick (underlined NPC fallback for non-opers), and `*A` variants wrap the body as a CTCP `ACTION`; **first widened the full 32-bit channel-flags bitfield to `u64`** (`ChannelModes.flags`/`ChanMode::bit()`/`MemberStatus.bits`/default-channel-modes plumbing — `ExtendLimit` had taken the last `u32` bit) so `Roleplay` could claim `0x1_0000_0000`; pure fuzz seam `roleplay::compose` = faithful charybdis `m_displaymsg` (NICKLEN truncate → `strip_unprintable` dropping control chars `<0x20` + eating mIRC `\x03` colour runs + trimming trailing spaces → `None` if empty → `\x1F`-underline → `(author)` suffix → `\x01ACTION …\x01` 500-cap), the gate ladder reusing `display_name`/`is_member`/`roleplay_enabled`/`can_send` (`461``403``442``573`-`+N`-off→`573`-cant-send→`573`-empty-nick), fanning the fake PRIVMSG to **all** local members incl. the issuer and propagating `:<uid> ENCAP * ROLEPLAY <chan> <display> :<body>` (new `s2s/roleplay.rs`, re-sourced + onward-relayed split-horizon like KNOCK); leveva-native (no oracle), new numeric `573 ERR_ROLEPLAY`, six help pages; 20 command units + 5 s2s units + `roleplay_proptest` (3 props: compose total/never-panics + structural soundness + underline-only-wraps + alphanumeric-always-`Some`) + `golden_roleplay`, with the isupport asserts + 9 CHANMODES/MYINFO snapshots regenerated; 307 = combination extbans `$&` (AND) / `$|` (OR), a port of charybdis `extensions/extb_combi.c`: two extban **operator** types whose data is a comma-separated list of child extbans (`[~]<type>[:<data>]`, no leading `$`), optionally paren-wrapped, with paren-aware comma splitting and backslash escaping — `$&` matches iff **all** children match, `$|` iff **any** does; children may nest (depth cap 5, ≤10 children/node, data ≤ `BANLEN` 195); faithful byte-walk of `eb_combi` in `extban/combi.rs` dispatching to the existing `eval_type` handlers (the C global `recursion_depth` threaded as a `depth` param — leveva runs many connections), preserving the two charybdis subtleties (a child **type** is validated for existence *always*, even when the result is short-circuited, via new `is_known_type`; a child **data** only up to the short-circuit point → combiban validity is subject-dependent); `extban_chars()``"&acjmorsxz|"` so `005` advertises `EXTBAN=$,&acjmorsxz|`; divergences = leveva-native (no oracle), and leveva's `$m` is **usermode** not charybdis's hostmask so host matching inside a combiban uses `$x:<nick!user@host>#*`; 11 `combi` units (AND/OR/negated/nested/paren+escaped-comma + inverses: every malformed shape Invalid, unknown-child-Invalid-even-when-short-circuited, >10-children/depth-6/>BANLEN Invalid) + `extban_combi_proptest` (6 props: total/never-panic over the structural alphabet + nested totality, AND==`all`/OR==`any` model, single-child==bare, De Morgan) + `golden_extban_combi` (`+b $&:~a,o` and `+b $|:o,z` JOIN gates biting only after OPER, + the 005 token), with the `isupport`/`elemental_extban`/`mod.rs` asserts updated and 9 EXTBAN-token snapshots regenerated; `EXTBANS.md` gains the `$&`/`$|` types + a Combination bans section; 306 = user mode `+C` (no-CTCP), a port of charybdis `extensions/umode_noctcp.c`: a self-settable umode that makes a user refuse CTCP queries (other than `ACTION`) addressed to them as a PRIVMSG, replying `531 ERR_CANNOTSENDTOUSER :+C set` — the recipient-side private-message counterpart of the channel `+C` (slice 220), reusing the fuzzed `channel::is_blocked_ctcp` classifier; pure seams `mode::is_noctcp`/`mode::noctcp_user_blocks` (the composite gate decision), enforced home-server-local on BOTH the local `command/message.rs` path and the inbound-S2S `s2s/relay::inbound_message` path (new `noctcp_reject_remote` routing `531` to the sender's uplink, the `+R` 248→249 split precedent) while the bit still propagates (slice-254 "no local-only umodes" — WHOIS shows `+C`); bit `0x80000`, numeric 531, `umode_diff`/`RENDERED`/004-literal/5-snapshot wiring per the user-mode checklist; units (mode seam, command self-settable, message gate + inverses, s2s inbound + inverses) + `golden_noctcp_umode` + `noctcp_umode_proptest` (5 props); 305 = `REHASH` hot-reloads the `webirc {}` trusted-gateway blocks (closing the slice-301 follow-on): the gateway list is carried through the `control::ConfStore` live-config chain (slices 81/83/91/.../303) and read live-or-`ctx` at `session::handle_webirc`, so an operator can add/edit/remove a trusted webchat gateway with no restart; new `control::live_webirc` global, `golden_rehash_webirc` (add→spoof→remove inverse), and the `rehash_conf_proptest` reload model extended with a generated `webirc` set; 304 = route the `user@server` alias target form (charybdis `m_alias` `find_server` branch): an `alias "<n>" { target "user@server" }` now resolves the server part to a **linked remote** server and routes `:<client-uid> PRIVMSG user@server :<text>` to its `uplink` (unknown/our-own name → `440`, then empty text → `412`); pure fuzz seam `alias::remote_privmsg_wire` (totality + clean-input round-trip proptest), plus the S2S `golden_alias` `PEER`/`GONE` cases and unit inverses; 303 = charybdis `alias {}` config blocks (services command aliases): a config `alias "<name>" { target "<t>" }` block makes `<name>` a command that rewrites `<name> <text…>` into `PRIVMSG <target> :<text…>` and delivers it through the message plane (charybdis `modules/m_alias.c` — the real, *faithful* mechanism, found by comparing against a charybdis checkout; the "configurable IDENTIFY nicks" originally scoped does not exist in charybdis, which hardcodes the nicks); pure fuzz seam `alias::{parse_target (split on first `@` → nick vs `user@server`), combined_args, reconstruct}`, new top-level `alias` block (model/parse/from_kdl) held live + REHASH-able through `ConfStore` (`live_aliases`, the slice-256 chain), dispatch hooking the existing `command/mod.rs` 421 fallback (real commands win; case-insensitive match), `command::alias::deliver_to_service_nick` shared core (target must be a present `+S` service else `440 ERR_SERVICESDOWN`; empty text → `412`; else synth `PRIVMSG``message::message`, so a remote service is reached by UID routing), new numeric `440`; **also folded in the slice-302 IDENTIFY faithfulness fix** the comparison surfaced — empty args → `412` (was `461`), absent/non-`+S` agent → `440` (was `401`), both via the shared core; `user@server` parsed-but-not-routed (→`440`, documented follow-on); units (alias seam 7, command 5, identify updated, config parse) + `alias_proptest` (2 props) + extended `rehash_conf_proptest` alias reload + `golden_alias` (S2S +S burst: route + `440` inverse + `421` inverse) + rewritten `golden_identify` (S2S +S NickServ/ChanServ); 302 = `IDENTIFY` services login shortcut (charybdis `extensions/m_identify.c`): the client-facing companion to the services/SASL cluster (296–299) — a registered client types `IDENTIFY [<target>] <password…>` and the server rewrites it into a `PRIVMSG` to a services pseudo-agent (`ChanServ` when the first argument is a channel — leading `#`, else `NickServ`), delivered through the ordinary message plane to a linked services server; pure fuzz seam `identify::rewrite(params) -> Option<Rewrite>` (target picked by `params[0]`'s `#`, verbatim args re-joined after the keyword, empty/empty-first → `461`), `command::identify` synthesizing the PRIVMSG and delegating to `message::message` (so the agent sees `:nick!user@host PRIVMSG NickServ :IDENTIFY …` and an unlinked-services sender gets the plane's own `401`), `HELP IDENTIFY`; leveva-native sugar — hardcoded service nicks, registered-clients-only, no state and no S2S of its own (relay/echo/`account-tag`/`server-time`/cross-server delivery are entirely the message plane's, 296–297); seam+handler units + `golden_identify` (NickServ/ChanServ routing with a PING fence) + `identify_proptest` (rewrite total + contractual); 301 = `WEBIRC` webchat-gateway client IP/host spoofing (charybdis `extensions/m_webirc.c`): a trusted gateway sends pre-registration `WEBIRC <password> <gateway> <hostname> <ip>` so the proxied connection is recorded under the **real** client's host/IP, not the gateway's — every later admission gate (D/K/X-line, config-`ban`, per-class conn caps, `+x` cloak) and `WHOIS` key off the spoofed `self.host`; new `webirc {}` config block (host mask + password) + pure fuzz seam `webirc::{parse_request,spoof_host,authorize}`, `Session::handle_webirc` dispatched phase-independently (CAP/AUTHENTICATE seam), `+s` audit snotice; leveva-native single-host/no-DNS collapse of charybdis's `host`/`sockhost`, boot-read, one-spoof-per-connection (registered → `462`, bad-pw/untrusted/malformed → NOTICE no spoof); `HELP WEBIRC` topic + commented example/dist block; units + `golden_webirc` + `webirc_proptest`; 300 = IRCv3 `draft/read-marker` cap + `MARKREAD <target> [timestamp=<iso>]` command (companion to `draft/chathistory`, slice 274): a client records/queries how far it has read a target; a *set* stores `max(existing,new)` (monotonic — never regresses), echoes the **effective** value, and on a genuine advance fans the `MARKREAD` to the user's **other** capable connections (cross-device read-state sync); a *query* returns the stored `timestamp=…` or `*`; markers owned by the services **account** (synced, survives a reconnect) when logged in else the connection **UID** (dropped on `Session::release`); a statically-advertised **valueless** cap (always available), the command processed regardless of the issuer's cap (the cap gates only the cross-connection fan, per spec); new pure-fuzzed `readmarker.rs` (`parse_set_timestamp` accept-only-`timestamp=<iso>` / `format_value` / monotonic `ReadMarkers::advance` keyed account|UID), `command/markread.rs`, a `read_marker` `ClientCaps` bit + `DRAFT_READ_MARKER` SUPPORTED entry, the `forget_connection` release hook; leveva-native (the C 2.11 oracle predates IRCv3 — no differential); units (parse accept-only-`timestamp=` + INVERSE junk/`*`/empty, advance monotonic-first-set + INVERSE older/equal no-move, account-vs-UID owner isolation, `forget_connection` drops-only-that-uid + INVERSE account+sibling survive; command NEED_MORE_PARAMS/query-`*`/effective-echo/INVALID_PARAMS-changes-nothing/older-no-regress), `golden_markread` (CAP LS advertises + single-conn set/query round-trip + INVERSE older-ignored + cross-device push to a capable same-account sibling + INVERSE uncapped-same-account gets nothing), `readmarker_proptest` (2 props: `parse_set_timestamp` total + accepted-input round-trips, `advance` keeps-the-max order-independently + idempotent); 299 = learn the SASL mechanism list from the services agent (`ENCAP * MECHLIST`): the atheme-arc follow-on to 296/297/298 — leveva had hardcoded `sasl=PLAIN,EXTERNAL`; now a linked SASL services agent announces its mechanisms via `:<svcSID> ENCAP * MECHLIST :<comma-list>`, leveva learns + advertises **that** list (client-facing `sasl=` cap value + `908 RPL_SASLMECHS`) and relays whichever **advertised** mechanism a client picks — faithful charybdis **transparent relay** (leveva a dumb relay, special-casing only `EXTERNAL` for the CertFP; an unadvertised mechanism → `908`+`904`, never relayed — strictly safer than the old hardcoded gate). New pure fuzzed `sasl::parse_mechlist` (comma-split/trim/`[A-Za-z0-9._-]{1,20}`/uppercase/dedupe; `None` on all-junk → caller keeps prior list), `Mechanism` reworked `{Plain,External,Other(Box<str>)}` with `from_advertised`/`name`/`is_external` (was `from_token`/`as_str`), `PeerLinks` gained a learned-mechlist store (cleared when the last `caps.sasl` peer unlinks), new `s2s/mechlist.rs` dispatched in `forward.rs`'s interpret-and-onward-relay block (broadcast, like `SU`), `cap.rs` `apply`/`ls`/`req` `sasl_available: bool``sasl_mechs: Option<&str>`, atheme `leveva_mechlist_sts` + `mechlist_sts` wired; leveva-native (no oracle); `docs/s2s.md` MECHLIST subsection; units (`from_advertised` accept-only-advertised + INVERSE, `parse_mechlist` normalize/dedupe + INVERSE all-junk, mechlist store learned + INVERSE malformed-keeps-prior, links learned + cleared-on-unlink + INVERSE non-sasl-unlink-keeps, cap `sasl=<announced>` + INVERSE default), `golden_sasl` advertised-list+transparent-relay + INVERSE unadvertised-`908`/`904`, `sasl_mechlist_proptest` (2 props: never-panics/valid-tokens + idempotent); 298 = apply the SVSLOGIN-assigned identity (services vhost): the atheme-arc follow-on to 296/297 — an inbound `ENCAP <us> SVSLOGIN <cliUID> <nick> <user> <host> <account>` previously honored only `<account>`, with `<nick>`/`<user>`/`<host>` parsed but used merely cosmetically in the `900`; now the non-`*` `<user>`/`<host>` are applied as a **services vhost** on SASL login (HostServ-vhost-on-login). New pure fuzzed parser `sasl::SvsIdentity::from_fields` (`*`/empty/IRC-invalid → unchanged via `UserName`/`HostName::try_from`), `SaslEntry.identity` + `SaslSessions::{set_identity,identity}`, `remove` now returns `SaslResolution {account, identity}`; `apply_encap_svslogin` stashes the identity; `finalize_registration` drains the resolution before the cloak block and commits the vhost (overriding auto-cloak) as a **display** identity — after every host-based admission gate ran on the real `user@host`, before `local_introduce` so the `UNICK`/welcome carry it, `orighost` kept as the real connect host; the `<nick>` field is deliberately **not** applied (no pre-introduction rename primitive; real flows send `*`; SANICK forces a nick); leveva-native (no oracle); `docs/s2s.md` + atheme `leveva_svslogin_sts` comment updated; units (`SvsIdentity` filter + sessions stash/peek/drain + s2s stash + INVERSE all-`*`), `golden_sasl` vhost-applied + INVERSE star-keeps-real-host, `svslogin_identity_proptest` (2 props); 297 = emit UID targets for user messages (TS6-consistent wire): the follow-on to 296 — leveva now **emits** the recipient's **UID** (not the typed nick) in the target slot of user-directed S2S messages (PRIVMSG/NOTICE/TAGMSG), removing the source-is-UID/target-is-nick asymmetry so the wire is fully UID-consistent like TS6 (a deliberate, maintainer-approved protocol divergence); flipped two outbound emit call sites (`command/message.rs` `route_message_to_remote`, `command/tagmsg.rs` `route_tagmsg_to_remote`) to pass `uid.as_str()`, generic helpers unchanged; receive side already UID-ready (296 `resolve_user_target` + `nick_of` final-hop render + verbatim onward relay); channel/opmod/`$$`/`$#` mask targets, local echo, and client-facing nick rendering all unchanged (clients never see a raw UID); `docs/s2s.md` updated; built via an ultracode discover→implement→verify workflow, helper/command/golden tests + snapshots flipped to UID, live-verified leveva emits `:0LVAAAAAA PRIVMSG 0ATHAAAAG :HELP` while the client still sees `NOTICE probe`; 296 = UID-addressed S2S target delivery + atheme services protocol module: shipped an atheme `protocol/leveva` module (`contrib/atheme/leveva.c` — atheme's ircnet burst skeleton + ts6 ENCAP grafts; links live, services burst, NickServ account login via `ENCAP * SU`, SASL relay) which surfaced a **critical protocol bug** — leveva's inbound S2S handlers resolved a user *target* via `registry.uid_of` (**nick-only**), so a peer/services package addressing a local client by **UID** (`:0ATH… NOTICE 0LVA… :…`) was silently **dropped**; fix = one `pub(crate) resolve_user_target` (nick-first, then `Uid::try_from` accepted only when it names a known user) routed through `inbound_message`/`inbound_tagmsg`/`inbound_numeric`/`apply_encap_redact`/`inbound_kill` (services **KILL by UID**), each keeping its `is_local_uid` guard so a remote UID is reached only by the onward relay; UID-addressed delivery now renders the client's display nick; `inbound_numeric` tightened to drop an unknown UID; built via an ultracode discover→implement→verify workflow with 15 TDD tests (inverse invariants) + live-verified `/msg NickServ HELP` returns; 295 = CYCLE: charybdis-style self-only channel refresh (`extensions/m_cycle.c`) — `CYCLE <channel>` parts and rejoins a channel **without races**, the client-convenience sibling of `PART`/`JOIN`; the load-bearing subtlety is that the real `m_cycle` does **not** leave server-side — it only sends `:source PART <chan> :Cycling`, `:source JOIN <chan>` and a fresh `353`/`366` NAMES list **to the issuing client**, changing nothing else, so the user keeps op/voice and place and cannot be locked out by `+i`/`+k`/`+l` or lose a glare (the whole point); **no new `Channels` seam** (CYCLE never mutates — it composes the read-only `display_name`/`is_member`/`names_query` accessors, the fuzz seam being the dispatch handler's observational purity), `command/cycle.rs` per-channel gate ladder (`461` no param → `403` nonexistent → `442` not-a-member → success), existence-before-membership, success honours the issuer's `multi-prefix`/`userhost-in-names` caps in the re-sent NAMES; **planning correction** — the slice-selection preview mis-described CYCLE as "re-announced to members", but a member-visible cycle would drop the cycler's ops, could destroy a sole-member channel, and would desync linked peers, so the faithful self-only behaviour shipped; divergences = leveva-native (no oracle — 2.11 has no CYCLE), self-only/observationally-pure, fixed `:Cycling` reason, local-only; `CYCLE.md` help + COMMANDS allowlist + index.md channel-commands line; 7 `command/cycle` units + `cycle_proptest` (2 props: `cycle_is_observationally_pure` reply-shape-model + state-byte-identical-before/after, `arbitrary_args_never_panic`) + `golden_cycle`; 294 = OKICK: charybdis-style operator force-kick (`extensions/m_okick.c`) — `OKICK <channel> <user> [:comment]` forcibly removes a member **without** the oper being a chanop or even a member of the channel, the oper-override sibling of `KICK` (chanop-gated) and `REMOVE` (chanop force-*part*, slice 290) and the kick-shaped member of the charybdis operator channel-intervention family (`SAJOIN`/`SAPART`/`SANICK`/`SAMODE`/`OJOIN`/`OPME`/`REMOVE`); new atomic seam `Channels::okick(name, victim) -> OkickOutcome` (single-lock, **no** chanop/membership/rank check — the override — the fuzz seam: channel-missing→`NoSuchChannel` / victim-not-member→`NotInChannel` / victim-`+Y``OfficialBusiness` / else remove→`Kicked{display,pre-removal-audience}`, deleting an emptied channel), `command/okick.rs` gate ladder (non-oper `481` → no/one-arg `461` → unknown nick `401``403` ghost channel → `441` victim-off-channel → `482` `+Y` victim → success), on success emits a **server-sourced** `:<server> KICK <chan> <victim> :<comment>` echoed to the oper (always — they need not be a member; excluded from the audience fan to avoid a duplicate) + fanned to all local members incl. the victim, relays server-sourced via new `s2s::relay::server_kick` (`:<our_sid> KICK <chan> <victim_uid>`, symmetric with the SID-prefix-aware `inbound_kick`), posts a `+s` audit snomask `<oper> used OKICK on <victim> in <chan>` (comment defaults to oper nick); divergences = leveva-native (no oracle), plain oper-bit (not a per-command privilege), **`+Y` immunity respected** (vs charybdis raw removal), oper always gets the echo even off-channel, `+s` audit not `+w` wallops (SA*/OJOIN/OPME precedent), local-only; `OKICK.md` help + COMMANDS allowlist + index.md operator line; 4 `channel::okick` units + 5 command units + `okick_proptest` (2 props: outcome-matches-state incl. rank-never-blocks + `arbitrary_args_never_panic`) + `golden_okick`; 293 = OLIST: charybdis-style operator `LIST` (`extensions/m_olist.c`) that bypasses the secret (`+s`)/private (`+p`) channel-hiding so an oper enumerates **every** channel — the read-only sibling of `LIST` and latest member of the operator diagnostic family (TESTLINE/TESTMASK/MASKTRACE/CHANTRACE/FINDFORWARDS/OPME); two new visibility-bypass `Channels` seams (`list_all` full folded sweep / `list_one_any` named-incl-hidden, the fuzz seam), `command/olist.rs` plain oper-bit gate (non-oper `481` before any sweep) emitting the same `321`/`322`/`323` numerics as LIST (no-arg → all incl. `+s`/`+p`; named → that channel revealed-or-skipped; no ELIST conditions, faithful to `mo_olist`); divergences = leveva-native (no oracle), plain oper-bit (not a per-command privilege), no `+s` audit snomask (read-only diagnostic family precedent), local-only; `OLIST.md` help + COMMANDS allowlist + index.md operator line; `list_all`/`list_one_any` units + 6 command units + `olist_proptest` (2 props: full-set-and-superset model equality + `arbitrary_args_never_panic`) + `golden_olist`; 292 = OPME: charybdis-style operator self-op of an *opless* channel (`extensions/m_opme.c`) — `OPME <channel>` grants the requesting operator `+o` **only** when the channel currently has no operators, the recovery tool for a channel whose last op left and the constrained, safety-gated sibling of `SAMODE +o`; completes the charybdis oper channel family (`SAJOIN`/`SAPART`/`SANICK`/`SAMODE`/`OJOIN`) with the recovery verb; new atomic seam `Channels::opme(name, uid) -> OpmeOutcome` (single-lock channel-missing→`NoSuchChannel` / non-member→`NotOnChannel` / has-an-op→`NotOpless` / else grant `+o``Opped`, the fuzz seam, no persistence — member status never persists), `command/opme.rs` gate ladder (non-oper `481` → no-arg `461``403`/`442`/not-opless `NOTICE`/success), on success broadcasts `:<server> MODE <chan> +o <nick>` to local members + relays server-sourced via `s2s::relay::server_channel_mode` + posts a `+s` audit snomask `<oper> used OPME on <chan>`; divergences = leveva-native (no oracle), membership-checked-before-opless (vs charybdis opless-first, so a non-member never learns op state), `+s` audit snomask not `+w` wallops (SA*/OJOIN family precedent), local-only; `OPME.md` help + COMMANDS allowlist + index.md operator line; `channel::opme` unit + 4 command units + `opme_proptest` (2 props) + `golden_opme`; 291 = FINDFORWARDS: charybdis-style reverse `+f` lookup (`extensions/m_findforwards.c`) — `FINDFORWARDS <channel>` lists every channel whose `+f` forward target (slice 242) is `<channel>`, the companion to the `+f` forwarding family (where `+f` points a *source* at a *target*, FINDFORWARDS asks the target "who points at me?"); new pure read seam `Channels::forwards_to(target)` (folds + scans the channel table, returns matching display names folded-sorted — the fuzz seam), `command/findforwards.rs` with the trace/ban-family gate ordering (`461` no-arg first → `403` nonexistent channel → `442` non-member-non-oper, the oper-bit bypass replacing charybdis's `IsOperSpy` à la CHANTRACE), output as charybdis-faithful `NOTICE` lines (chunked space-separated forwarder list + `End of forwards for <chan>`, or `No channels forward to <chan>` when empty) **not** numerics, `FINDFORWARDS.md` help + COMMANDS allowlist + index.md; leveva-native (no oracle — 2.11 has no FINDFORWARDS), local-only (a read of this server's channel table, mirroring charybdis's per-server `+f` state); `forwards_to` unit + 8 command units + `findforwards_proptest` (2 props: `forwards_to_is_exactly_the_pointing_channels` model set-equality + `arbitrary_args_never_panic`) + `golden_findforwards`; 290 = REMOVE: charybdis-style chanop force-part (`extensions/m_remove.c`) — a channel operator forcibly removes a member who, on the wire, appears to **PART** rather than be **KICK**ed (so client auto-rejoin-on-kick scripts don't fire); `command/remove.rs` reuses `Channels::kick_gate`/`kick` verbatim so it **inherits every KICK rule** (`403`/`442`/`482` chanop gate, `401`/`441`, elemental rank protection, `+Y` official-join immunity, cross-product, `461`), but emits `:victim!user@host PART <chan> :requested by <remover> [(<reason>)]` (prefix = the **victim's** own mask via `Registry::record_of`, works local+remote) echoed to the remover + delivered to all local members incl. the victim, propagated S2S as the victim's PART (`s2s::relay::local_part`) and recorded to chat-history as a PART; a normal chanop command (no oper privilege), the sibling of KICK/the SA*-force family; leveva-native (the C oracle has no REMOVE) → no differential, the "requested by" wording is leveva's; `REMOVE.md` help + COMMANDS allowlist + index.md; `golden_remove` + `remove_proptest` (2 props); 289 = XLINE/UNXLINE: charybdis realname (gecos) operator ban — the realname sibling of K-line/D-line, completing the runtime ban quartet **K/D/X/RESV**; bolt-for-bolt the DLINE plane (slice 284) with a single-token gecos matcher (`matching::matches`) instead of CIDR — `leveva::xline::{Xline,XlineStore}` (SQLite write-through + boot reload), `command/xline.rs` (461→`OperPrivilege::Xline` 481, reap local matching realnames with 465+ERROR), `s2s/xline.rs` `ENCAP * XLINE/UNXLINE` propagation + `xline_burst` re-assertion, registration X-line gate in `finalize_registration` after the K-line/config-`ban` gates, `STATS x``247 RPL_STATSXLINE`; **lights up the dead `xline_exempt` flag** end-to-end (`Admission`/`ClientRecord.xline_exempt` + `set_xline_exempt`, the dedicated exemption distinct from `kline-exempt`); divergences = single-token-glob mask (vs charybdis spaced gecos) + dedicated xline-exempt + leveva-native no-differential; `golden_xline` + `xline_proptest` (6 props); 288 = TESTMASK: `TESTMASK <[nick!]user@host> [<gecos>]` — a charybdis-style read-only operator diagnostic that **counts** how many connected clients match a hostmask (and optional realname glob), split into local vs remote; the population sibling of TESTLINE (285, which reports *bans*), together completing charybdis's `test*` diagnostic family. `461` param gate before the plain oper-bit gate (`481`); a malformed mask (no `@`, or empty user/host) → `NOTICE :Invalid parameters` (faithful, not a numeric); a registry sweep glob-matches `nick`/`user`/`host`(+`orighost`)/`gecos` (nick & gecos default `*`) and tallies `is_local_uid` → one `727 RPL_TESTMASKGECOS` `<lcount> <gcount> <nick>!<user>@<host> <gecos> :Local/remote clients match`; new numeric 727 (charybdis's 724 is dead — no format/caller), pure `parse_mask` fuzz seam, `TESTMASK.md` help; leveva-native, local-only (no S2S, mirroring charybdis); confirmed against cloned charybdis `m_testmask.c`/`messages.h`; 287 = CHANTRACE: `CHANTRACE <#channel>` — the channel-scoped sibling of TRACE (26)/ETRACE/MASKTRACE (286), completing the trace quartet: reports every member of a named channel in ETRACE's extended `708 RPL_ETRACEFULL` column format, folded-nick order, closing `262 RPL_TRACEEND`; gate is `461` (missing channel) → `403` (no such channel, existence before membership) → `442` unless the requester is a member **or** holds `OperPrivilege::Trace` (the operspy-equivalent bypass; leveva has no operspy `!` prefix); reuses `etrace::etrace_line`, no new numeric; `CHANTRACE.md` help; leveva-native (2.11 has no CHANTRACE), reports the **full roster incl. remote members** (unlike local-only ETRACE/MASKTRACE) but no S2S propagation, no IP-hiding (host==host, no separate IP column); 286 = MASKTRACE: `MASKTRACE <nick!user@host mask> [<gecos mask>]` — a charybdis-style mask-filtered extended trace, the sibling of TRACE (26) and ETRACE: reports every local client whose `nick!user@host` glob-matches (and optionally whose realname matches a 2nd gecos glob) in ETRACE's extended column format; `461` param gate before the `OperPrivilege::Trace` oper gate (`481`), one reused `708 RPL_ETRACEFULL` line per match in folded-nick order via `matching::HostMask` + the promoted `etrace::etrace_line`, closing with `262 RPL_TRACEEND` (no new numeric); `MASKTRACE.md` help; leveva-native (2.11 has no MASKTRACE), local-only (no S2S); 285 = TESTLINE: `TESTLINE <[nick!]user@host | ip/cidr | nick | #channel>` — a charybdis-style **read-only** operator diagnostic, the capstone of the ban family (RESV 272 / TKLINE 281 / DLINE 284): it reports which **active** ban would match a mask, laying/lifting nothing. Param gate (`461`) then a plain oper-bit gate (any oper, no per-ban privilege → `481`); a pure `probe()` classifier routes by mask shape (`user@host`→D-line-then-K-line, `#channel`→channel RESV, bare token→D-line-as-IP-then-nick-RESV) and reports the first hit in priority **D > K > R** as `725 RPL_TESTLINE` (`<type K/D/R> <minutes-remaining, 0 for permanent RESV> <ban-mask> :<reason>`), else `726 RPL_NOTESTLINE`; expiry honoured via `find_active`; new numerics 725/726, `TESTLINE.md` help; leveva-native (no oracle — 2.11 has no TESTLINE), single-best-match, no I-line arm, **local-only** (no S2S, mirroring charybdis); 284 = D-lines: `DLINE <duration> <ip/cidr> [:reason]` / `UNDLINE <ip/cidr>` — charybdis-style IP-level operator bans, the IP/CIDR sibling of the temporary K-line family (281–283); new `DlineStore` (mirrors `KlineStore`, CIDR-aware `host_component_matches`, same `database {}` SQLite write-through + boot reload), `OperPrivilege::Dline` (bit `0x400000`), registration gate **before** the K-line gate matching the pre-cloak connect IP (`465`+`ERROR`+REJ snomask, exemption reuses `kline-exempt`), local reap by `orighost`, S2S `ENCAP * DLINE`/`UNDLINE` propagation + burst re-assertion (slice-281 KLINE shape), `STATS d``250 RPL_STATSDLINE`, `DLINE.md`/`UNDLINE.md` help; leveva-native, no oracle differential; 283 = SQLite persistence for permanent (`+P`) channels — a `+P` channel (slice 244, survives empty) now also survives a **restart**, persisting to the same `database {}` SQLite file that backs chat-history (274) and K-lines/reservations (282): its identity + `created_at` TS + full `ChannelModes` + topic write through to a `channel` table (JSON `modes`/`topic` columns, the serde codec chat-history uses) on every durable mutation, via the single `Channels::reconcile_persist` chokepoint (upsert when `+P`, **delete on `-P`**) hooked into all eight durable-state mutators — `apply_modes`/`apply_modes_oper`/`apply_modes_as_server` (MODE incl. the `+P`/`-P` toggle, key, limit, all `+b/+e/+I/+R` lists), `reset_flag_state`/`wipe_list_masks` (CHANTS-merge), `set_topic`/`set_topic_as_server` (TOPIC), and `create_permanent_from_burst` (an empty `+P` channel learned from a peer burst persists so it outlives *this* server's restart too, mirroring 282's inbound-S2S persist); recreated **empty** at boot (members/INVITE-tokens/join-throttle counters never persist — an empty `+P` channel is exactly what 244/269 make valid), with `created_at` carried so TS6 merge arbitration is stable post-reboot; a `persisted: HashSet` of folded keys makes a `reconcile_persist` on a never-persisted (non-`+P`) channel a pure in-memory miss — **no disk write per MODE on an ordinary channel** — and the two leaf locks (`db`/`persisted`) are never held simultaneously so there is no lock-order hazard with the held `by_name` guard; a `new()`/no-`database{}` table stays in-memory only (every persistence path a no-op); `ChannelModes`/`Topic` gained `#[derive(Serialize, Deserialize)]`; `from_config` opens the table against `cfg.database.path` (log + fall back to in-memory on error, never fatal), exactly the 282 klines/resvs shape; 282 = SQLite persistence for K-lines + reservations — operator-laid `TKLINE` bans and `RESV` reservations (lost on restart before) now persist to the **same `database {}` SQLite file** that backs chat-history (slice 274), so they survive a reboot: each `KlineStore`/`ResvStore` gains an optional write-through connection (`open(path)` creates a `kline`/`resv` table + loads existing rows; `add`/`remove`/`prune` mirror to it; a `new()`/no-`database{}` store stays in-memory only, every persistence path a no-op), the in-memory `Vec` staying the hot-path authority; K-lines drop `expires <= now` rows on load + prune; only **runtime** (`conf == false`) reservations persist (config `resv {}` re-derives from `ircd.kdl`, so `seed_config` bypasses persistence and a `REHASH` never drops a learned reservation); `COLLATE NOCASE` primary keys make a case-differing re-add upsert not duplicate; open/write errors log and fall back to in-memory (never fatal); inbound S2S `apply_encap_kline`/`apply_encap_resv` persist what a server learns from the network too (all via `add`); 281 = S2S `KLINE`/`UNKLINE` propagation — the K-line analogue of slice 280: a `TKLINE`/`UNTKLINE` (leveva's temporary K-line family, there is no separate permanent `KLINE`) now propagates network-wide as `ENCAP * KLINE <duration> <user> <host> :<reason>`/`ENCAP * UNKLINE <user> <host>` (server-prefixed, leveva-native, the **remaining** seconds on the wire so each server re-clocks `expires = now + duration` against its own clock — no absolute-timestamp skew), wired into `command/tkline.rs` after the param/privilege/format gates; every server applies the ban to its own `KlineStore` (gating future registrations) **and reaps its own local matching clients** via the now-`is_local_uid`-guarded `reap_matching` helper (which also fixed a latent over-reap of remote users in the local `TKLINE`); `kline-exempt` clients are spared; `UNKLINE` lifts the gate only (never un-kills) and propagates unconditionally; re-asserted to a freshly-linked peer by `s2s/burst.rs::kline_burst` (active bans only, with remaining time; expired bursts nothing); new `s2s/kline.rs` modelled on `s2s/resv.rs`, ENCAP dispatch in `s2s/forward.rs`; 280 = S2S `RESV`/`UNRESV` propagation — an operator-laid nick/channel reservation (and its lift) now propagates network-wide as `ENCAP * RESV`/`UNRESV` (server-prefixed, kind re-derived from the mask prefix on inbound apply), wired into `command/resv.rs` after the param/privilege gates (a `461`/`481` reject propagates nothing; `UNRESV` propagates unconditionally), applied + split-horizon-relayed by `s2s/forward.rs`, and re-asserted to a freshly-linked peer via `s2s/burst.rs::resv_burst` (runtime `conf == false` reservations only — config `resv {}` blocks stay per-server local policy); closes the slice-272/273 S2S-propagation follow-on, mirroring the METADATA/KNOCK ENCAP carriers; 279 = remote-originated JOIN/PART/QUIT membership events are now recorded into chat-history — the membership-event parallel to slice 276 (which did PRIVMSG/NOTICE): a **live** `NJOIN` (`s2s/njoin.rs`, never a burst — bursts are bulk state-sync), a bare `JOIN`/`PART` (`s2s/relay.rs`), and a **discrete** remote `QUIT`/`KILL` (`s2s/squit.rs::quit_one_remote_user`, *not* a netsplit mass teardown) each record an event keyed by a locally-minted msgid + clock, gated on `has_local_member` (only a local member can `CHATHISTORY` it; a pure hub stores nothing); an unmirrored `NJOIN`-only ghost has no mask so its QUIT is skipped; replay/query side unchanged (slice 274 already gates these behind `draft/event-playback`); 278 = `+R` auto-reop is now enforced for **local** members only — the `enforce_reop` chokepoint gained an explicit `is_local_uid` filter, so a `+R` mask matching a `UNICK`-introduced remote user's `nick!user@host` no longer wrongly ops it locally and relays a `+o` we have no authority to emit (the locality guarantee previously rested on the false premise that remote members lack a registry record); 277 = netsplit batches are no longer duplicated to `batch`-capable clients under concurrent teardown — when a dying peer is detected on two paths at once (abrupt socket drop + a sibling's inbound `SQUIT`, in separate tasks), the `NJOIN`-only ghost-member sweep raced (`co_members` then `remove_everywhere`, no single-shot gate) and double-relayed a casualty's `QUIT`; new atomic `Channels::detach_member` fuses recipient-gather + removal in one critical section so only the first teardown to reach a member relays it (the loser gets `None`), mirroring the already-atomic `remove_behind_sid` mirror drain; new `s2s_netsplit_concurrency_proptest` fuzz; 276 = remote-originated channel messages are now recorded into chat-history — the S2S relay's inbound channel fan (`s2s::relay::inbound_message`) records the delivered line on this server too, keyed by the carried network msgid (slice 130) + `@time` (slice 134), gated on this server having a local member of the channel (only a local member can `CHATHISTORY` it; no whole-network accumulation on a hub); the `+z` op-redirect/non-channel branches never record, mirroring the local plane; closes the slice-274 gap where `CHATHISTORY` held only locally-sent lines; 275 = IRCv3 `message-redaction` now deletes from chat-history — an accepted channel `REDACT <chan> <msgid>` deletes the slice-274 stored line (`History::delete_by_msgid`, keyed `(target, msgid)`) so `CHATHISTORY` can never replay it; wired in both the local `CanSend::Ok` path and inbound `ENCAP * REDACT`, per-server; mirrors recording — a `+z` op-moderated/"shown to ops" attempt is never recorded so never deleted; authorization stays a stateless relay; 274 = IRCv3 `draft/chathistory` — SQLite-backed (`rusqlite`, bundled) channel message history behind an optional `ircd.kdl` `database {}` block; all channels auto-enrolled (PRIVMSG/NOTICE + JOIN/PART/QUIT recorded at the delivery plane), joining clients auto-replayed `replay-lines` recent lines unless they negotiated `draft/chathistory` (then exempt, per spec), full `CHATHISTORY LATEST/BEFORE/AFTER/BETWEEN/AROUND/TARGETS` (timestamp=/msgid= selectors) framed as a `chathistory` batch, `draft/chathistory`+`draft/event-playback` dynamic caps + `CHATHISTORY`/`MSGREFTYPES` ISUPPORT, age-based `retention-days` prune ticker; `Message` gained serde; 273 = registration-time nick `RESV` enforcement — a connection may no longer *register* directly as a reserved nick, only the post-registration `NICK`/JOIN were gated before; refuse `432` + reopen registration, mirroring the `433` nick-in-use path; 272 = charybdis `RESV`/`UNRESV` nick/channel reservation — JOIN→`437`/NICK→`432` gates, opers exempt, new `OperPrivilege::Resv`; defaults declared in `resv {}` config blocks, boot-seeded + REHASH-re-seeded keeping runtime `RESV`s). The slices span the full client command surface (registration; channels + the complete channel-mode surface; WHO/WHOIS/WHOX; OPER/STATS; and the query/util commands incl. OPERWALL/LOCOPS/USERIP); the UID-based S2S link/burst/netsplit protocol (keepalive, live introduction, NJOIN chunking, TS merge arbitration); `leveva-iauth` (dnsbl/socks/webproxy/pipe/ident); config live-rehash across every block; CertFP; the **IRCv3 cap track** (message-tags/server-time/msgid/account/bot/oper/echo/labeled-response/CAP, `draft/metadata-2`, caller-id `+g`/`+G`, the **STS/TLS** sub-track); elemental channel modes + extbans; KNOCK; command-rate flood protection (fakelag); the **complete charybdis channel-mode set** (`+C/+c/+S/+j/+z/+T/+g/+r/+f/+Q/+F/+P/+L`, with `+L` the last standard one) and **user-mode set** (`+D/+R/+Q/+G/+z/+S/+l`, plus the product decision that leveva has no local-only umodes — `SEND_UMODES` is every umode); and the `+s` server-notice **snomask** (a charybdis category mask with a producer wired for every category — the matrix is complete). Most const limits (`max-penalty`/`max-bans`/`service-string`/`default-snomask`/…) have been promoted to REHASH-able `options { … }` knobs. **Full per-slice detail (scope, mechanism, divergences, gate) for every slice lives in [`docs/progress-log/p11.md`](docs/progress-log/p11.md); the standing testing plan is [`docs/superpowers/specs/2026-06-10-leveva-testing-plan.md`](docs/superpowers/specs/2026-06-10-leveva-testing-plan.md). This row stays thin.** | Per‑slice: leveva boots and serves the slice; leveva golden snapshot + proptest green; the `leveva-integration` differential agrees with `ircd-common` on the shared skeleton with divergences asserted. Across the eventual matrix: registration, channels, WHO/WHOIS/WHOX, OPER/STATS, server burst, netsplit, rehash, restart; iauth end‑to‑end. | 94| **P12 — Retire the mechanical port; `leveva` is the product ✅ DONE (2026‑06‑13)** ([`docs/progress-log/p12.md`](docs/progress-log/p12.md)) | deleted `ircd-common`/`ircd-rs` + C‑era `iauth-rs` + `leveva-integration` + the mechanical port's deployment rigging; workspace = `leveva` + `leveva-iauth` | Ran the differential suite a **final time green** (`cargo test -p leveva-integration`, skeleton‑identical to the oracle), then **deleted the mechanical port** (`ircd-common`, `ircd-rs`), its C‑era auth scaffolding (`iauth-rs`, long since subsumed by the native `leveva-iauth`), and the oracle differential crate (`leveva-integration`). The oracle tests retired with their oracle — the load‑bearing behavior was already carried by leveva's self‑contained 212‑file golden + proptest suite (verified: leveva/leveva-iauth have zero Cargo/`use` dep on the deleted crates). Also stripped the mechanical port's **deployment rigging** (`docker/Dockerfile.ircd`, the `ircd` bake target, the entire `docs/k8s/` example network) and updated the README to describe the finished strangler. This is the literal end of the strangler — same lifecycle the C tree had at P8. | **MET. Final differential run green; `ircd-common`/`ircd-rs`/`iauth-rs`/`leveva-integration` deleted; `cargo build --workspace` 0 warnings; `cargo clippy --workspace --tests` clean; `cargo test -p leveva -p leveva-iauth` green — the behavioral guarantees carry forward. The workspace is now `leveva` + `leveva-iauth`.** | 95 96--- 97 98## Progress log 99 100The per-phase progress log lives in [`docs/progress-log/`](docs/progress-log/), one file per top-level phase. Each P5 command-handler cluster appends an entry to `p5.md` (see the [command-cluster-port skill](.claude/skills/command-cluster-port/SKILL.md)). 101 102- [P0 — Scaffold + baseline + harness](docs/progress-log/p0.md) 103- [P1 — Leaf / pure utils](docs/progress-log/p1.md) 104- [P2 — Data & lookup](docs/progress-log/p2.md) 105- [P3 — Transport & format](docs/progress-log/p3.md) 106- [P4 — Parser & dispatch](docs/progress-log/p4.md) 107- [P5 — Command handlers](docs/progress-log/p5.md) 108- [P6 — Config & DNS](docs/progress-log/p6.md) 109- [P7 — I/O core + event loop + glue](docs/progress-log/p7.md) 110- [P8 — Delete the C + retire the oracle harness](docs/progress-log/p8.md) 111- [P10 — Rename to `leveva` + typed identifier strings](docs/progress-log/p10.md) 112- [P11 — leveva protocol layer → feature parity](docs/progress-log/p11.md) 113- [P12 — Retire the mechanical port; leveva is the product](docs/progress-log/p12.md) 114- [P‑IAUTH — greenfield iauth-rs](docs/progress-log/piauth.md) 115 116--- 117 118## Key decisions & open risks 119 120- **"100% Rust" is P8, not P7.** ~25 trivial C variadic trampolines persist through the faithful port (stable Rust can't define variadic `extern "C"`). They are the *last* C deleted — P8 replaces them with a non‑variadic Rust sender API at every call site (the full `format!`‑based cleanup of string formatting then completes in P11). (Alternative: nightly `c_variadic` — not recommended; pins the toolchain.) 121- **Determinism is the project's spine.** Hash/list iteration order, clocks, UID/SID sequence, DNS, and fd ordering must all be pinned, or L2 becomes noisy and stops catching regressions. A "faithful" port that changes iteration order passes L1 but fails L2 — by design. 122- **zlib bit‑exactness** (`ZIP_LINKS` server links): flate2 must match compression level/strategy/flush points, or P5 golden fails. Validate early in P5. 123- **Resolver swap risk:** hickory‑dns caching/timeout drift is visible via WHOIS/WHO hostnames; keep a faithful `res*.c` port as fallback if golden diffs appear (P6). 124- **crypt() legacy:** keep libc `crypt` for oper‑password dual‑validation; divergence locks out opers; availability varies by platform. 125- **Reference‑build longevity:** the pristine C tree must keep compiling as the Rust product diverges; OS/toolchain drift is a maintenance cost (pin a container image). 126 127--- 128 129## Verification (end‑to‑end) 130 1311. **Per module:** `cargo test -p <crate>` runs L1 differential (Rust vs `cref_…`) to zero diffs; `cargo fuzz run <target>` for parser/format/config tiers. 1322. **Per phase:** `ircd-testkit` boots reference‑C + Rust ircd under the `LD_PRELOAD` clock shim, replays the scripted serialized session suite (registration, JOIN/PRIVMSG/MODE/KICK/TOPIC, WHO/WHOIS/WHOX, OPER/STATS, server link burst, netsplit, rehash, restart), and asserts canonicalized wire output is byte‑identical. L4 conformance assertions run in the same harness. 1333. **iauth:** `iauth-rs` vs C‑iauth replay with fixed `<id>` scripts + per‑module fixtures; then end‑to‑end ircd+iauth‑rs golden. 1344. **CI (every commit):** build reference‑C; build Rust workspace; boot the server; run L1 (all crates), L2 golden, L4 conformance; nightly L3 fuzz + soak. Green CI after a module's `.c` is dropped is the definition of "that module is migrated and fully tested." 1355. **Final (P7 exit):** `ircd-sys` compiles no C logic; the entire L1–L4 + soak suite is byte‑identical to the archived reference‑C across the full scenario matrix.