---
name: leveva-ircv3-track
description: leveva has an IRCv3 track starting at P11 slice 48 (CAP negotiation framework); how to add a capability and the framework-vs-behavior scope rule
metadata: 
  node_type: memory
  type: project
  originSessionId: dc315cf7-0867-45e4-98a1-f2a021b6ea94
---

P11 slice 48 (2026-06-09) opened an **IRCv3 track** in leveva, starting with the `CAP`
capability-negotiation **framework** in `leveva/src/cap.rs` (`CapState` per-connection +
the static `SUPPORTED` registry; `Session` owns one `CapState` and CAP suspends/resumes
registration via a two-gate finalize — auth verdict AND `CAP END`). The C 2.11 oracle has
no CAP, so this and every IRCv3 slice is leveva-native: **no `leveva-integration`
differential** — the gate is unit + boot-golden + proptest only (like [[leveva-next-step-s2s-completeness]] slices 45–47).

**Hard scope rule (locked with the user):** the negotiation *mechanism* and the *behavior*
of each capability are **separate slices**. Slice 48 shipped the framework + `cap-notify`
ONLY (the one cap the framework itself honors). Behavior-bearing caps — multi-prefix,
userhost-in-names, message-tags, server-time, etc. — each get their own later slice and are
added to the `SUPPORTED` table in `cap.rs`. Don't fold a behavior cap into a framework change.

**Slice 51 (2026-06-10) shipped `multi-prefix`** — the FIRST behavior-bearing cap. The
reusable pattern for the next behavior cap: add the name const + a `SUPPORTED` entry; add a
field to `cap::ClientCaps` (the per-conn snapshot struct) + `CapState::client_caps()`;
`command::Registered.caps: ClientCaps` holds the snapshot (set at finalize, **re-synced**
after `cap.apply` in `session.rs` so a post-registration `CAP REQ` is honored live); the
affected handlers read `client.caps.<field>`. multi-prefix's effect is
`MemberStatus::prefixes(multi)` in `channel.rs`, threaded through `names_reply` / WHO
`who_status` / `whois_channels` (gates NAMES 353 / WHO 352+WHOX 354 / WHOIS 319). Adding a
cap to `SUPPORTED` changes the LS token list → expect the `golden_cap` snapshots +
`supported_names` test to update (deliberate). NOTE the churn: `Registered` is built as a
struct literal in ~25 test sites — a new field touches all of them (use a `Default` field).

**Slice 53 (2026-06-10) shipped WebSocket transport** (IRCv3 `websocket`) — the first
**transport** slice (all prior spoke raw TCP/TLS). Served by **axum** (`axum`+`hyper-util`
deps; tokio-tungstenite owns the WS protocol) at HTTP path `/socket`. Structure: pure
IRC-over-WS helpers in `leveva::websocket` (`websocket/mod.rs` — `negotiate`/`decode_incoming`/
`frame_lines`/`origin_allowed`/`proxy_trusted`, faithful to InspIRCd `m_websocket.cpp`,
fully proptested) + the axum glue in `leveva::websocket::server` (`websocket/server.rs` —
`serve_websocket`/`ws_handler`/`serve_ws`). **`tls.rs` was promoted from a binary module to
`leveva::tls`** so the ws server is a clean lib submodule (it needs `TlsEndpoint`). A
`listen{}` port becomes a ws port via a nested `websocket{}` block (`origins`/`proxy-ranges`/
`default-mode`); `Listener.websocket: Option<WebsocketConfig>` + `DefaultMode` in config; the
binary's accept loop branches on it. `serve_ws` reuses the **same `Session` state machine**
(a dedicated `select!`, one ws frame per outbound line via `SinkExt::send`). **New
leveva-native `UserMode::Websocket` (`+W`, `0x0040`)**: set at registration for ws sessions
(`Session::set_websocket`), shown in WHOIS as `320`; **propagates network-wide** (in
`SEND_UMODES` = 125, carried by `UNICK` — the user's explicit call, so remote ws users' WHOIS
shows 320 too) but **server-set-only** (client `+W` silently ignored). It joins the advertised
umode list → `RPL_MYINFO`/ISUPPORT are now `oOiwraW` (snapshots updated). Config flag lives in
a `listen` (NOT `connect`) block. End-to-end gate: a real `tokio-tungstenite` client in
`tests/golden_websocket.rs`. leveva-native (no differential). Also added the table-formatted
`HELP USER_MODES`/`CHANNEL_MODES` topic pages (see [[help-files-per-command]]). Plan:
`docs/superpowers/plans/2026-06-10-p11-slice53-websocket.md`.

