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.

feat(leveva): OKICK — operator force-kick bypassing chanop status (P11 slice 294)

charybdis extensions/m_okick.c: an operator force-kicks a member without being
a chanop (or even a member of the channel), sourced from the server so the
channel sees a normal KICK. The oper-override sibling of KICK (chanop-gated) and
REMOVE (chanop force-part), completing the operator channel-intervention family.

- New atomic seam Channels::okick: no permission/rank check (the override); the
one refusal is +Y official-join, whose immunity leveva keeps absolute.
- command/okick.rs gate ladder 481/461/401/403/441/482; server-sourced KICK
echoed to the oper + fanned to local members; relayed via new
s2s::relay::server_kick; +s audit snomask.
- Help page + COMMANDS allowlist + index.md operator line.
- 4 channel units + 5 command units + okick_proptest (2 props) + golden_okick.

leveva-native (no oracle); plain oper-bit gate; +Y respected (vs charybdis raw
removal); oper always gets the echo even off-channel; local-only.

+799 -5
+1 -1
PLAN.md
··· 90 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 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 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: 293 slices** (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. | 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: 294 slices** (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 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 95 96 96 ---
+61
docs/progress-log/p11.md
··· 16083 16083 16084 16084 **Gate:** `cargo test -p leveva` green; `cargo clippy -p leveva --tests` clean; `cargo build 16085 16085 --workspace` 0 warnings. 16086 + 16087 + ## P11 slice 294 — OKICK (operator force-kick bypassing chanop status) 16088 + 16089 + **Plan:** `docs/superpowers/plans/2026-06-20-p11-slice294-okick.md`. 16090 + 16091 + charybdis `extensions/m_okick.c`: an **operator** force-kick. `OKICK <channel> <user> [:comment]` 16092 + forcibly removes `user` from `channel` **without** requiring the oper to be a chanop — or even a 16093 + member of the channel — sourced from the server so the channel sees `:<server> KICK <chan> <user> 16094 + :<comment>`. It is the oper-override sibling of `KICK` (chanop-gated) and `REMOVE` (chanop 16095 + force-*part*, slice 290), and the kick-shaped member of the charybdis operator channel-intervention 16096 + family (`SAJOIN`/`SAPART`/`SANICK`/`SAMODE`/`OJOIN`/`OPME`/`REMOVE`). Distinct from all of them: 16097 + nothing else is an oper-override *visible KICK*. 16098 + 16099 + ### Mechanism 16100 + - New atomic seam `Channels::okick(name, victim) -> OkickOutcome` (single lock, the fuzz seam): 16101 + **no** chanop/membership/rank check (the whole point — operator privilege is established by the 16102 + caller). Channel-missing → `NoSuchChannel`; victim-not-a-member → `NotInChannel { display }`; 16103 + victim `+Y` → `OfficialBusiness { display }` (the one refusal — leveva keeps `+Y` immunity 16104 + absolute, a divergence from charybdis's raw removal); else remove + return `Kicked { display, 16105 + audience(pre-removal roster) }`, deleting the channel if now empty. 16106 + - `command/okick.rs` gate ladder: non-oper `481` → no-arg/one-arg `461` → unknown nick `401` → 16107 + `403` ghost channel → `441` victim-not-on-channel → `482` `+Y` victim → success. On success it 16108 + emits a **server-sourced** `:<server> KICK <chan> <victim> :<comment>` echoed back to the oper 16109 + (always — they need not be a member; excluded from the audience fan to avoid a duplicate) and 16110 + fanned to every other local member incl. the victim, relays server-sourced via the new 16111 + `s2s::relay::server_kick` (`:<our_sid> KICK <chan> <victim_uid> :<reason>`, symmetric with the 16112 + SID-prefix-aware `inbound_kick`), and posts a `+s` audit snomask `<oper> used OKICK on <victim> 16113 + in <chan>`. Comment defaults to the oper's nick (KICK precedent). 16114 + - Wired `"OKICK" => okick::okick(...)` + `mod okick;` in `command/mod.rs` (and `OkickOutcome` in the 16115 + command prelude re-export). Help: `help/OKICK.md` + `OKICK` in the `help.rs` COMMANDS allowlist + 16116 + the operator line in `help/index.md`. 16117 + 16118 + ### Divergences (documented) 16119 + - **leveva-native** — IRCnet 2.11 has no OKICK (charybdis extension); no oracle, no differential. 16120 + - **Plain oper-bit gate** — any oper, not a per-command `OperPrivilege` (OPME/OLIST/TESTLINE 16121 + precedent); charybdis OKICK is plain `mg_not_oper` too. 16122 + - **`+Y` immunity respected** — charybdis OKICK raw-removes (bypasses everything); leveva keeps the 16123 + `+Y` official-join absolute-immunity invariant, refusing with `482`. Deliberate. 16124 + - **Oper always gets the KICK echo** — even when not a member (every leveva handler confirms to the 16125 + actor); charybdis sends the oper nothing extra when off-channel. 16126 + - **`+s` audit snomask not `+w` wallops** — SA*/OJOIN/OPME family precedent (slices 184/186/292). 16127 + - **Local-only** — the S2S relay carries the removal onward, but OKICK is a per-server command. 16128 + 16129 + ### Tests (TDD; inverse invariants + fuzzing) 16130 + - `channel.rs` units (4): `okick_removes_any_member_without_a_permission_check` (no chanop/rank 16131 + check — a plain member removed, roster shrinks, channel survives; inverse — re-join clean); 16132 + `okick_refuses_an_official_join_member` (`+Y` → `OfficialBusiness`, member stays); 16133 + `okick_missing_channel_and_missing_member`; `okick_last_member_deletes_channel`. 16134 + - `command/okick.rs` units (5): `481` non-oper (no removal); `461`/`401`/`403`/`441` ladder; success 16135 + as a non-member oper (server-sourced KICK echoed to oper + co-member sees it + victim gone); 16136 + default comment = oper nick; `482` `+Y` victim (member stays). 16137 + - `tests/okick_proptest.rs` (2 props): `okick_outcome_matches_channel_state` (random 16138 + member/op/`+Y` + ghost target → exact outcome; victim present afterwards **iff** not removed — 16139 + op rank never blocks) + `arbitrary_args_never_panic` (replies ⊆ `{481, 461, 401, 403, 441, 482, 16140 + KICK}`). 16141 + - `tests/golden_okick.rs` (real binary, `dline.kdl` oper): bob owns `#rust` (carol a plain member); 16142 + alice opers but never joins; non-oper bob `OKICK` → `481`; alice `OKICK #rust carol :spam` → 16143 + server-sourced KICK seen by alice/bob/carol; `OKICK #ghost carol` → `403`. 16144 + 16145 + **Gate:** `cargo test -p leveva` green; `cargo clippy -p leveva --tests` clean; `cargo build 16146 + --workspace` 0 warnings.
+98
docs/superpowers/plans/2026-06-20-p11-slice294-okick.md
··· 1 + # P11 slice 294 — OKICK (operator force-kick bypassing chanop status) 2 + 3 + ## What & why 4 + 5 + charybdis `extensions/m_okick.c`: an **operator** force-kick. `OKICK <channel> <user> [:comment]` 6 + forcibly removes `user` from `channel` **without requiring the oper to be a chanop — or even a 7 + member of the channel**, sourced from the server so the channel sees `:<server> KICK <chan> 8 + <user> :<comment>`. It is the oper-override sibling of `KICK` (chanop-gated, slice baseline) and 9 + `REMOVE` (chanop force-*part*, slice 290), and the kick-shaped member of the charybdis operator 10 + channel-intervention family (`SAJOIN`/`SAPART`/`SANICK`/`SAMODE`/`OJOIN`/`OPME`/`REMOVE`). 11 + 12 + Distinct from everything that exists: `KICK`/`REMOVE` enforce chanop + rank protection; the SA* 13 + family forces *part*/join/nick/mode. There is no oper-override visible-KICK yet. OKICK fills it. 14 + 15 + ## Gate order (command/okick.rs) 16 + 17 + 1. Not an IRC operator (`+o`/`+O`) → `481 ERR_NOPRIVILEGES` (OPME/SAJOIN precedent: plain oper 18 + bit, **not** a per-command `OperPrivilege` — charybdis `m_okick` is plain `mg_not_oper` too). 19 + 2. Missing channel or user param → `461 ERR_NEEDMOREPARAMS` ("OKICK"). 20 + 3. Unknown victim nick → `401 ERR_NOSUCHNICK`. 21 + 4. Channel does not exist → `403 ERR_NOSUCHCHANNEL`. 22 + 5. Victim not on the channel → `441 ERR_USERNOTINCHANNEL`. 23 + 6. Victim holds `+Y` (official-join) → `482 ERR_CHANOPRIVSNEEDED` "official network business" 24 + (**divergence from charybdis's raw removal**: leveva's `+Y` immunity is documented *absolute*, 25 + even an oper override respects it — keeps the [[leveva-ojoin-official-join]] invariant true). 26 + 7. Success → remove victim, broadcast `:<server> KICK <chan> <victim> :<comment>` to every local 27 + member (incl. the victim), **echo the KICK back to the oper** (always — the oper need not be a 28 + member; excluded from the audience fan to avoid a duplicate), relay server-sourced S2S, and 29 + post a `+s` audit snomask. 30 + 31 + Comment defaults to the oper's nick (KICK precedent) when absent. 32 + 33 + ## Channel seam — `Channels::okick` (the fuzz seam) 34 + 35 + New atomic seam, single lock, **no permission/rank check** (that is the point): 36 + 37 + ```rust 38 + pub enum OkickOutcome { 39 + NoSuchChannel, 40 + NotInChannel { display: String }, 41 + OfficialBusiness { display: String }, 42 + Kicked { display: String, audience: Vec<Uid> }, 43 + } 44 + pub fn okick(&self, name: &str, victim: &Uid) -> OkickOutcome 45 + ``` 46 + 47 + - channel missing → `NoSuchChannel` 48 + - victim not a member → `NotInChannel { display }` 49 + - victim `+Y` → `OfficialBusiness { display }` 50 + - else remove victim, return `Kicked { display, audience(pre-removal roster) }`, delete the channel 51 + if now empty (`destroy_when_empty`). 52 + 53 + ## S2S — `s2s::relay::server_kick` 54 + 55 + New helper mirroring `server_channel_mode` (server-sourced): broadcast `:<our_sid> KICK <chan> 56 + <victim_uid> :<reason>`. Inbound `inbound_kick` already accepts a SID prefix via `source_mask` 57 + (renders the server name), so the path is symmetric — no inbound change needed. 58 + 59 + ## Divergences (documented) 60 + 61 + - **leveva-native** — IRCnet 2.11 has no OKICK (charybdis extension); no oracle, no differential. 62 + - **Plain oper-bit gate** — any oper, not a per-command `OperPrivilege` (OPME/OLIST/TESTLINE 63 + precedent); charybdis OKICK has no distinct privilege either. 64 + - **`+Y` immunity respected** — charybdis OKICK raw-removes (bypasses everything); leveva keeps the 65 + `+Y` official-join absolute-immunity invariant, refusing with `482`. Documented, deliberate. 66 + - **Oper always gets the KICK echo** — even when not a member (every leveva handler confirms to the 67 + actor); charybdis sends the oper nothing extra when they are not on the channel. 68 + - **`+s` audit snomask not `+w` wallops** — SA*/OJOIN/OPME family precedent (slices 184/186/292); 69 + charybdis sends a wallops. The audit wording is leveva's. 70 + - **Local-only** — removal of a local-or-remote member of *this* server's channel; the S2S relay 71 + carries the removal onward, but OKICK itself is a per-server command (charybdis is per-server). 72 + 73 + ## Tests (TDD; inverse invariants + fuzzing) 74 + 75 + - `channel.rs` units: `okick_removes_any_member_without_a_permission_check` (bypasses chanop/rank — 76 + a plain member is removed, the roster shrinks, channel survives; inverse — re-join is clean); 77 + `okick_refuses_an_official_join_member` (`+Y` → `OfficialBusiness`, member stays); the 78 + missing-channel/missing-member arms; last-member removal deletes the channel. 79 + - `command/okick.rs` units: `481` non-oper (no removal); `461` missing args; `401` unknown nick; 80 + `403` ghost channel; `441` victim-not-on-channel; `482` `+Y` victim; success (oper not a member) 81 + → server-sourced KICK echoed to oper + co-member sees it + victim gone; default comment = oper 82 + nick; inverse — the gated/refused paths change nothing. 83 + - `tests/okick_proptest.rs` (2 props): `okick_outcome_matches_channel_state` (random member/op/`+Y` 84 + + ghost target → exact outcome; victim present afterwards **iff** not removed) + 85 + `arbitrary_args_never_panic` (replies ⊆ `{481, 461, 401, 403, 441, 482, KICK}`). 86 + - `tests/golden_okick.rs` (real binary, `dline.kdl` oper): bob owns `#rust` (carol a plain member); 87 + alice opers but never joins; non-oper bob `OKICK` → `481`; alice `OKICK #rust carol :spam` → 88 + server-sourced KICK seen by bob/carol + echoed to alice; carol gone; `OKICK #ghost x` → `403`. 89 + 90 + ## Wiring 91 + 92 + - `mod okick;` + `"OKICK" => okick::okick(...)` in `command/mod.rs`. 93 + - `help/OKICK.md` + `OKICK` in `help.rs` COMMANDS allowlist + the operator line in `help/index.md`. 94 + 95 + ## Gate 96 + 97 + `cargo test -p leveva` green; `cargo clippy -p leveva --tests` clean; `cargo build --workspace` 0 98 + warnings.
+25
leveva/help/OKICK.md
··· 1 + OKICK <channel> <user> [:comment] 2 + 3 + Operator force-kick. Forcibly removes <user> from <channel> without requiring 4 + you to be a channel operator — or even a member of the channel. The kick is 5 + sourced from the server, so the channel sees a normal KICK from <server>. OKICK 6 + is the operator-override sibling of KICK (which needs channel-operator status) 7 + and REMOVE (which makes the victim appear to PART). 8 + 9 + You must be an IRC operator; a non-operator gives 481. Missing arguments give 10 + 461. An unknown nick gives 401, a channel that does not exist gives 403, and a 11 + nick that is not on the channel gives 441. A member protected by +Y (official 12 + join) is immune even to OKICK and gives 482 — unlike KICK, no other rank or 13 + chanop protection applies. 14 + 15 + When the kick succeeds the channel sees ":<server> KICK <channel> <user> 16 + :<comment>", echoed back to you (even if you are not on the channel) and 17 + delivered to every other member, including the victim. The comment defaults to 18 + your nick when none is given. Every OKICK is logged to the +s server-notice 19 + snomask. 20 + 21 + Examples: 22 + OKICK #leveva troublemaker 23 + OKICK #leveva spammer :read the rules 24 + 25 + See also: KICK, REMOVE, SAPART, OPME.
+1 -1
leveva/help/index.md
··· 10 10 User state: NICK AWAY MODE ACCEPT 11 11 Server info: VERSION TIME ADMIN INFO LUSERS USERS MOTD 12 12 LINKS MAP STATS 13 - Operator commands: OPER WALLOPS KILL CHGHOST OPME OLIST TRACE ETRACE FINDFORWARDS TKLINE UNTKLINE 13 + Operator commands: OPER WALLOPS KILL CHGHOST OKICK OPME OLIST TRACE ETRACE FINDFORWARDS TKLINE UNTKLINE 14 14 DLINE UNDLINE XLINE UNXLINE RESV UNRESV REHASH RESTART DIE 15 15 SQUIT MKPASSWD 16 16
+129
leveva/src/channel.rs
··· 478 478 Opped { display: String }, 479 479 } 480 480 481 + /// The outcome of a [`Channels::okick`] attempt — `OKICK <channel> <user>` (P11 slice 294, 482 + /// charybdis `extensions/m_okick.c`): an operator force-kicks a member **without** any chanop / 483 + /// membership / rank check (that is the whole point — it is the oper-override sibling of `KICK`). 484 + #[derive(Debug, Clone, PartialEq, Eq)] 485 + pub enum OkickOutcome { 486 + /// No channel by that (folded) name exists → `403 ERR_NOSUCHCHANNEL`. 487 + NoSuchChannel, 488 + /// The victim is not on the channel → `441 ERR_USERNOTINCHANNEL`. `display` is the canonical 489 + /// channel name (for the numeric). 490 + NotInChannel { display: String }, 491 + /// The victim holds `+Y` (official-join / network service) and is **un-kickable by anyone** — 492 + /// even an oper override → `482 ERR_CHANOPRIVSNEEDED` "official network business". A leveva 493 + /// divergence from charybdis's raw removal: the `+Y` immunity invariant is absolute. `display` 494 + /// is the canonical channel name. 495 + OfficialBusiness { display: String }, 496 + /// The victim was removed. `display` is the canonical name; `audience` is the roster **before** 497 + /// removal (so the departing victim is included, for the `KICK` broadcast). The channel is 498 + /// deleted if it is now empty. 499 + Kicked { display: String, audience: Vec<Uid> }, 500 + } 501 + 481 502 /// Why a JOIN was refused by [`Channels::check_join`] (each maps to a numeric). 482 503 #[derive(Debug, Clone, Copy, PartialEq, Eq)] 483 504 pub enum JoinReject { ··· 1809 1830 } 1810 1831 } 1811 1832 1833 + /// Operator force-kick `victim` from `name` (P11 slice 294, charybdis `m_okick`). Performs 1834 + /// **no** chanop / membership / rank check — the caller has already established operator 1835 + /// privilege; this is the oper-override path. The only refusal is `+Y` (official-join), whose 1836 + /// immunity leveva keeps absolute even here ([`OkickOutcome::OfficialBusiness`]). On success it 1837 + /// returns the roster **before** removal (so the departing victim is included, for the `KICK` 1838 + /// broadcast) and deletes the channel if it is now empty. 1839 + pub fn okick(&self, name: &str, victim: &Uid) -> OkickOutcome { 1840 + let key = casemap::fold(name); 1841 + let mut g = self.by_name.lock().expect("channels mutex poisoned"); 1842 + let Some(chan) = g.get_mut(&key) else { 1843 + return OkickOutcome::NoSuchChannel; 1844 + }; 1845 + let display = chan.name.clone(); 1846 + let Some(victim_status) = chan.members.get(victim) else { 1847 + return OkickOutcome::NotInChannel { display }; 1848 + }; 1849 + // `+Y` official-join is un-kickable by anyone, even an oper override (leveva keeps this 1850 + // invariant absolute; charybdis's raw `m_okick` would remove it). 1851 + if victim_status.has(ChanMode::OfficialJoin) { 1852 + return OkickOutcome::OfficialBusiness { display }; 1853 + } 1854 + let audience: Vec<Uid> = chan.members.keys().cloned().collect(); 1855 + chan.members.remove(victim); 1856 + if chan.members.is_empty() && destroy_when_empty(chan.modes.flags) { 1857 + g.remove(&key); 1858 + } 1859 + OkickOutcome::Kicked { display, audience } 1860 + } 1861 + 1812 1862 /// The ban masks on `name` (in add order), or `None` if no such channel. 1813 1863 pub fn bans(&self, name: &str) -> Option<Vec<String>> { 1814 1864 self.list_masks(name, ChanMode::Ban) ··· 2902 2952 2903 2953 // Inverse 3 — missing channel. 2904 2954 assert_eq!(ch.opme("#ghost", &alice()), OpmeOutcome::NoSuchChannel); 2955 + } 2956 + 2957 + /// `okick` (P11 slice 294) removes any member **without** a chanop/rank check — the 2958 + /// oper-override path — and the channel survives if other members remain. 2959 + #[test] 2960 + fn okick_removes_any_member_without_a_permission_check() { 2961 + let ch = Channels::new(); 2962 + ch.join("#rust", &alice(), 0).unwrap(); // alice the chanop 2963 + ch.join("#rust", &bob(), 0).unwrap(); // bob a plain member 2964 + // No kicker rank is supplied — okick bypasses the whole permission model. 2965 + match ch.okick("#RUST", &bob()) { 2966 + // fold-insensitive query → canonical display + pre-removal roster (incl. the victim). 2967 + OkickOutcome::Kicked { display, audience } => { 2968 + assert_eq!(display, "#rust"); 2969 + assert!(audience.contains(&bob()), "victim is in the broadcast roster"); 2970 + assert!(audience.contains(&alice())); 2971 + } 2972 + other => panic!("expected Kicked, got {other:?}"), 2973 + } 2974 + assert!(!ch.is_member("#rust", &bob()), "bob is gone"); 2975 + assert!(ch.is_member("#rust", &alice()), "alice (a survivor) remains"); 2976 + 2977 + // Inverse — re-join is clean (no ghost membership). 2978 + ch.join("#rust", &bob(), 0).unwrap(); 2979 + assert!(ch.is_member("#rust", &bob())); 2980 + } 2981 + 2982 + /// `okick` refuses a `+Y` (official-join) member — the immunity is absolute even to an oper 2983 + /// override (a leveva divergence from charybdis's raw removal). 2984 + #[test] 2985 + fn okick_refuses_an_official_join_member() { 2986 + let ch = Channels::new(); 2987 + ch.join("#rust", &alice(), 0).unwrap(); 2988 + ch.join("#rust", &bob(), 0).unwrap(); 2989 + ch.apply_modes_as_server( 2990 + "#rust", 2991 + &[ModeChange::Member { 2992 + add: true, 2993 + mode: ChanMode::OfficialJoin, 2994 + uid: bob(), 2995 + }], 2996 + ); 2997 + assert_eq!( 2998 + ch.okick("#rust", &bob()), 2999 + OkickOutcome::OfficialBusiness { 3000 + display: "#rust".into() 3001 + } 3002 + ); 3003 + assert!(ch.is_member("#rust", &bob()), "the +Y member stays"); 3004 + } 3005 + 3006 + /// `okick` lookup misses: an absent channel and an absent member. 3007 + #[test] 3008 + fn okick_missing_channel_and_missing_member() { 3009 + let ch = Channels::new(); 3010 + ch.join("#rust", &alice(), 0).unwrap(); 3011 + assert_eq!(ch.okick("#ghost", &alice()), OkickOutcome::NoSuchChannel); 3012 + // bob exists nowhere on #rust. 3013 + assert_eq!( 3014 + ch.okick("#rust", &bob()), 3015 + OkickOutcome::NotInChannel { 3016 + display: "#rust".into() 3017 + } 3018 + ); 3019 + } 3020 + 3021 + /// `okick`-ing the last member deletes the channel, then a re-create is clean. 3022 + #[test] 3023 + fn okick_last_member_deletes_channel() { 3024 + let ch = Channels::new(); 3025 + ch.join("#rust", &alice(), 0).unwrap(); 3026 + assert!(matches!( 3027 + ch.okick("#rust", &alice()), 3028 + OkickOutcome::Kicked { .. } 3029 + )); 3030 + assert_eq!(ch.len(), 0, "empty channel removed"); 3031 + ch.join("#rust", &alice(), 0).unwrap(); 3032 + assert!(ch.is_member("#rust", &alice())); 3033 + assert_eq!(ch.len(), 1); 2905 3034 } 2906 3035 2907 3036 /// `wipe_list_masks` (P11 slice 174) clears **all four** list modes — `+b`/`+e`/`+I`/`+R` —
+2 -1
leveva/src/command/help.rs
··· 91 91 /// dispatch router must agree on. Test-only: it powers the two invariants below. 92 92 const COMMANDS: &[&str] = &[ 93 93 "NICK", "SANICK", "PRIVMSG", "NOTICE", "TAGMSG", "JOIN", "SAJOIN", "OJOIN", "OPME", "PART", 94 - "SAPART", "MODE", "SAMODE", "TOPIC", "KICK", "REMOVE", "INVITE", "KNOCK", "NAMES", "LIST", 94 + "SAPART", "MODE", "SAMODE", "TOPIC", "KICK", "REMOVE", "OKICK", "INVITE", "KNOCK", "NAMES", 95 + "LIST", 95 96 "WHO", 96 97 "WHOIS", "WHOWAS", "USERHOST", "USERIP", "ISON", "MONITOR", "ACCEPT", "METADATA", 97 98 "CHATHISTORY", "AWAY",
+4 -1
leveva/src/command/mod.rs
··· 19 19 pub(crate) use crate::casemap; 20 20 pub(crate) use crate::channel::{ 21 21 ApplyOutcome, CanSend, ChannelListing, InviteOutcome, JoinReject, KickGate, KickVictim, 22 - MemberStatus, ModeChange, NamesChannel, NamesReply, OpmeOutcome, PartResult, SetTopic, Topic, 22 + MemberStatus, ModeChange, NamesChannel, NamesReply, OkickOutcome, OpmeOutcome, PartResult, 23 + SetTopic, Topic, 23 24 TopicQuery, 24 25 }; 25 26 pub(crate) use crate::clock; ··· 111 112 "TOPIC" => topic::topic(client, msg, ctx), 112 113 "KICK" => kick::kick(client, msg, ctx), 113 114 "REMOVE" => remove::remove(client, msg, ctx), 115 + "OKICK" => okick::okick(client, msg, ctx), 114 116 "INVITE" => invite::invite(client, msg, ctx), 115 117 "KNOCK" => knock::knock(client, msg, ctx), 116 118 "MODE" => mode::mode(client, msg, ctx), ··· 627 629 mod names; 628 630 pub(crate) mod nick; 629 631 mod ojoin; 632 + mod okick; 630 633 mod oper; 631 634 mod opme; 632 635 pub(crate) mod part;
+217
leveva/src/command/okick.rs
··· 1 + use crate::command::*; 2 + use crate::UserMode; 3 + 4 + /// `OKICK <channel> <user> [:comment]` — leveva's port of charybdis `extensions/m_okick.c` (P11 5 + /// slice 294). An IRC **operator** force-kicks `user` from `channel` **without** being a chanop — 6 + /// or even a member of the channel — sourced from the server so the channel sees `:<server> KICK 7 + /// <chan> <user> :<comment>`. It is the oper-override sibling of [`KICK`](crate::command::kick) 8 + /// (chanop-gated) and [`REMOVE`](crate::command::remove) (chanop force-*part*), and the 9 + /// kick-shaped member of the charybdis operator channel-intervention family 10 + /// (`SAJOIN`/`SAPART`/`SANICK`/`SAMODE`/`OJOIN`/`OPME`/`REMOVE`). 11 + /// 12 + /// Gate order: 13 + /// - Not an IRC operator (`+o`/`+O`) → `481 ERR_NOPRIVILEGES` (OPME precedent: a plain oper bit, 14 + /// not a per-command [`OperPrivilege`](crate::config::OperPrivilege); charybdis `m_okick` is 15 + /// plain `mg_not_oper`). 16 + /// - Missing channel or user → `461 ERR_NEEDMOREPARAMS`. 17 + /// - Unknown victim nick → `401 ERR_NOSUCHNICK`. 18 + /// - Channel does not exist → `403 ERR_NOSUCHCHANNEL`. 19 + /// - Victim not on the channel → `441 ERR_USERNOTINCHANNEL`. 20 + /// - Victim holds `+Y` (official-join) → `482 ERR_CHANOPRIVSNEEDED` "official network business" 21 + /// (a leveva divergence from charybdis's raw removal — the `+Y` immunity stays absolute). 22 + /// - Success → remove the victim, broadcast `:<server> KICK <chan> <victim> :<comment>` to every 23 + /// local member (incl. the victim), **echo the KICK back to the oper** (always — the oper need 24 + /// not be a member; excluded from the audience fan to avoid a duplicate), relay server-sourced 25 + /// to the network ([`crate::s2s::relay::server_kick`]), and post a `+s` audit snomask. 26 + /// 27 + /// Divergences (documented): leveva-native (no oracle — 2.11 has no OKICK); the `+Y` immunity is 28 + /// respected (charybdis raw-removes); the oper always receives the KICK echo even when not a 29 + /// member; the audit notice rides the `+s` snomask, not `+w` wallops (SA*/OJOIN/OPME family, 30 + /// slices 184/186/292); local-only. The comment defaults to the oper's nick (KICK precedent). 31 + pub(crate) fn okick(client: &Registered, msg: &Message, ctx: &ServerContext) -> Vec<Message> { 32 + if client.modes & (UserMode::Oper.bit() | UserMode::LocalOp.bit()) == 0 { 33 + return vec![no_privileges(ctx, &client.nick)]; 34 + } 35 + let Some(chan) = msg.params().first().filter(|s| !s.is_empty()) else { 36 + return vec![need_more_params(ctx, &client.nick, "OKICK")]; 37 + }; 38 + let Some(victim) = msg.params().get(1).filter(|s| !s.is_empty()) else { 39 + return vec![need_more_params(ctx, &client.nick, "OKICK")]; 40 + }; 41 + // Comment is the trailing or a third middle param; defaults to the oper's nick (KICK precedent). 42 + let comment = msg 43 + .trailing() 44 + .or_else(|| msg.params().get(2).map(String::as_str)) 45 + .filter(|s| !s.is_empty()) 46 + .unwrap_or(&client.nick) 47 + .to_string(); 48 + 49 + let Some(uid) = ctx.registry.uid_of(victim) else { 50 + return vec![no_such_nick(ctx, &client.nick, victim)]; 51 + }; 52 + 53 + match ctx.channels.okick(chan, &uid) { 54 + OkickOutcome::NoSuchChannel => vec![no_such_channel(ctx, &client.nick, chan)], 55 + OkickOutcome::NotInChannel { display } => vec![Message::builder(Numeric::ErrUsernotinchannel) 56 + .prefix(&ctx.name) 57 + .param(&client.nick) 58 + .param(victim) 59 + .param(&display) 60 + .trailing("They aren't on that channel") 61 + .build()], 62 + OkickOutcome::OfficialBusiness { display } => { 63 + let victim_nick = ctx 64 + .registry 65 + .nick_of(&uid) 66 + .unwrap_or_else(|| victim.to_string()); 67 + vec![Message::builder(Numeric::ErrChanoprivsneeded) 68 + .prefix(&ctx.name) 69 + .param(&client.nick) 70 + .param(&display) 71 + .trailing(format!( 72 + "Can't kick {victim_nick} as they're on official network business" 73 + )) 74 + .build()] 75 + } 76 + OkickOutcome::Kicked { display, audience } => { 77 + // The canonical victim nick (resolved live) keeps the broadcast consistent with NAMES. 78 + let victim_nick = ctx 79 + .registry 80 + .nick_of(&uid) 81 + .unwrap_or_else(|| victim.to_string()); 82 + // Server-sourced KICK (charybdis `:me.name KICK`), echoed to the oper (always — they 83 + // may not be a member) and fanned to every other local member incl. the victim. 84 + let echo = Message::builder("KICK") 85 + .prefix(&ctx.name) 86 + .param(&display) 87 + .param(&victim_nick) 88 + .trailing(&comment) 89 + .build(); 90 + let wire = echo.to_wire(); 91 + for m in &audience { 92 + if *m != client.uid && ctx.is_local_uid(m) { 93 + ctx.registry.deliver_uid(m, wire.clone()); 94 + } 95 + } 96 + crate::s2s::relay::server_kick(ctx, &display, &uid, &comment); 97 + crate::snotice::server_notice( 98 + ctx, 99 + &format!("{} used OKICK on {victim_nick} in {display}", client.nick), 100 + ); 101 + vec![echo] 102 + } 103 + } 104 + } 105 + 106 + #[cfg(test)] 107 + mod tests { 108 + use super::*; 109 + use crate::command::testutil::*; 110 + 111 + /// An opered `alice` connection (claimed in the registry) carrying `+o`. 112 + fn opered(ctx: &ServerContext) -> Registered { 113 + let _ = claim_observed(ctx, "alice"); 114 + let mut c = client(); 115 + c.modes |= UserMode::Oper.bit(); 116 + c 117 + } 118 + 119 + fn drive(ctx: &ServerContext, c: &Registered, line: &str) -> Vec<Message> { 120 + dispatch(&mut c.clone(), &Message::parse(line).unwrap(), ctx) 121 + } 122 + 123 + /// The gate (and its inverse): a non-operator's OKICK is `481` and removes nothing. 124 + #[test] 125 + fn okick_requires_oper() { 126 + let ctx = ctx(); 127 + let _bob = claim_observed(&ctx, "bob"); 128 + ctx.channels.join("#rust", &uid_for("bob"), 0); // bob the chanop/victim 129 + let r = drive(&ctx, &client(), "OKICK #rust bob"); 130 + assert_eq!(codes(&r), &["481"]); 131 + assert!( 132 + ctx.channels.is_member("#rust", &uid_for("bob")), 133 + "a gated OKICK removes nothing" 134 + ); 135 + } 136 + 137 + /// Operand / lookup errors land on the oper. 138 + #[test] 139 + fn okick_operand_and_lookup_errors() { 140 + let ctx = ctx(); 141 + let oper = opered(&ctx); 142 + assert_eq!(codes(&drive(&ctx, &oper, "OKICK")), &["461"]); 143 + assert_eq!(codes(&drive(&ctx, &oper, "OKICK #rust")), &["461"]); 144 + // Unknown nick → 401 (checked before the channel exists). 145 + assert_eq!(codes(&drive(&ctx, &oper, "OKICK #rust nobody")), &["401"]); 146 + // Known nick, ghost channel → 403. 147 + let _bob = claim_observed(&ctx, "bob"); 148 + assert_eq!(codes(&drive(&ctx, &oper, "OKICK #ghost bob")), &["403"]); 149 + // Known nick not on an existing channel → 441. 150 + let _ = claim_observed(&ctx, "carol"); 151 + ctx.channels.join("#real", &uid_for("carol"), 0); 152 + let r = drive(&ctx, &oper, "OKICK #real bob"); 153 + assert_eq!(codes(&r), &["441"]); 154 + assert_eq!(r[0].params(), &["alice", "bob", "#real"]); 155 + } 156 + 157 + /// Success: an oper who is **not a member** force-kicks; the KICK is server-sourced, echoed to 158 + /// the oper, seen by a co-member, and the victim is gone. 159 + #[test] 160 + fn okick_removes_member_as_a_non_member_oper() { 161 + let ctx = ctx(); 162 + let oper = opered(&ctx); // alice, an oper, never joins #rust 163 + let _bob = claim_observed(&ctx, "bob"); 164 + let mut carol = claim_observed(&ctx, "carol"); 165 + ctx.channels.join("#rust", &uid_for("bob"), 0); // bob the chanop/victim 166 + ctx.channels.join("#rust", &uid_for("carol"), 0); // carol a bystander 167 + 168 + let r = drive(&ctx, &oper, "OKICK #rust bob :spam"); 169 + // The oper gets the server-sourced KICK echoed back (even though not a member). 170 + assert_eq!(codes(&r), &["KICK"]); 171 + assert_eq!(r[0].prefix(), Some(ctx.name.as_str())); 172 + assert_eq!(r[0].params(), &["#rust", "bob"]); 173 + assert_eq!(r[0].trailing(), Some("spam")); 174 + // carol witnesses the same KICK. 175 + let seen = delivered(&mut carol); 176 + assert_eq!(seen.command(), "KICK"); 177 + assert_eq!(seen.params(), &["#rust", "bob"]); 178 + assert!(!ctx.channels.is_member("#rust", &uid_for("bob")), "bob gone"); 179 + assert!(ctx.channels.is_member("#rust", &uid_for("carol"))); 180 + } 181 + 182 + /// Default comment is the oper's nick (no reason supplied). 183 + #[test] 184 + fn okick_default_comment_is_oper_nick() { 185 + let ctx = ctx(); 186 + let oper = opered(&ctx); 187 + let _bob = claim_observed(&ctx, "bob"); 188 + ctx.channels.join("#rust", &uid_for("bob"), 0); 189 + let r = drive(&ctx, &oper, "OKICK #rust bob"); 190 + assert_eq!(r[0].command(), "KICK"); 191 + assert_eq!(r[0].trailing(), Some("alice")); 192 + } 193 + 194 + /// A `+Y` (official-join) member is un-okick-able → `482`, and nothing changes. 195 + #[test] 196 + fn okick_refuses_official_join_member() { 197 + let ctx = ctx(); 198 + let oper = opered(&ctx); 199 + let _bob = claim_observed(&ctx, "bob"); 200 + ctx.channels.join("#rust", &uid_for("bob"), 0); 201 + ctx.channels.apply_modes_as_server( 202 + "#rust", 203 + &[ModeChange::Member { 204 + add: true, 205 + mode: ChanMode::OfficialJoin, 206 + uid: uid_for("bob"), 207 + }], 208 + ); 209 + let r = drive(&ctx, &oper, "OKICK #rust bob :try"); 210 + assert_eq!(codes(&r), &["482"]); 211 + assert!(r[0].trailing().unwrap().contains("official network business")); 212 + assert!( 213 + ctx.channels.is_member("#rust", &uid_for("bob")), 214 + "the +Y member stays" 215 + ); 216 + } 217 + }
+18
leveva/src/s2s/relay.rs
··· 538 538 ctx.peers.broadcast(None, b.to_wire()); 539 539 } 540 540 541 + /// Relay a **server-initiated** KICK (the `OKICK` oper override, P11 slice 294) as 542 + /// `:<our_sid> KICK <chan> <victim_uid> :<reason>`. Sourced from *our* SID rather than the oper's 543 + /// UID because the kick is presented network-wide as coming from the server (charybdis `m_okick` 544 + /// sends `:me.name KICK`). [`inbound_kick`] already renders a SID prefix to the server name via 545 + /// `source_mask`, so the path is symmetric. A no-op if no peer is linked. 546 + pub fn server_kick(ctx: &ServerContext, chan: &str, victim: &Uid, reason: &str) { 547 + if ctx.peers.is_empty() { 548 + return; 549 + } 550 + let wire = Message::builder("KICK") 551 + .prefix(ctx.uids.sid()) 552 + .param(chan) 553 + .param(victim.as_str()) 554 + .trailing(reason) 555 + .to_wire(); 556 + ctx.peers.broadcast(None, wire); 557 + } 558 + 541 559 /// Relay a local client's **user-mode** change as `:<uid> UMODE <nick> <diff>` to every peer. 542 560 /// The `diff` is the `old`→`new` change masked to [`umode::SEND_UMODES`](super::umode::SEND_UMODES) 543 561 /// (every umode — leveva has no local-only modes), so only a no-net-change emits nothing. A no-op
+77
leveva/tests/golden_okick.rs
··· 1 + //! Boot-level golden: `OKICK` end-to-end through the real `leveva` binary (slice 294). 2 + //! 3 + //! `OKICK <channel> <user> [:comment]` lets an operator force-kick a member without being a 4 + //! chanop — or even a member of the channel. One booted server (`dline.kdl`, whose `root` 5 + //! operator suffices — OKICK needs only the oper bit), three clients: 6 + //! 7 + //! 1. `alice`, `bob`, `carol` register; `alice` opers up but never joins `#rust`. 8 + //! 2. `bob` JOINs `#rust` (→ `@op`), `carol` JOINs (a plain member). 9 + //! 3. `bob` (not an operator) `OKICK #rust carol` → `481 ERR_NOPRIVILEGES`. 10 + //! 4. `alice` (an oper, **not** a member) `OKICK #rust carol :spam` → `:leveva.test KICK #rust 11 + //! carol :spam`, echoed to `alice` and seen by `bob` (and `carol`, the victim). 12 + //! 5. `alice` `OKICK #ghost carol` (channel does not exist) → `403`. 13 + //! 14 + //! Lines are server- or numeric-prefixed and deterministic, so only the shared masks apply. 15 + 16 + mod harness; 17 + use harness::{boot_fixture, canonicalize, Client, PORT}; 18 + 19 + fn register(name: &str) -> Client { 20 + let mut c = Client::connect(); 21 + c.send(&format!("NICK {name}")); 22 + c.send(&format!("USER {name} 0 * :{name} Tester")); 23 + c.read_until(" 422 "); // drain the welcome burst (MOTD-missing 422) 24 + c 25 + } 26 + 27 + #[test] 28 + fn okick_end_to_end() { 29 + let _srv = boot_fixture("dline.kdl", PORT); 30 + 31 + let mut alice = register("alice"); 32 + let mut bob = register("bob"); 33 + let mut carol = register("carol"); 34 + 35 + // alice opers up (any operator suffices for OKICK) but never joins #rust. 36 + alice.send("OPER root hunter2"); 37 + alice.read_until(" 381 "); 38 + 39 + // bob creates #rust (→ @op); carol joins as a plain member. 40 + bob.send("JOIN #rust"); 41 + bob.read_until(" 366 "); 42 + carol.send("JOIN #rust"); 43 + carol.read_until(" 366 "); 44 + bob.read_until("JOIN #rust"); // bob sees carol's JOIN 45 + 46 + // 3. non-oper bob → 481 (and the channel is untouched). 47 + bob.send("OKICK #rust carol"); 48 + let bob_481 = bob.read_until(" 481 "); 49 + 50 + // 4. alice (oper, not a member) force-kicks carol → server-sourced KICK, echoed + seen. 51 + alice.send("OKICK #rust carol :spam"); 52 + let alice_kick = alice.read_until("KICK #rust"); 53 + let bob_kick = bob.read_until("KICK #rust"); 54 + let carol_kick = carol.read_until("KICK #rust"); 55 + 56 + // 5. nonexistent channel → 403. 57 + alice.send("OKICK #ghost carol"); 58 + let alice_403 = alice.read_until(" 403 "); 59 + 60 + let mut transcript = String::new(); 61 + let mut keep = |src: &str, needle: &str, label: &str| { 62 + for line in canonicalize(src).lines() { 63 + if line.contains(needle) { 64 + transcript.push_str(label); 65 + transcript.push_str(line); 66 + transcript.push('\n'); 67 + } 68 + } 69 + }; 70 + keep(&bob_481, " 481 ", "bob 481: "); 71 + keep(&alice_kick, "KICK #rust carol", "alice sees KICK: "); 72 + keep(&bob_kick, "KICK #rust carol", "bob sees KICK: "); 73 + keep(&carol_kick, "KICK #rust carol", "carol sees KICK: "); 74 + keep(&alice_403, " 403 ", "alice 403: "); 75 + 76 + insta::assert_snapshot!(transcript); 77 + }
+156
leveva/tests/okick_proptest.rs
··· 1 + //! Property-based fuzzing of `OKICK` (slice 294) — operator force-kick bypassing chanop status. 2 + //! 3 + //! Two surfaces: 4 + //! 5 + //! - **`okick_outcome_matches_channel_state`** — the pure atomic seam [`Channels::okick`]. For a 6 + //! randomly-constructed channel (a base member; an optional victim that may or may not be a 7 + //! member, may or may not hold `+Y`) and an optionally-nonexistent target, the returned outcome 8 + //! must match the model — `NoSuchChannel` / `NotInChannel` / `OfficialBusiness` / `Kicked` — and 9 + //! the victim is present afterwards **iff** it was not removed (i.e. the call did not return 10 + //! `Kicked`). The conservation invariant: OKICK removes a member only when it is on the channel 11 + //! and not `+Y`, regardless of rank — no permission check ever blocks it. 12 + //! - **`arbitrary_args_never_panic`** — `OKICK` at the [`dispatch`] layer with arbitrary printable 13 + //! args never panics and only ever replies `{481, 461, 401, 403, 441, 482, KICK}`. 14 + 15 + use std::sync::Arc; 16 + 17 + use leveva::channel::{Channels, ModeChange, OkickOutcome}; 18 + use leveva::command::{dispatch, Registered}; 19 + use leveva::config::Admin; 20 + use leveva::mode::ChanMode; 21 + use leveva::registry::Registry; 22 + use leveva::server::{Counters, ServerContext}; 23 + use leveva::{Message, Sid, Uid}; 24 + use proptest::prelude::*; 25 + 26 + const SERVER: &str = "leveva.test"; 27 + 28 + fn ctx() -> Arc<ServerContext> { 29 + Arc::new(ServerContext { 30 + name: SERVER.to_string(), 31 + description: "T".to_string(), 32 + network: "TestNet".to_string(), 33 + version: "leveva-0.0.0", 34 + created: "FIXED".to_string(), 35 + motd: None, 36 + counters: Counters::default(), 37 + registry: Registry::new(), 38 + channels: Channels::new(), 39 + whowas: leveva::whowas::WhowasHistory::default(), 40 + uids: leveva::uid::UidGenerator::new(Sid::try_from("0ABC").unwrap()), 41 + default_user_modes: 0, 42 + default_channel_modes: 0, 43 + operators: Vec::new(), 44 + stats_conf: leveva::server::StatsConf::default(), 45 + klines: leveva::kline::KlineStore::new(), 46 + dlines: leveva::dline::DlineStore::new(), 47 + xlines: leveva::xline::XlineStore::new(), 48 + net: leveva::s2s::Network::new(), 49 + peers: leveva::s2s::PeerLinks::new(), 50 + monitors: leveva::monitor::Monitors::new(), 51 + conn_limits: leveva::connlimit::ConnLimits::new(), 52 + linking: leveva::link::LinkingSet::new(), 53 + knock_throttle: leveva::knock_throttle::KnockThrottle::new(), 54 + knock_user_throttle: leveva::knock_throttle::KnockThrottle::new(), 55 + resvs: leveva::resv::ResvStore::new(), 56 + history: leveva::history::History::disabled(), 57 + auth: leveva::server::AuthConfig::default(), 58 + admin: Admin::default(), 59 + }) 60 + } 61 + 62 + fn uid(i: usize) -> Uid { 63 + let c = (b'A' + i as u8) as char; 64 + Uid::try_from(format!("0ABCAAAA{c}").as_str()).unwrap() 65 + } 66 + 67 + /// The requester — an operator, so the `481` gate never masks the outcome path at dispatch. 68 + fn oper() -> Registered { 69 + Registered { 70 + uid: uid(0), 71 + nick: "alice".into(), 72 + user: "u".into(), 73 + host: "h".into(), 74 + realname: "R".into(), 75 + modes: leveva::UserMode::Oper.bit(), 76 + privileges: Vec::new(), 77 + caps: Default::default(), 78 + } 79 + } 80 + 81 + proptest! { 82 + /// The seam's outcome is exactly what the channel state dictates, and a member is removed only 83 + /// on the `Kicked` arm — no chanop/rank check ever blocks it. 84 + #[test] 85 + fn okick_outcome_matches_channel_state( 86 + victim_member in any::<bool>(), 87 + victim_op in any::<bool>(), 88 + victim_official in any::<bool>(), 89 + query_ghost in any::<bool>(), 90 + ) { 91 + let ctx = ctx(); 92 + // A base member (uid(25)) keeps the channel alive across the kick. 93 + ctx.channels.join("#c", &uid(25), 0); 94 + if victim_member { 95 + ctx.channels.join("#c", &uid(1), 0); // the victim (auto-ops on create? no — base exists) 96 + if victim_op { 97 + ctx.channels.apply_modes_as_server( 98 + "#c", 99 + &[ModeChange::Member { add: true, mode: ChanMode::Op, uid: uid(1) }], 100 + ); 101 + } 102 + if victim_official { 103 + ctx.channels.apply_modes_as_server( 104 + "#c", 105 + &[ModeChange::Member { add: true, mode: ChanMode::OfficialJoin, uid: uid(1) }], 106 + ); 107 + } 108 + } 109 + 110 + let target = if query_ghost { "#ghost" } else { "#c" }; 111 + let outcome = ctx.channels.okick(target, &uid(1)); 112 + 113 + let expected = if query_ghost { 114 + OkickOutcome::NoSuchChannel 115 + } else if !victim_member { 116 + OkickOutcome::NotInChannel { display: "#c".to_string() } 117 + } else if victim_official { 118 + OkickOutcome::OfficialBusiness { display: "#c".to_string() } 119 + } else { 120 + // Op rank is irrelevant — okick removes regardless. Compare only the variant; the 121 + // audience roster is checked via membership below. 122 + OkickOutcome::Kicked { display: "#c".to_string(), audience: Vec::new() } 123 + }; 124 + match (&outcome, &expected) { 125 + (OkickOutcome::Kicked { display, .. }, OkickOutcome::Kicked { .. }) => { 126 + prop_assert_eq!(display, "#c"); 127 + } 128 + (a, b) => prop_assert_eq!(a, b, "state→outcome mismatch"), 129 + } 130 + 131 + // The victim is on #c afterwards iff it was NOT removed (not a member, or `+Y` immune). 132 + let still_member = ctx.channels.is_member("#c", &uid(1)); 133 + let removed = matches!(outcome, OkickOutcome::Kicked { .. }); 134 + prop_assert_eq!(still_member, victim_member && !removed, "removal conservation"); 135 + } 136 + 137 + /// Arbitrary args never panic; the reply is always in the allowed set. 138 + #[test] 139 + fn arbitrary_args_never_panic(args in "[#a-zA-Z0-9 ,:*!@._-]{0,40}") { 140 + let ctx = ctx(); 141 + // A channel with a kickable member "bob" so the success (KICK) path is reachable. 142 + let (tx, _rx) = tokio::sync::mpsc::unbounded_channel(); 143 + ctx.registry 144 + .try_claim(&uid(1), "bob", "u", "h", "real", tx) 145 + .expect("free nick"); 146 + ctx.channels.join("#c", &uid(1), 0); 147 + 148 + let line = format!("OKICK {args}"); 149 + let Ok(msg) = Message::parse(&line) else { return Ok(()); }; 150 + let r = dispatch(&mut oper(), &msg, &ctx); 151 + const ALLOWED: &[&str] = &["481", "461", "401", "403", "441", "482", "KICK"]; 152 + for c in r.iter().map(|m| m.command()) { 153 + prop_assert!(ALLOWED.contains(&c), "unexpected reply {c} for {line:?}"); 154 + } 155 + } 156 + }
+1 -1
leveva/tests/snapshots/golden_help_users__help_and_users_queries.snap
··· 15 15 INDEX: :leveva.test NOTICE alice :User state: NICK AWAY MODE ACCEPT 16 16 INDEX: :leveva.test NOTICE alice :Server info: VERSION TIME ADMIN INFO LUSERS USERS MOTD 17 17 INDEX: :leveva.test NOTICE alice : LINKS MAP STATS 18 - INDEX: :leveva.test NOTICE alice :Operator commands: OPER WALLOPS KILL CHGHOST OPME OLIST TRACE ETRACE FINDFORWARDS TKLINE UNTKLINE 18 + INDEX: :leveva.test NOTICE alice :Operator commands: OPER WALLOPS KILL CHGHOST OKICK OPME OLIST TRACE ETRACE FINDFORWARDS TKLINE UNTKLINE 19 19 INDEX: :leveva.test NOTICE alice : DLINE UNDLINE XLINE UNXLINE RESV UNRESV REHASH RESTART DIE 20 20 INDEX: :leveva.test NOTICE alice : SQUIT MKPASSWD 21 21 INDEX: :leveva.test NOTICE alice :
+9
leveva/tests/snapshots/golden_okick__okick_end_to_end.snap
··· 1 + --- 2 + source: leveva/tests/golden_okick.rs 3 + expression: transcript 4 + --- 5 + bob 481: :leveva.test 481 bob :Permission Denied- You're not an IRC operator 6 + alice sees KICK: :leveva.test KICK #rust carol :spam 7 + bob sees KICK: :leveva.test KICK #rust carol :spam 8 + carol sees KICK: :leveva.test KICK #rust carol :spam 9 + alice 403: :leveva.test 403 alice #ghost :No such channel