P11 slice 47 — WHO / WHOIS / WHOX parity (full)#
Why this slice#
The third parity-matrix slice (user-chosen: "WHO / WHOIS / WHOX", then "Full parity").
Unlike slices 45/46 (which confirmed an already-sound implementation), this slice closes
real gaps found while scoping leveva against the oracle (ircd-common s_user.rs
m_who/who_one/parse_who_arg/m_whois):
-
WHOX is entirely missing. The oracle advertises
WHOXin005, parses the WHO second argument for the legacyoopers-only filter and a%fields[,token]selector, and emits354 RPL_WHOSPCRPLbuilt field-by-field. leveva'swho()ignores the second arg completely andisupport::tokens()does not advertiseWHOX. -
Coupled classic-WHO/WHOIS gaps, now visible because S2S landed after WHO/WHOIS. leveva hardcodes, for every user: server =
ctx.name, hopcount =0, and no*oper flag. With remote users now in the registry (UNICK, slice 31+), a remote user's352server/hop andWHOIS 312server wrongly name leveva itself instead of the user's real server. TheNetworkmirror (ctx.net.server(sid)) already holds the server name + hopcount needed to fix this.
The end state: WHO/WHOIS/WHOX are correct for local and remote users, and WHOX is a real advertised feature.
Oracle reference (the contract to match)#
ircd-common/src/s_user.rs:
parse_who_arg(s_user.c:1886): if the arg's first char iso→ opers-only. From the first%, each subsequent char sets a field flag; a,begins the token (≤ 3 chars, stored raw). Field letters:t c u i h s n f d l a o S U r(note: lowercaseo= op-level field, distinct from the leadingoopers-only filter).who_one(s_user.c:1685): status ={G|H}then*ifis_an_oper, then@/+. If any field flag beyond opers-only is set → emit354with the head:<server> 354 <req>then, in this fixed order, each requested field:t(token|0)c(chan|*)u(user)i(ip)h(host)s(server)n(nick)f(status)d(hop)l(idle; 0 for non-local)a(0)o(n/a)S(sid)U(uid)r(:realname). Else → the classic352.m_who(s_user.c:1969):parc<2→who_find(NULL)+315 *.parc>2→parse_who_arg. Per comma mask:0→all (chan*),*→all, a channel name →who_channel, an exact client →who_one, else globwho_find; each mask closes with315 <mask>.m_whois:312 RPL_WHOISSERVERcarries(*user)->server+ the server's info string.
leveva mapping & deliberate divergences#
| Oracle field | leveva source | Note |
|---|---|---|
s server name, 312 |
local: ctx.name; remote: ctx.net.server(sid_of_uid(uid)).name |
the core remote-field fix |
d hop / 352 trailing <hop> |
local: 0; remote: ctx.net.server(...).hopcount |
|
S SID |
uid.as_str()[..4] (works for local & remote) |
|
U UID |
rec.uid |
|
f status |
`{G | H}+*if+o/+O+@/+` |
i IP |
rec.host |
divergence: leveva has no DNS-resolved host distinct from the peer IP, so %i == %h. Documented. |
l idle |
0 |
divergence: leveva has no idle counter (oracle also emits 0 for non-local). |
a account |
0 |
matches oracle constant (no SASL). |
o op-level |
n/a |
matches oracle constant. |
312 server info |
local: ctx.description; remote: ctx.net.server(...). description? RemoteServer has no description field → use a constant "server" placeholder, documented. |
Remote-server-info for 312: RemoteServer carries no description, so a remote 312 uses a
fixed placeholder string (documented divergence — the oracle has the real burst description, a
field leveva never mirrored). The server name (the load-bearing routing identity) is correct.
Design#
New leveva/src/command/who_opts.rs (or inline in who.rs): a WhoOpts struct
(opers_only: bool, fields: WhoFields bitset-or-bools, token: Option<String>) + a pure
parse_who_arg(arg: &str) -> WhoOpts matching the oracle letter-for-letter. Pure → unit- and
proptest-friendly, and the natural differential candidate (but the oracle parse_who_arg is
unsafe extern "C" over raw *mut c_char + a who_opts C struct — not a clean pure entry, so
no leveva-integration differential, as with every recent leveva-native slice; pinned by
unit + proptest + golden).
A server_view(ctx, uid) -> (server_name: String, hopcount: u16, sid: String) helper resolves
local-vs-remote once, used by both 352/354 (WHO) and 312 (WHOIS).
who_reply gains the * oper flag + remote server/hop. A new whox_reply builds 354
field-by-field in the oracle order. who() calls parse_who_arg on params()[1], applies the
opers-only filter in every sweep (channel, all-users, exact, glob), and dispatches to
whox_reply when fields is non-empty else who_reply.
isupport::tokens() gains "WHOX".
TDD checklist (RED → GREEN per step, one commit each)#
Tests first, always paired with inverses.
parse_who_argunit tests (inwho.rs):osets opers-only;%cnsets chan+nick only;%tcuihsnfdlaorSU,123sets all + token123;o%cnlegacy combo; token > 3 chars ignored;%with junk letters ignored; empty/no-%→ no fields. Inverse: a field not named is unset.354field-by-field unit tests: each field renders in the right slot;WHO #c %cn,%tcuhsnfdlaorSUfull line; token present vs absent (0); channel*for a user sweep.- classic
352parity unit tests: an oper shows*inf(H*,H*@); inverse — deopering clears*. Theofilter drops non-opers from a channel/all sweep; inverse — withoutothey reappear. - remote-user unit tests (new testutil
claim_remotehelper: a0XYZ…UID + aRemoteServer{0XYZ, peer.test, hop 2}inctx.net): WHO%s/352server =peer.test,%d/trailing hop =2,%S=0XYZ; WHOIS312server =peer.test. Inverse: a local user still showsctx.name/0/our sid. - WHOIS 312 remote unit test (covered by 4) + ensure local WHOIS unchanged.
isupporttest:tokens()includesWHOX.- Boot golden
golden_whox.rs(NEW): a real clientWHO #chan %tcuhnfr,42→ snapshot the354lines +315; plus a classicWHO #chanstill works; plus an oper*flag line. - Boot golden
golden_s2s_who.rs(NEW — the REQUIRED S2S test per the cluster skill): on thes2s.kdlfixture, NJOIN/UNICK a remote user into a shared channel; a local clientWHO #hubshows the remote user's352with server = the peer's name and hop ≥ 1 (not leveva);WHOIS <remote>→312names the peer;WHO #hub %s→354server field = peer. Inverse: after the peer drops (slice 46 teardown),WHO/WHOISno longer list it. - proptest
whox_proptest.rs(NEW): random%-field subsets + token → the rendered354has exactly the requested fields in the canonical order and is well-formed CRLF; a second prop feeds arbitrary bytes afterWHO #xfor panic-freedom. (Extend the existingwho_proptestmodel only if cheap; otherwise a focused field-order model.)
Verification gate#
cargo build --workspace 0 warnings; cargo clippy -p leveva --tests clean; new + existing
who/whois goldens & proptests green (golden_who/_whois/_whox/_s2s_who,
who_proptest/whois_proptest/whox_proptest); sibling S2S goldens still green. Update PLAN.md
P11 row + append to PLAN-P11-progress.md (n/a — P11 uses the progress log) and
docs/progress-log/p11.md; update the leveva-next-step-s2s-completeness memory.
Classification / S2S#
WHO and WHOIS are handler clusters that format remote-user fields (server/hop/SID) → an
L2-S2S test is REQUIRED (step 8), not deferred. No oracle differential (the oracle entry
points are unsafe/global-buffer); parity pinned by unit + proptest + boot-golden + S2S-golden.