**Slice 55 (2026-06-10) shipped IRCv3 bot mode** — a *client-settable* `+B` umode
(`UserMode::Bot`, bit `0x0080`, leveva-native). The reusable pattern for a
client-settable leveva-native umode (contrast `+W`'s server-set-only): add the
variant to `mode.rs` (`ALL`/`as_char`/`bit`/`from_char`/`from_bit`/`render`/`Display`,
appended last so oracle bits are untouched); add the letter to the **settable** arm
`'i' | 'w'` in `command::mode::umode()` AND to `umode_diff`'s iterated list (so the
echo carries it); add the bit to `s2s::umode::SEND_UMODES` to propagate. Surfaced via
`BOT=B` ISUPPORT (`isupport.rs` — also bumps `user_modes_string()` to `oOiwraWB`, so
the 4 `004`/`005` golden snapshots regenerate), `335 RPL_WHOISBOT` in `whois.rs` (after
the `320` slot), and the `B` WHO/WHOX flag in `who::who_status` (after `H`/`G` and the
oper `*`). **The `bot` MESSAGE TAG is DEFERRED** to the future `message-tags` slice (user
ruling): the `Message` type has no tag rendering yet and the tag only reaches
`message-tags` subscribers, so it's dormant regardless. Tests: dedicated
`bot_mode_proptest.rs` (lockstep `{i,w,B}` model + observer WHO/WHOIS) + `golden_bot_mode.rs`.
Plan: `docs/superpowers/plans/2026-06-10-p11-slice55-bot-mode.md`.

**Slice 56 (2026-06-10) shipped the `message-tags` FOUNDATION** — the highest-leverage
IRCv3 slice, because the `Message` type had **no** tag support, the hard prerequisite
blocking ≥5 spec'd caps (the slice-55 bot tag, server-time, message-ids, message-redaction,
oper-tag). Split framework-vs-behavior per the rule above: **56 = the machinery**, **57 =
the delivery behavior**. Slice 56: `Message`/`MessageBuilder` carry ordered
`tags: Vec<(String,String)>`; `parse` peels a leading `@key=value;...` segment (length-checked
vs `MAX_TAG_DATA_LEN=8191` → `ParseError::TagsTooLong`, values unescaped via the spec table);
`write_into` split into `write_tags_into`+`write_body_into` so `to_wire` caps **only the body**
at 512 and the tag prefix is separate budget; accessors `tags()`/`tag(key)`; builder `.tag(k,v)`.
Escaping both ways: `;`↔`\:`, ` `↔`\s`, `\`↔`\\`, CR↔`\r`, LF↔`\n`. Advertised `message-tags`
in `cap.rs` SUPPORTED + `ClientCaps.message_tags`. **Behavior-bearing on its own**: once
advertised, tag-capable clients send `@tag PRIVMSG …` and the server now parses the `@tags`
segment correctly (else `@tags` is mistaken for the command); a client-only `+`tag is parsed
and currently **dropped** (not relayed). **Normalization:** `key` and `key=` both → `(key,"")`
→ render bare `key` (so render→parse→render is a fixed point). Tests: message.rs units +
`message_tags_proptest.rs` (round-trip/escaping/never-panic/untagged-no-regression) + the 2
`golden_cap` snapshots regenerated. **Slice 57 (NEXT, deferred) = the behavior:** the `TAGMSG`
command, client-only (`+`) tag relay to `message-tags` subscribers (per-recipient cap gating
in the delivery plane — `command/message.rs` currently sends ONE wire to all members, so this
needs a tagged/untagged split keyed on each recipient's `ClientCaps.message_tags` — AND across
S2S `relay.rs`), and `417 ERR_INPUTTOOLONG` on oversized inbound tag data. Plan:
`docs/superpowers/plans/2026-06-10-p11-slice56-message-tags.md`. Once it lands, attaching a tag
is just `Message::builder(..).tag("time", ts)` — server-time/msgid/bot-tag/oper-tag become thin
follow-ons.

**Slice 58 (2026-06-10) shipped the message-tags DELIVERY PLANE** — the behavior slice 56
deferred (it landed as **58**, not 57; slice 57 took the PRIVMSG mask-targets path instead).
Three pieces: (1) `TAGMSG <target>` (new `command/tagmsg.rs` + dispatch arm + `s2s::relay::
inbound_tagmsg`/`local_tagmsg`/`route_tagmsg_to_remote`), delivered **only** to `message-tags`
consumers (a non-consumer gets nothing; for a nick target not an error); errors mirror PRIVMSG
(411/404/401, no 412); (2) per-recipient **client-only (`+`) tag relay** on PRIVMSG/NOTICE/
TAGMSG — `Message::client_only_tags()` (new) filters the `+`-prefixed subset; the fan-out
builds a tagged variant for cap-havers and the plain wire for everyone else; unprefixed server
tags stripped; carried across S2S unconditionally (`local_channel_message`/`route_message_to_
remote` gained a `tags` param). The gate is a **`ClientRecord.caps: ClientCaps` snapshot**
mirrored from the connection via new `Registry::set_caps`/`caps_of` (at registration + post-reg
`CAP`), exactly as `set_modes` mirrors `+i`; (3) `417 ErrInputtoolong` (new numeric) in
`session.rs::feed` on `ParseError::TagsTooLong` (no longer a silent drop; pre-reg target `*`).
Divergences: mask broadcasts don't carry client tags; a `$$`/`$#` TAGMSG target isn't a mask
(→401); no `CLIENTTAGDENY`. Help: `help/TAGMSG.md` + index. Tests: units across message/tagmsg/
registry/session/relay + `golden_tagmsg.rs` + `tagmsg_proptest.rs` (the full assembled CAP→reg→
delivery gate). See [[leveva-message-tags-seams]]. Plan: `docs/superpowers/plans/2026-06-10-
p11-slice58-tagmsg-client-tags-417.md`. Now attaching a tag is just `.tag("time", ts)` →
server-time/msgid/bot-tag/oper-tag are thin follow-ons.

**Behavior caps on the slice-58 cap mirror (59–61, 2026-06-10):** once the
`Registry::set_caps`/`caps_of` + `Registered.caps` snapshot existed, each subsequent
behavior cap is a thin per-recipient/per-sender gate read off it — no new plumbing. **59
`away-notify`** (`:nick AWAY [:msg]` to a channel co-member's negotiating clients on an away
*change* or away-user JOIN; gate `caps_of(recipient).away_notify`). **60 `invite-notify`**
(`:inviter INVITE <target> :<chan>` to channel **ops** that negotiated it on a successful
INVITE; `command/invite.rs::notify_channel_ops`). **61 `echo-message`** (sender gets a copy
of its own PRIVMSG/NOTICE/TAGMSG per successful target; `command/message.rs` + `tagmsg.rs`
`echo_to_sender` closure reusing the slice-58 `pick` so the echo carries `+` tags iff the
sender consumes message-tags; gate is **`client.caps.echo_message` straight off the
`Registered`** — no `caps_of` round-trip, the sender is the local connection). Common
divergence shared by 59/60: **remote** members aren't notified (single-server local-mailbox
relay). echo-message was deliberately chosen over `server-time` for slice 61 to keep golden
snapshots wall-clock-deterministic. Plans: `…slice59-away-notify.md`/`…slice60-invite-notify.md`/`…slice61-echo-message.md`.

**Slice 62 (2026-06-10) shipped `server-time`** — the server stamps an
`@time=YYYY-MM-DDThh:mm:ss.sssZ` (RFC 3339 millis, UTC) tag on each delivered/echoed
`PRIVMSG`/`NOTICE`/`TAGMSG` for a negotiating recipient. Reusable patterns established: (1)
the per-target `pick(bool)` in `command/message.rs`/`tagmsg.rs` became `render(ClientCaps)` —
choose the plain-vs-`+`tagged base by `caps.message_tags`, then `Message::with_server_time(&now)`
iff `caps.server_time` (the helper prepends a `("time",ts)` tag at the FRONT so it renders
before client `+`tags); fan-out renders with `caps_of(recipient)`, the slice-61 echo with the
sender's own `client.caps`. (2) **No `ServerContext` clock field** — that would break ~40
`ServerContext {…}` test literals; instead the timestamp is a pure free fn
`clock::format_rfc3339_millis(u64)` (Howard-Hinnant civil_from_days; total/panic-free;
fixed-width + lexicographically monotonic since the year is always 4 digits → the prime
fuzz/proptest target) and golden determinism comes from a **canonicalizer `time=<TS>` mask**
(the established RPL_CREATED/333/391 pattern) + presence/shape (not pinned-value) assertions in
unit/proptest. Scope = the message delivery plane ONLY (the slices-56/58/61 surface); JOIN/PART/
QUIT/MODE/TOPIC/etc., numeric replies, mask broadcasts, and **remote** members are deliberate
follow-ons (each thin now `with_server_time` exists). `SERVER_TIME` appended to `SUPPORTED` +
`ClientCaps.server_time` (so the six LS-string asserts + `supported_names` + 2 `golden_cap`
snapshots regenerate). **`server-time` was deferred from slice 61 for snapshot determinism —
now solved by the canonicalizer mask, so future wall-clock-volatile caps golden-test the same
way.** Plan: `docs/superpowers/plans/2026-06-10-p11-slice62-server-time.md`.

**Slice 63 (2026-06-10) shipped `userhost-in-names`** — a new cap (`UHNAMES` in `SUPPORTED` +
`ClientCaps.userhost_in_names`) that renders every `353 RPL_NAMREPLY` member as
`nick!user@host` instead of the bare nick, gated by the **requester's own** cap (read off
`client.caps`, no `caps_of`). One renderer change: `command/mod.rs::names_reply` gained a
`userhost_in_names: bool` param, resolves each member via `Registry::record_of` (nick+user+host)
and composes with `multi-prefix` sigils (`@+nick!user@host`); the 3 call sites (NAMES named/sweep
+ JOIN burst) thread `client.caps.userhost_in_names`. The legacy `PROTOCTL UHNAMES`/ISUPPORT
method is deliberately NOT implemented (CAP-only, per the RFC). Plan:
`…slice63-userhost-in-names.md`.

**Slice 64 (2026-06-10) shipped the `bot` MESSAGE TAG** — lit up slice 55's deferred bot tag,
the thin follow-on mirroring slice 62. **Key contrast with 59–63: NO new capability.** The bot
tag isn't negotiated by the receiver — it's gated by the **existing** `message-tags` cap AND the
**sender's** `+B` mode (`sender_is_bot = client.modes & UserMode::Bot.bit() != 0`), so
`cap.rs`/`ClientCaps`/`SUPPORTED` are untouched. New `Message::with_bot_tag()` inserts a valueless
`("bot","")` at the FRONT (like `with_server_time`; both → `@time;bot;+client`). The
`render(caps)` closures in `command/message.rs`/`tagmsg.rs` became a 2×2 match over
`(bot, server_time)` where `bot = sender_is_bot && caps.message_tags`; the echo carries `@bot` iff
the bot sender itself consumes message-tags. Scope = PRIVMSG/NOTICE/TAGMSG delivery plane only
(other commands + numerics + remote members are follow-ons — a remote's own server stamps the tag
from network-propagated `+B`). Gate: units + `golden_bot_tag.rs` + `bot_tag_proptest.rs` (the
valueless tag is deterministic → no canonicalizer rule). Plan: `…slice64-bot-tag.md`.

**Slice 66 (2026-06-10) shipped `message-ids` (the `msgid` tag)** — the next thin follow-on
after 62/64. **NO new capability** (rides `message-tags`, like the slice-64 bot tag, so
`cap.rs`/`ClientCaps`/`SUPPORTED` untouched). New `leveva::msgid` module:
`generate(sid)`=`<sid>-<hex AtomicU64 counter>` (monotonic→unique, sid prefix→cross-server
unique) + `is_valid(s)` (spec byte rule: non-empty, no leading `:`, no SPACE/CR/LF). New
`Message::with_msgid(id)` prepends `("msgid",id)` between `with_bot_tag` and `with_server_time`
→ wire order `@time;msgid;bot;+client`. The `render(caps)` closures in `command/message.rs`/
`tagmsg.rs` went from a 2×2 `(bot,server_time)` match to a small composition (fast path +
conditional `with_bot_tag`/`with_msgid`/`with_server_time`). **Per-event invariant:** one id per
target, minted lazily into a per-target `std::cell::OnceCell<String>` so every channel member +
the echo of one message share the SAME id; distinct messages differ. Gated on `message-tags`
(spec) → a server-time-only consumer gets `@time` but no `msgid` (verified: server-time/echo/
privmsg goldens unchanged). Canonicalizer gained a `msgid=<MSGID>` mask (counter-derived →
volatile, like `time=<TS>`); `golden_bot_tag`/`golden_tagmsg` snaps regenerated to `@msgid=…;bot`
/`@msgid=…;+example=…`. Divergences: single-server-local (S2S relay unchanged, remote stamps its
own); in-memory counter resets on restart (spec calls this out, accepted). Gate: `msgid.rs`/
message/tagmsg units + `golden_msgid.rs` + `msgid_proptest.rs` (400 cases: gated-iff-cap,
always-valid, per-event sharing + distinctness, panic-freedom). Plan: `…slice66-msgid.md`.

**Slice 67 (2026-06-10) shipped `oper-tag` (the `draft/oper` tag)** — the next IRCv3 cap after 66.
**HAS its own capability `draft/oper-tag`** (unlike the bot/msgid tags that ride `message-tags`),
so `cap.rs`/`ClientCaps.oper_tag`/`SUPPORTED` DO gain an entry (the 8 LS-string asserts +
`supported_names` + 2 `golden_cap` snaps regenerate, appending ` draft/oper-tag`). Mechanism =
the slice-64 bot-tag analog with two swaps: gate bit is `+o`/`+O` (`sender_is_oper`, hoisted next
to `sender_is_bot`, reused for the existing `$$`/`$#` mask check) not `+B`, and the recipient gate
is `draft/oper-tag`'s OWN cap (independent of `message-tags`, per spec — negotiating the cap
declares the client parses tags). New `Message::with_oper_tag()` prepends a **valueless**
`("draft/oper","")` (`draft/` prefix MANDATORY until the WIP spec ratifies — both cap name
`draft/oper-tag` and tag name `draft/oper`); applied after `with_msgid`, before `with_server_time`
→ wire `@time;draft/oper;msgid;bot;+client`. The `render(caps)` closures in
`command/message.rs`/`tagmsg.rs` add `oper_tagged = sender_is_oper && caps.oper_tag` to the no-tag
fast-path guard + a `with_oper_tag()` arm. **Divergence: VALUELESS tag** (spec permits valued
`draft/oper=<account>` but leveva keeps no post-OPER account store — only the mode bit + privilege
set; valueless conveys the security-relevant fact, the spec's own example line 2). Scope =
PRIVMSG/NOTICE/TAGMSG delivery plane only; single-server-local (S2S unchanged); no mask-broadcast
tagging. Gate: cap/message/tagmsg units + `golden_oper_tag.rs` (alice `OPER root hunter2` then
speaks; dan w/ `draft/oper-tag` sees `@draft/oper`, mel bare) + `oper_tag_proptest.rs` (400 cases,
full 2×2 truth table sender-oper×recipient-cap + toggle-flips-tag inverse + panic-freedom). Plan:
`…slice67-oper-tag.md`. **Pitfall: a blanket `perl -pe` over `userhost-in-names\r\n` also hits the
`CAP REQ :userhost-in-names` ACK assertions — patch the LS line only.**

**Slice 68 (2026-06-10) shipped `message-redaction` (the `REDACT` command + `draft/message-redaction`
cap)** — the next IRCv3 cap after 67, and **TWO firsts on the track: the first NEW COMMAND
(`REDACT`) and the first standard-replies `FAIL` line.** Cap added to `cap.rs`/`SUPPORTED`/
`ClientCaps.message_redaction` (LS-string asserts + `supported_names` + 2 `golden_cap` snaps
regenerate, appending ` draft/message-redaction`). New `command/redact.rs` +
`mod redact;`/dispatch arm `"REDACT"`: `REDACT <target> <msgid> [<reason>]` routes like a PRIVMSG
to a **single** target (no comma list, per the spec's singular `<target>`) and forwards
`:sender!u@h REDACT <target> <msgid> [:reason]` only to recipients with `message_redaction`
(others get NOTHING, spec MUST NOT). **Key design = STATELESS RELAY:** leveva keeps no message
history (msgids are an in-memory counter, slice 66), so it does NO authorship/permission/
msgid-existence validation and NEVER emits `UNKNOWN_MSGID`/`REDACT_FORBIDDEN`/
`REDACT_WINDOW_EXPIRED` — the spec's explicit relay model. The one reachable error is the spec's
`FAIL REDACT INVALID_TARGET <target> :You cannot delete messages from <target>` (leveva's FIRST
`FAIL`; the full standard-replies framework — the `standard-replies` cap, WARN/NOTE — is a later
independent-track slice). Reason read from `msg.trailing()` OR `params()[2]` (single-word reason
can be a plain middle param), always re-emitted as trailing. Divergences: sender cap NOT enforced
(any registered client may send REDACT; gate is recipient-only — the TAGMSG precedent);
single-server-local (S2S relay of REDACT deferred); no server tags on the REDACT line;
`message-tags` dependency not enforced (caps ACKed independently). Gate: cap unit + 10 redact
units (incl. inverses: non-cap co-member/nick gets nothing, sender skipped, moderated/ghost →
FAIL, absent-reason → no trailing) + `golden_redact.rs` (bob w/ cap sees REDACT, mel plain sees
none — sentinel-PRIVMSG technique) + `redact_proptest.rs` (400 cases: delivered-iff-cap,
reason-preserved, toggle-flips-delivery, panic-freedom). Plan:
`…slice68-message-redaction.md`. **Pitfall: `Message::params()` EXCLUDES the trailing param —
assert the reason via `m.trailing()`, not `params()[2]`.**

**Slice 69 (2026-06-10) shipped `standard-replies` (the `FAIL`/`WARN`/`NOTE` framework + cap)** —
a **framework slice** (the framework-vs-behavior split, like slice 48/56): it formalized the
slice-68 ad-hoc redact `FAIL` into a shared builder + advertised the cap. New `leveva::stdreply`
module: `ReplyType{Fail,Warn,Note}` (`verb()`) + `standard_reply(server, ty, command, code,
context: &[&str], description) -> Message` + `fail`/`warn`/`note` wrappers (format `<TYPE>
<command> <code> [<context>...] :<description>`; prefix=server, description always trailing).
`cap.rs` gained `STANDARD_REPLIES="standard-replies"` + SUPPORTED (appended LAST) +
`ClientCaps.standard_replies` + client_caps line (→ the 8 LS-string asserts + `supported_names` +
2 `golden_cap` snaps regenerate, appending ` standard-replies`; the slice-68 message_redaction
"advertised last" assert relaxed to containment). `command/redact.rs::fail_invalid_target`
refactored onto `stdreply::fail` — **byte-identical**, so slice-68 redact units/golden/proptest
unchanged. **KEY divergence (= teeth deferred, like message-tags slice 56):** the cap is
advertised/negotiable + the builder is live, but `ClientCaps.standard_replies` is **wired but not
yet read** — a `FAIL` enabled by a specific spec (the redact one) is sent regardless of the cap;
the cap governs only ARBITRARY (non-spec-enabled) replies, which leveva doesn't emit yet. No
WARN/NOTE caller yet. Gate: 6 stdreply units + the cap unit + `golden_standard_replies.rs` (negotiate
cap, `REDACT #nope 123`→`FAIL REDACT INVALID_TARGET #nope`) + `stdreply_proptest.rs` (400 cases,
the fuzz target = the new builder: build→to_wire→parse round-trip + empty-context-no-stray-param +
panic-freedom). **Fuzz finding:** a middle param (command/code/context) must NOT start with `:`
(re-parses as the trailing) — constrained the generator's first char (real codes are uppercase
idents). Plan: `…slice69-standard-replies.md`.

**Slice 70 (2026-06-10) shipped `MONITOR`** — the IRCv3 online/offline watch list, and the
track's FIRST **server-pushed-on-a-lifecycle-event** feature (vs gating a reply). **NOT a CAP**
— advertised via the `MONITOR=100` ISUPPORT (005) token, so `cap.rs`/`SUPPORTED` untouched (but
the 005 list hit 14 tokens → `NETWORK` wraps to a 2nd 005 line, `TOKENS_PER_LINE=13`; 5
005-goldens regenerated). New `leveva::monitor` module: `MONITOR_LIMIT=100` + a shared
`Monitors` index (`Mutex<{by_target: folded-nick→HashSet<watcher Uid>, by_watcher:
Uid→Vec<display>}>`) — **watch state keyed by the watcher's UID, NOT on `Registered`** (the key
design choice: avoids the ~25 Registered test-literal sites — the slice-51 churn lesson) — plus
free fns `notify_online`/`notify_offline` (deliver `730 :nick!u@h` / `731 :nick` to each
watcher's mailbox, addressed to the watcher's live nick). New `command/monitor.rs` + dispatch
arm: `+`(add≤limit, report 730/731, overflow→`734`)/`-`/`C`/`L`(732+733)/`S`, comma-chunked
within the 512 budget; no-modifier→461, unknown modifier/invalid-nick→silent skip. Live-notify
hooks at the lifecycle seams: `session.rs::finalize_registration`→online, `session.rs::release`→
offline + `monitors.clear`, `command/nick.rs::nick_change`→offline(old)+online(new) (a rename =
offline-of-old + online-of-new). Numerics 730–734 added. `ServerContext.monitors` field (+ the
43 proptest literals, inserted after `peers:`). Help: `help/MONITOR.md` + COMMANDS + index.
**KEY divergence: single-server-local** — online status (`+`/`S`) reads the registry (which holds
remote S2S users), but live `730`/`731` pushes fire only on LOCAL events; remote transitions over
S2S don't push (the track's single-server stance, slices 59+). Other divergences: CAP-less (no
PROTOCTL/WATCH); 730 always carries the hostmask; re-add is a silent no-op. Gate: monitor module
units (6) + command units (11) + live-notify seam units (session/nick) + `golden_monitor.rs`
(two clients: offline→online→offline) + `monitor_proptest.rs` (3: index-vs-model, chunker
budget/lossless/capped, junk-never-panics). Plan: `…slice70-monitor.md`.

**Slice 71 (2026-06-10) shipped `chghost`** — the operator self-`CHGHOST` command + the cap notify
plane + `ENCAP CHGHOST` S2S relay (the user explicitly directed: "to get chghost working you need
to send encap CHGHOST commands to other links"). `chghost` added to `cap.rs`/`SUPPORTED`/
`ClientCaps.chghost` (LS-string asserts + `supported_names` + 2 `golden_cap` snaps regenerate,
appending ` chghost`). **Origination is SELF-only** (`CHGHOST <newuser> <newhost>` changes the
*issuing* oper's own identity — it owns its `Registered`, so the mutation is in-place and correct;
targeting another user needs a control-envelope leveva lacks, a documented future extension — this
is what resolves the old "needs a vhost path" blocker). Gated on `+o`/`+O` like KILL (**no new
`OperPrivilege`** — that enum is 1:1 with oracle `ACL_*` bits); new `Registry::set_host` mirrors
user+host. **The shared `s2s::chghost::notify_co_members(ctx,uid,nick,old_user,old_host,newuser,
newhost)` does the spec's TWO delivery modes per local co-member:** a `chghost` consumer gets
`:nick!old_user@old_host CHGHOST <newuser> <newhost>`; a **non-cap** member gets the
reconnect-simulation fallback `QUIT :Changing host` → per-shared-channel `JOIN` (new mask) +
server `MODE <chan> +<o/v> <nick>` (new `Channels::member_status`) — the user mid-slice required
the fallback (it was first deferred). Self-echo (cap only) is the handler return (co_members
excludes self). **S2S carrier = `ENCAP`:** `local_chghost` broadcasts `:<uid> ENCAP * CHGHOST u h`
to all peers; `forward::inbound_encap` routes the `CHGHOST` subcommand to `s2s::chghost::
apply_encap_chghost` (updates the `RemoteUser` mirror + registry + notifies local co-members; the
changed user = the message source uid, like `UMODE`) while still onward-relaying every ENCAP
verbatim (a non-CHGHOST ENCAP stays opaque — slice-35 regression guarded). **Fuzz finding:** a
leading-`:` user/host passes the typed `UserName`/`HostName` validators (idents/IPv6 admit `:`)
but can't be an IRC middle param (reparses as trailing) → the handler rejects it with a NOTICE
(defensive — the wire parser never yields such a param; the proptest builds messages directly).
Invalid input → NOTICE (no standard-replies code, avoids the slice-69 gate). Gate: cap/registry/
command(10)/s2s(6)/forward(2) units + `golden_chghost.rs` (oper+cap alice, cap bob, plain mel: bob
& self get CHGHOST, mel gets QUIT/JOIN/MODE) + `chghost_proptest.rs` (the fuzz target: command
valid-applies/invalid-inert + last-valid model; inbound tracks-last-valid + never-panics; non-CHGHOST
ENCAP inert). Plan: `…slice71-chghost.md`.

**Slice 76 (2026-06-10) shipped `utf8only`** — the `UTF8ONLY` ISUPPORT token (NOT a cap;
`isupport.rs::tokens`, before `NETWORK`) + the `INVALID_UTF8` standard reply. Before this slice
`session.rs::feed` lossy-decoded every inbound line (`from_utf8_lossy`), silently mangling
non-UTF-8 into U+FFFD and dispatching it. Now `feed` gates on `std::str::from_utf8(&line)`:
`Err` → `stdreply::fail(name, command, "INVALID_UTF8", &[], INVALID_UTF8_DESC)` + `continue` (no
dispatch); `command` = the ASCII-validated verb from a lossy parse (`*` otherwise). Sent
**unconditionally** (enabled-by-spec, like the redact FAIL — not gated on `standard-replies`).
Whole-line rejection (not content-only); no WARN/modify variant. Outbound truncation was already
char-boundary safe (`to_wire`). Additive harness `Client::send_raw(&[u8])`. **Interaction found:
the `ping_arbitrary_bytes_are_robust` proptest** asserted any non-framing token PONGs (the old
lossy contract) → split to PONG-iff-valid-UTF-8, INVALID_UTF8-otherwise (separate `test(leveva)`
commit). Regenerated the 4 full-burst 005 snapshots (UTF8ONLY joins the 2nd 005 line;
`golden_bot_mode` snapshots only the 1st line → untouched). Gate: isupport/session units +
`golden_utf8only` + `utf8only_proptest`. Plan: `…slice76-utf8only.md`.

**Remaining IRCv3 caps: none of the message-tags follow-ons left, and the named independent caps
are now DONE (`utf8only` slice 76, `extended-monitor` slice 74, `chghost` slice 71, `monitor`
slice 70, `standard-replies` slice 69 — its FAIL/WARN/NOTE framework exists; future features add
WARN/NOTE callers + may gate arbitrary replies on `ClientCaps.standard_replies`). No spec'd cap is
queued — pick the next P11 area with the user (see [[leveva-next-step-s2s-completeness]]).**

Added `ErrInvalidcapcmd = 410` to `numeric.rs` (slice 48); `RplWhoisbot = 335` (slice 55);
`ErrInputtoolong = 417` (slice 58). Plans:
`docs/superpowers/plans/2026-06-09-p11-slice48-cap-negotiation.md`,
`docs/superpowers/plans/2026-06-10-p11-slice51-multi-prefix.md`. Spec lives in-repo at
`docs/rfcs/ircv3/`.

**Slice 72 (2026-06-10) shipped IP cloaking (`+x`)** — a port of Elemental-IRCd's
`extensions/ip_cloaking.c`, the first non-IRCv3 *extension* on the track but it follows the
same leveva-native usermode pattern as `+W`/`+B`. New `leveva::cloak` (faithful FNV-1/32
`fnv_hash(s,32)` + `cloak_ip`/`cloak_host`/`cloak`); `UserMode::Cloak` (`+x`, `0x0100`,
appended last). Setting `+x` replaces the visible host with a deterministic cloak of the
**original connect host** (new immutable `ClientRecord.orighost`, set at `try_claim`, never
touched by `set_host`); `-x` restores it. The host change is **distributed by reusing the
slice-71 `chghost` plane** (`notify_co_members` + `local_chghost`/`ENCAP CHGHOST`) — so `+x`
is deliberately **NOT** in `SEND_UMODES` (no double-propagation; the host change is the
propagation, not the umode bit). `396 RPL_HOSTHIDDEN` added. Wire-safety invariant: `cloak`
never introduces a leading `:`. `+x` is client-settable (joins the `i|w|B` arm) AND allowed in
`default-user-modes` (network-wide auto-cloak; `config/parse.rs` now accepts `x`) — a
default-`+x` client is cloaked at registration (the C `check_new_user` path, in
`session::finalize` before the burst). **Also (user request): clients are told their user
modes on connect** — `session::finalize` emits `:nick MODE nick :+<modes>` after the welcome
burst (regenerated 4 registration snapshots: `004`→`oOiwraWBx` + the trailing MODE notice).
Gate: cloak/mode/numeric/registry/command/session/config units + `golden_ip_cloaking`
(`127.0.0.1`→`127.0.y.p`) + `cloak_proptest` (the fuzz target). Plan:
`docs/superpowers/plans/2026-06-10-p11-slice72-ip-cloaking.md`.

**Slice 78 (2026-06-10) shipped CertFP** — charybdis-style TLS client-certificate
fingerprints (researched against a fresh charybdis clone: `ircd/sslproc.c`/`m_certfp.c`/
`m_whois.c:344`/`m_oper.c:120`/`s_user.c:734`). The first **TLS-identity** feature on the
track and NOT a cap. New `leveva::certfp` (`fingerprint(der)` = lowercase-hex **SHA-256** of
the leaf cert DER via the `sha2` dep — the fuzz target; `from_peer_certs(Option<&[CertificateDer]>)`).
**Key TLS pattern: an `AcceptAnyClientCert` `ClientCertVerifier` in `tls.rs`** — requests but
does NOT require a client cert, accepts any issuer (self-signed is the norm; the fingerprint
IS the identity) while still verifying the handshake signature via the ring provider's
`signature_verification_algorithms`; `TlsEndpoint::new` switched `with_no_client_auth()` →
`with_client_cert_verifier(...)`. `main.rs` pulls `tls_stream.get_ref().1.peer_certificates()`
→ `from_peer_certs` → `serve_client(..,certfp)` → `Session::set_certfp` (new field+setter, the
`set_websocket` pattern). **Single source of truth = `ClientRecord.certfp`** (new field + new
`Registry::set_certfp`): finalize mirrors it; WHOIS and OPER read it back, so **no `Registered`
field** (dodged the ~23-literal churn). Surfaced as `276 RPL_WHOISCERTFP` (last extra-info line
before 318, local+remote). **OPER gate:** `Operator.certfp: Option<String>` (`certfp "..."`,
`opt_string`) — a block grants oper iff password verifies AND certfp matches case-insensitively;
a name+host+password match that fails certfp → **491** (charybdis parity), distinct from the
464 password arm (precedence flag `certfp_mismatch`). **S2S = `ENCAP * CERTFP`** (new
`s2s/certfp.rs::apply_encap_certfp` + `forward.rs` arm + burst appends `:<uid> ENCAP * CERTFP
:<fp>` per local certfp user) — the slice-71 CHGHOST relay pattern (record source uid + onward-
relay verbatim). Divergences: **sha256-only** (charybdis defaults to deprecated sha1 + a method
menu); single-server-local notify (burst carries it, no live post-burst rebroadcast — leveva has
no live UNICK introduce); `RemoteUser` untouched (WHOIS reads the registry record UNICK populates).
**Gate incl. the crown-jewel `golden_certfp.rs`: a REAL TLS handshake** (`rcgen`-generated
server+client certs as dev-deps, a synchronous `rustls::StreamOwned` client presenting the
client cert) against a booted leveva → WHOIS 276 == independently-computed SHA-256 + certfp-
gated OPER → 381. Plus `certfp_proptest.rs` + oper/whois/session/s2s units. Adding a 4th field
to `Operator` touched 4 `tests/*_proptest.rs` literals (kill/oper/oper_tag/wallops → `certfp:
None`). Plan: `docs/superpowers/plans/2026-06-10-p11-slice78-certfp.md`.
