Commits
The chantrace/masktrace golden tests snapshot the 262 RPL_TRACEEND line,
which embeds VERSION (concat!("leveva-", CARGO_PKG_VERSION)). The v0.3.0
bump changed that token from leveva-0.2.0 to leveva-0.3.0, breaking both
snapshots on every release.
Mask the version column to V, mirroring how these tests already mask the
volatile host/ip columns of 708 lines, so future version bumps no longer
break them.
Assisted-by: Claude Opus 4.8 via Claude Code
Signed-off-by: Xe Iaso <me@xeiaso.net>
Continue the leaf-crate carve-out (after leveva-casemap / leveva-string /
leveva-message / leveva-patricia / leveva-cidr / leveva-cloak) by moving the IRC
glob matcher + nick!user@host hostmasks out of the monolith into its own
workspace crate:
- leveva-matching: the C match.c cluster (matches / Pattern / collapse / HostMask
/ host_component_matches / allow_host_matches, 498 LoC). The survey-flagged
"natural next pick": once leveva-cidr split, its only intra-crate edges were
casemap::to_lower and cidr::cidr_match — both already crates — plus nom. Deps =
leveva-casemap + leveva-cidr + nom.
Body moved verbatim via git mv; five repoints only: `use crate::casemap;` ->
`use leveva_casemap as casemap;`, `crate::cidr::cidr_match` ->
`leveva_cidr::cidr_match`, the module-header intra-doc link (crate::casemap) ->
(leveva_casemap), and two doctests `use leveva::matching::…` ->
`use leveva_matching::…`. leveva re-exports it as leveva::matching
(`pub use leveva_matching as matching;`), so the flat
`pub use matching::{collapse, matches, HostMask, Pattern};` and all ~13 consumers
(xline / kline / resv / webirc / metadata / registration +
command/{oper,map,mask,hunt,dline,tkline}) keep their crate::matching::* paths.
No dependency cycle — the code edges all run into matching.
Fuzzing: stable toolchain only (no nightly / no cargo-fuzz), so proptest is the
runnable fuzz tier, matching the other leaves. leveva's matcher deliberately
diverges from match.c (RFC 1459 case-fold, not ASCII tolowertab; #=any-digit), so
there is NO byte-faithful differential oracle — the 13 proptest invariants ARE the
gate. Fed arbitrary (incl. non-ASCII) strings + glob masks: totality/no-panic over
every entry point, `*` is universal, `?`^k iff exact byte-length, case-insensitivity
under leveva_casemap::fold of the name, compile == matches, collapse is
matching-preserving + idempotent + non-growing, HostMask re-parses its own Display,
matches_target == matches_parts, allow_host_matches decomposes into user-glob &&
host-component, and a glob host component falls back to matches.
Gate: cargo build --workspace (0 warnings) · cargo clippy --workspace --tests
(clean) · cargo test -p leveva-matching (28 unit/collapse/escaping + 13 fuzz, + 2
doctests) green · leveva matching-consumer tests (xline/kline/resv/webirc, 181) green.
What else can move out: uid (only crate::ident, blocked on extracting ident — ident
itself blocked by a single crate::command reference worth auditing, the next unlock);
msgid pulls cap/clock/ident; clock is a small candidate leaf. Recorded in the plan +
progress log.
Continue the leaf-crate carve-out (after leveva-casemap / leveva-string /
leveva-message / leveva-patricia / leveva-cidr) by moving the +x hidden-host
transform out of the monolith into its own workspace crate:
- leveva-cloak: the deterministic FNV-keyed IP/host cloak (cloak / cloak_ip /
cloak_host), a faithful port of Elemental-IRCd's ip_cloaking.c. The cleanest
remaining leaf, exactly as the leveva-cidr survey predicted ("zero intra-crate
deps — perfect standalone leaf, strongest next pick"): it depends only on
std::net::IpAddr and on nothing in the workspace. Its only crate::-references
were doc comments — the actual code edges all run *into* it
(command::mode::apply_cloak_change at mode.rs:916 and session.rs:1786 call
crate::cloak::cloak), so no cycle.
leveva re-exports it as leveva::cloak (`pub use leveva_cloak as cloak;`), so every
crate::cloak::* / leveva::cloak::* path is unchanged across consumers. Body moved
verbatim via git mv — no doctest, and every intra-doc link (cloak/cloak_ip/
cloak_host/IP_CHARTABLE/HOST_ALPHABET + the FNV reference) resolves in-module, so
nothing repointed.
Fuzzing: stable toolchain only (no nightly / no cargo-fuzz), so proptest is the
runnable fuzz tier, matching casemap/string/cidr. And since the C 2.11 oracle has
no +x usermode, there is NO differential oracle here — the 10 proptest invariants
ARE the gate, not a supplement. Fed arbitrary (incl. non-ASCII) strings and the
full v4/v6 address space: determinism (the ban-on-a-cloak-keeps-biting contract),
the leading-colon wire-safety biconditional (a non-: input never grows a leading
colon, a :-input is returned verbatim), length preservation for both cloak_ip and
cloak_host, separator/dot byte-index invariance, IP-literal dispatch to cloak_ip,
the g-z IP alphabet (v4 keeps its first two octets; v6 is hex/:/g-z with no leading
colon), and fnv_hash_32 == charybdis's explicit shift-decomposition over arbitrary
bytes.
What else can move out: matching (now a clean casemap+cidr leaf, pulls nom — the C
match.c cluster, natural next pick); uid (only crate::ident, blocked on extracting
ident); msgid pulls cap/clock/ident. Recorded in the plan + progress log.
Gate: cargo build --workspace 0 warnings; cargo clippy --workspace --tests clean;
leveva-cloak 18 (8 unit + 10 fuzz) green; leveva +x consumer tests (command::mode
plus_x suite + session registers-cloaked + the s2s +s+x spy, 8) green.
Carve the pure CIDR network-mask helpers (parse_cidr / contains /
network_base / cidr_match) out of leveva into a self-contained
leveva-cidr leaf crate, re-exported as `leveva::cidr` — continuing the
post-P12 leaf-crate carve-out after casemap/string/message/patricia.
cidr is a genuinely pure leaf: zero workspace deps, only std::net. This
corrects the earlier leveva-string survey, which filed matching+cidr as
"mutually referential, not a clean single leaf yet" — cidr's references
to matching/connlimit are doc-comment-only; the real edge is one-way
matching -> cidr. Extracting cidr therefore *unblocks* matching, whose
only remaining intra-crate deps are now casemap + cidr (both crates).
git mv leveva/src/cidr.rs -> leveva-cidr/src/lib.rs verbatim except the
doctest import and two cross-crate doc links; leveva/src/lib.rs swaps
`pub mod cidr;` for `pub use leveva_cidr as cidr;`. All four call sites
use fully-qualified crate::cidr::*, so zero consumer churn.
Fuzzing (the user ask): 10 proptest invariants over the full v4/v6
address space and arbitrary in-range prefixes, all consistency/inverse
properties — parse round-trip + out-of-range reject, contains reflexive
through network_base, network_base idempotent, contains prefix-monotone,
/0 universal + full-length exact, family mismatch never contains, and
cidr_match == parse + contains.
Next extraction candidates: matching (now a clean leaf, the C match.c
cluster), cloak (zero intra-crate deps), uid (blocked on ident).
Gate: cargo build -p leveva 0 warnings; cargo clippy -p leveva-cidr
--tests clean; cargo test -p leveva-cidr 18 (8 unit + 10 fuzz) + 1
doctest green; leveva cidr-consumer tests (matching/connlimit/websocket,
54) green.
Continue the leaf-crate carve-out (after leveva-message / leveva-patricia /
leveva-casemap) by moving the casemapping-aware IRC string types out of the
monolith into their own workspace crate:
- leveva-string: IrcStr / IrcString (str/String wrappers whose equality,
ordering, and hashing fold under RFC 1459) + IrcError + IrcStringBuilder. The
architectural #2 leaf in the string cluster — now that casemap is its own crate,
string depends on exactly one workspace crate (leveva-casemap) plus core/std,
while ident/channel/registry/whowas all depend on it. No cycle: leveva-message
uses neither, leveva-casemap depends only on core.
leveva re-exports it as leveva::string (`pub use leveva_string as string;`), and
the existing `pub use string::{IrcError, IrcStr, IrcString, IrcStringBuilder};`
re-exports straight out of the new crate — so every crate::string::* /
leveva::string::* path and the four flat re-exports are unchanged across all
consumers (the full dependent build confirms zero churn). Body moved verbatim
except `use crate::casemap;` -> `use leveva_casemap as casemap;` and the two
doctests' `use leveva::...;` -> `use leveva_string::...;`.
Fuzzing: stable toolchain only (no nightly / no cargo-fuzz), so proptest is the
runnable fuzz tier, matching casemap/message. Added 6 consistency/inverse
invariants fed arbitrary (incl. non-ASCII) strings: eq lifts casemap (the wrapper
adds no equality of its own), Ord consistent-with-Eq + antisymmetric, Hash agrees
with Eq (the HashMap-key contract behind the nick/channel tables), Borrow<IrcStr>
round-trip (insert under one casing, find by a case-different borrowed key),
validation soundness (accept iff no NUL/CR/LF, FromStr agrees, storage
byte-identical on success), and builder = deferred try_from.
What else can move out: cloak (zero intra-crate deps — perfect standalone leaf,
strongest next pick); uid (only crate::ident); matching+cidr are a coupled pair;
msgid pulls cap/clock/ident. Recorded in the plan + progress log.
Gate: cargo build --workspace 0 warnings; cargo clippy --workspace --tests clean;
leveva-string 13 (7 unit + 6 fuzz) + 2 doctests green; full leveva suite (413
binaries) green.
Continue the leaf-crate carve-out (after leveva-message / leveva-patricia) by
moving the RFC 1459 case-folding module out of the monolith into its own
workspace crate:
- leveva-casemap: byte/slice fold, compare, hash, and the owned-key `fold`. It
imports only `core` (Ordering + Hasher) and is the lowest leaf in the string
cluster — string/ident/matching/channel/registry/whowas depend on it, it on
nothing. std-only lib, proptest dev-dep.
leveva re-exports it as leveva::casemap (`pub use leveva_casemap as casemap;`),
so every crate::casemap::* / leveva::casemap::* path across ~40 source and test
files is unchanged — no consumer churn.
Fuzzing: stable toolchain only (no nightly / no cargo-fuzz), so proptest is the
runnable fuzz tier, matching leveva-message. Added 7 consistency/inverse
invariants fed arbitrary bytes and arbitrary (incl. non-ASCII) strings:
fold ⇔ eq_ignore_case both directions, cmp==Equal ⇔ eq, cmp antisymmetry,
hash-agrees-with-eq (prefix-free 0xff marker exercised), fold idempotent +
UTF-8-safe + byte-length-preserving, high bytes (≥0x80) identity, and
to_upper∘to_lower identity on the uppercase domain over the full byte range.
Gate: cargo build --workspace 0 warnings; cargo clippy --workspace --tests
clean; leveva-casemap 13 (6 unit + 7 fuzz) and the full leveva suite (60
binaries) green.
Carve the two most self-contained, runtime-free modules out of the monolithic
leveva crate into their own workspace crates:
- leveva-message: the RFC 1459 / IRCv3 wire Message, its builder and parser.
The API is generic over impl Display, so it carries no dependency on leveva's
typed identifiers or server runtime (only std + unicode-segmentation + serde).
- leveva-patricia: the generic, safe, arena-backed Patricia (radix) trie for
longest-prefix matching. Zero intra-crate coupling, std-only.
leveva re-exports each as leveva::message / leveva::patricia (pub use ... as ...),
so every existing crate::message::* and crate::patricia::* path and the crate-root
re-exports are unchanged — no consumer churn. Doc intra-links to other leveva
modules became plain code spans and the doctest/test imports point at the new
crate names; the message test's typed-id stand-in is a local Display newtype.
Gate: cargo build --workspace 0 warnings; cargo clippy --workspace --tests clean;
leveva-message 40+3, leveva-patricia 14+1, and the full leveva suite all green.
Slice 309 made +M (operpeace) oper-only and updated the mode.rs unit
assertion, but this duplicate check in exlimit_proptest.rs still asserted
that only +P and +L are oper-only, so it failed once OperPeace joined the
set. Fold OperPeace into the expectation and rename to match.
operpeace_proptest (3 props: total + model-equal over the boolean cube; an oper
kicker is never blocked; a non-+M channel never blocks) + golden_operpeace
(boot: a non-oper chanop's KICK of an oper on +M -> 482; an oper kicker bypasses).
Port charybdis extensions/chm_operpeace.c: a +M channel flag prohibiting a
non-operator from kicking an IRC operator. New ChanMode::OperPeace (letter M,
high-half bit, oper-only to set like +P/+L); pure seam channel::operpeace_blocks
+ Channels::operpeace_enabled; shared command::{operpeace_protects,
operpeace_refusal} enforced in KICK and REMOVE (482 + a +s audit snotice, victim
untouched). OKICK bypasses (oper-gated). Refusal reuses 482 not charybdis 484
(leveva 484 = ERR_RESTRICTED). isupport/registration asserts + 10 CHANMODES/
MYINFO snapshots regenerated for the new M token.
roleplay_proptest: compose is total/never-panics over spicy nick+text input, the
display nick keeps no control char but the deliberate \x1F underline pair, ACTION
bodies are \x01ACTION…\x01, the (author) attribution is always present, and
underline only wraps the same inner nick. golden_roleplay: NPC underlined fake
nick + SCENE =Scene= delivered to a co-member, inverse -N channel → 573.
Port of charybdis extensions/m_roleplay.c. On a +N channel a member speaks from a
fake nick <nick>!<author>@npc.fakeuser.invalid: NPC (underlined), NPCA (action),
SCENE/AMBIANCE (fixed =Scene=), FSAY/FACTION (clean nick for opers). The pure
roleplay::compose seam does the charybdis strip_unprintable + underline + ACTION
wrap; the gate ladder reuses display_name/is_member/roleplay_enabled/can_send,
fanning to all local members (incl. issuer) and relaying via s2s::roleplay. New
numeric 573 ERR_ROLEPLAY; six help pages + COMMANDS allowlist.
Wire ChanMode::Roleplay (letter +N, bit 0x1_0000_0000) through the channel-mode
checklist (enum/ALL/as_char/bit/mode_name/from_char) + the Channels::roleplay_enabled
accessor. +N appears in CHANMODES/MYINFO; isupport asserts + golden snapshots
regenerated for the new token. The commands that use it land next.
The IRCnet-derived 32-bit channel-flags space is full (ExtendLimit = 0x1, the
last free bit). Widen ChannelModes.flags, ChanMode::bit(), MemberStatus.bits and
the default-channel-modes plumbing (config/control/server/chants) to u64 so
leveva-native flags can be added past the 32-bit boundary. No behaviour change.
Port of charybdis extensions/extb_combi.c: two extban operator types whose
data is a comma-separated list of child extbans ([~]<type>[:<data>], no leading
$), optionally paren-wrapped, with paren-aware comma splitting and backslash
escaping. $& matches iff all children match; $| iff any does. Children may
nest (depth cap 5, <=10 children/node, data <= BANLEN 195).
A faithful byte-walk of eb_combi in extban/combi.rs dispatching to the existing
eval_type handlers — the C global recursion_depth is threaded as a depth param
(leveva runs many connections). Preserves the two charybdis subtleties: a child
type is validated for existence always (even when short-circuited, via the new
is_known_type), but child data only up to the short-circuit point, so combiban
validity is subject-dependent.
extban_chars() -> "&acjmorsxz|" so 005 advertises EXTBAN=$,&acjmorsxz|.
Divergence: leveva's $m is usermode (not charybdis's hostmask), so host
matching inside a combiban uses $x:<nick!user@host>#*.
Tests: 11 combi units (AND/OR/negated/nested/paren+escaped-comma + inverses for
every malformed shape, short-circuit type validation, the caps), extban_combi_
proptest (6 props: totality, AND==all/OR==any model vs an independent fold,
single-child==bare, De Morgan), golden_extban_combi (JOIN gates biting only
after OPER + the 005 token). isupport/elemental_extban/mod.rs asserts updated;
9 EXTBAN-token snapshots regenerated. EXTBANS.md documents the operators.
Plan file, p11.md progress entry, and the PLAN.md P11 row bump to 306 slices.
Port charybdis extensions/umode_noctcp.c to a leveva user mode +C: a
self-settable umode that refuses CTCP queries (other than ACTION) sent to the
user as a PRIVMSG, replying 531 ERR_CANNOTSENDTOUSER :+C set. NOTICE, CTCP
replies, and ACTION always pass. The recipient-side private-message counterpart
of the channel +C (slice 220), reusing the fuzzed channel::is_blocked_ctcp.
Pure seams mode::is_noctcp + mode::noctcp_user_blocks (the composite gate
decision), enforced home-server-local on both the local command/message.rs path
and the inbound-S2S s2s/relay::inbound_message path (new noctcp_reject_remote
routes 531 to the sender's uplink, the +R 248->249 split precedent). The bit
still propagates (slice-254 "no local-only umodes"), so WHOIS shows +C. Bit
0x80000, numeric 531; umode_diff/RENDERED/004-literal/snapshot wiring per the
user-mode checklist.
Tests: mode seam units, command self-settable + inverse, message gate +
inverses, s2s inbound + inverses, golden_noctcp_umode, noctcp_umode_proptest
(5 props). 5 welcome snapshots regenerated (004 gains C).
Plan file + p11.md progress entry + PLAN.md P11 row bump (304 -> 305 slices).
Close the slice-301 follow-on: a webirc {} trusted-gateway block was parsed
once at boot into ctx.stats_conf.webirc and never re-read, so adding, editing,
or removing a trusted webchat gateway required a restart. Carry the gateway
list through the control::ConfStore live-config chain (mirroring how slice 303
made alias {} reloadable) and read it live-or-ctx at session::handle_webirc, so
REHASH hot-applies an edited webirc set with no restart.
- control.rs: ConfBlocks.webirc + config_conf extraction; ConfStore.webirc
ArcSwapOption + new()/seed() + webirc() accessor + live_webirc() global.
- main.rs: seed the live store with config.webirc at boot.
- session.rs::handle_webirc: read gateways live-or-ctx (live store wins; a
session that never ran main falls back to the immutable boot list).
Tests (TDD; inverse invariants): control units (fresh none / no-block empty /
seeded reads back / empty-seed clears the stale gateway); golden_rehash_webirc
(no-block refuse -> add+REHASH spoof -> remove+REHASH refuse again); the
rehash_conf_proptest reload model extended with a generated webirc set
(live (label,host) == last successful reload; None while unseeded).
Plan file + p11.md progress entry + PLAN.md row bump to 304 slices.
An alias { target "user@server" } block now resolves the server part to a
linked remote server and routes :<uid> PRIVMSG user@server :<text> to its
uplink, matching charybdis m_alias's find_server branch (was: 440). Unknown or
our-own server name -> 440, empty text -> 412. Pure fuzz seam
alias::remote_privmsg_wire (totality + round-trip proptest); S2S golden +
unit inverses.
Port charybdis's alias{} mechanism (modules/m_alias.c + newconf.c alias_entry):
an `alias "<name>" { target "<t>" }` block makes <name> a command that rewrites
`<name> <text...>` into `PRIVMSG <target> :<text...>`, delivered through the message
plane. A real command always takes precedence (the hook is the existing 421 fallback).
Found by comparing against a charybdis checkout: the 'configurable IDENTIFY nicks'
originally scoped does not exist in charybdis (the nicks are hardcoded #defines); the
alias{} block is the real, faithful, more general mechanism.
- alias.rs pure seam: parse_target (split on first @ -> nick vs user@server),
combined_args / reconstruct; fuzzed by tests/alias_proptest.rs.
- new top-level alias{} config block (model/parse/from_kdl), held live + REHASH-able
through ConfStore (live_aliases, the service-string chain); main.rs boot seed.
- command/alias.rs: deliver_to_service_nick shared core (target must be a present +S
service else 440 ERR_SERVICESDOWN; empty text -> 412; else synth PRIVMSG +
message::message, so a remote service is reached by UID routing). new numeric 440.
- folds in the slice-302 IDENTIFY faithfulness fix the comparison surfaced: empty args
-> 412 (was 461), absent/non-+S agent -> 440 (was 401), via the shared core.
- user@server target parsed/fuzzed but not yet routed (-> 440, documented follow-on).
Tests: alias seam + command + config-parse units; identify units updated; extended
rehash_conf_proptest alias reload; golden_alias (S2S +S burst: route + 440 + 421
inverses); golden_identify rewritten (S2S +S NickServ/ChanServ).
cargo test -p leveva green / clippy clean / build --workspace 0 warnings.
A port of charybdis's extensions/m_identify.c, the client-facing companion
to the services/SASL cluster (296-299): a registered client types
`IDENTIFY [<target>] <password...>` and the server rewrites it into a PRIVMSG
to a services pseudo-agent (ChanServ when the first argument is a channel,
else NickServ), delivered through the ordinary message plane.
- leveva/src/identify.rs: the pure, fuzzable rewrite seam
`rewrite(params) -> Option<Rewrite>` (target by params[0]'s '#', verbatim
args re-joined after the keyword, empty first arg -> 461).
- leveva/src/command/identify.rs: synthesizes the PRIVMSG and delegates to
message::message, so the agent sees the real source mask and an unlinked
services sender gets the plane's own 401. Dispatch + HELP IDENTIFY.
leveva-native sugar: hardcoded service nicks, registered-clients-only, no
state and no S2S of its own. Units + golden_identify + identify_proptest.
Port charybdis extensions/m_webirc.c: a trusted webchat gateway sends a
pre-registration WEBIRC <password> <gateway> <hostname> <ip> so the proxied
connection is recorded under the real client's host/IP, not the gateway's.
Every later admission gate (D/K/X-line, config-ban, per-class conn caps, +x
cloak) and WHOIS key off the spoofed self.host.
- new pure fuzz seam leveva::webirc {parse_request, spoof_host, authorize}
- webirc {} config block (host mask + password) -> StatsConf (boot-read)
- Session::handle_webirc dispatched phase-independently (CAP/AUTHENTICATE seam),
one-spoof-per-connection, +s audit snotice; registered -> 462, bad-pw /
untrusted host / malformed -> NOTICE, no spoof
- HELP WEBIRC topic + commented example/dist config block
Divergences (leveva-native, no oracle): single-host/no-DNS collapse of
charybdis host/sockhost; trust via webirc {} not auth{}; boot-read.
Tests: webirc/config/session units (inverses), golden_webirc end-to-end,
webirc_proptest (parse total + round-trip, spoof_host range, authorize
contract).
Plan file + p11.md detailed entry + PLAN.md P11 row bump (299 → 300).
The read-state companion to draft/chathistory (slice 274). MARKREAD
<target> [timestamp=<iso8601>] records or queries how far a client has
read a target; a set stores max(existing, new) (monotonic — never
regresses), echoes the effective value, and on a genuine advance fans
the MARKREAD to the user's other capable connections (cross-device
read-state sync). A bare MARKREAD <target> queries the stored marker.
Markers are owned by the services account (synced across connections,
survives a reconnect) when logged in, else by the connection UID
(per-connection, dropped on Session::release). The cap is statically
advertised and valueless; the command is processed regardless of the
issuer's own cap — the cap gates only the cross-connection fan, per spec.
leveva-native: the C 2.11 oracle predates IRCv3, so the gate is unit +
golden + proptest (no differential).
- readmarker.rs: pure parse/format seam (parse_set_timestamp /
format_value) + the monotonic ReadMarkers store (account|UID owner key)
- command/markread.rs: the handler (NEED_MORE_PARAMS / query / set /
INVALID_PARAMS / advance + fan)
- cap.rs: DRAFT_READ_MARKER const + SUPPORTED entry + read_marker
ClientCaps bit; CAP LS snapshots refreshed (a long SASL mechlist now
tips the 302 LS into a spec-legal multiline split)
- server.rs: read_markers field; session.rs: forget_connection release hook
- help/MARKREAD.md + COMMANDS allowlist + index.md Queries line
- tests: readmarker/markread units (inverse invariants), golden_markread
(single-conn round-trip + cross-device push vs uncapped sibling),
readmarker_proptest (total parse + round-trip; advance keeps-max,
order-independent + idempotent)
Slice 295 added the CYCLE command and listed it on the help index
"Channel commands:" line, but the golden_help_users snapshot was not
regenerated, leaving the test red. Accept the current output (adds
CYCLE) to restore parity.
Assisted-by: Claude Opus 4.8 via Claude Code
Signed-off-by: Xe Iaso <me@xeiaso.net>
- contrib/atheme/leveva.c: leveva_mechlist_sts emits `ENCAP * MECHLIST`, wired
via the mechlist_sts pointer; module-header ENCAP list updated.
- docs/s2s.md: MECHLIST row + subsection; transparent-relay note.
- PLAN.md P11 row bumped to 299 slices; plan file + progress-log entry.
A linked SASL services agent announces its mechanisms via
`:<svcSID> ENCAP * MECHLIST :<comma-list>`; leveva learns the list, advertises
it (client-facing `sasl=` cap value + `908 RPL_SASLMECHS`) instead of the
hardcoded `PLAIN,EXTERNAL`, and relays whichever advertised mechanism a client
picks — faithful charybdis transparent relay (special-casing only EXTERNAL for
the CertFP; an unadvertised mechanism → 908+904, never relayed).
- sasl.rs: pure fuzzed `parse_mechlist`; `Mechanism` reworked
`{Plain,External,Other(Box<str>)}` with `from_advertised`/`name`/`is_external`.
- s2s/links.rs: PeerLinks learned-mechlist store, cleared when the last sasl
agent unlinks.
- s2s/mechlist.rs (new): `apply_encap_mechlist`, dispatched in forward.rs's
interpret-and-onward-relay block (broadcast, like SU).
- cap.rs: `apply`/`ls`/`req` `sasl_available: bool` → `sasl_mechs: Option<&str>`.
- session.rs: AUTHENTICATE gate uses `from_advertised`; 908 lists the advertised
set; relay_start takes &Mechanism.
leveva-native (the C 2.11 oracle predates SASL). Tests cover the happy path and
every inverse invariant (unadvertised rejected, all-junk keeps the prior list,
unlink clears, default fallback) + 2 proptests.
Plan file + p11.md entry + PLAN.md row bump; docs/s2s.md SASL-relay vhost note;
contrib/atheme/leveva.c leveva_svslogin_sts comment (atheme is the sender).
An inbound ENCAP <us> SVSLOGIN <cliUID> <nick> <user> <host> <account> previously
honored only <account>; <nick>/<user>/<host> were parsed but used merely cosmetically
in the 900 RPL_LOGGEDIN. Now non-* <user>/<host> are applied as a services vhost on SASL
login (the HostServ-vhost-on-login case); * / empty / IRC-invalid = unchanged.
- sasl.rs: pure fuzzed SvsIdentity::from_fields (validated via UserName/HostName::try_from);
SaslEntry.identity; SaslSessions::{set_identity,identity}; remove -> SaslResolution{account,identity}.
- s2s/sasl.rs apply_encap_svslogin: stash the identity alongside the account.
- session.rs finalize_registration: drain the resolution before the cloak block, commit the
vhost (overriding auto-cloak) as a DISPLAY identity after all host-based admission gates ran
on the real user@host, before local_introduce so UNICK/welcome carry it; orighost kept real.
The <nick> field is deliberately not applied (no pre-introduction rename primitive; real flows
send *; SANICK forces a nick). leveva-native; TDD + inverse invariants + golden + proptest.
leveva used UID sources + UID targets for member ops, but emitted the typed NICK
in the target slot of user-directed messages (PRIVMSG/NOTICE/TAGMSG). Flip the two
outbound emit call sites (command/message.rs route_message_to_remote,
command/tagmsg.rs route_tagmsg_to_remote) to pass the recipient's UID, making the
wire fully UID-consistent like TS6.
Deliberate protocol divergence (every peer must parse the new form). Receive side
already UID-ready (slice 296 resolve_user_target + nick_of final-hop render +
verbatim onward relay). Channel/opmod/$$/$# mask targets, the sender's local echo,
and the final-hop client rendering are unchanged -- clients still see nicks.
Tests/snapshots flipped to UID targets; docs/s2s.md updated. Live-verified: leveva
emits ':0LVAAAAAA PRIVMSG 0ATHAAAAG :HELP' to atheme; the client still sees
'NOTICE probe'. Built via an ultracode discover->implement->verify workflow.
An atheme protocol module (contrib/atheme/leveva.c) letting a services package
(NickServ/ChanServ/OperServ/SaslServ/...) link to a leveva server. Hybrid of
atheme's ircnet burst skeleton (PASS/SERVER/UNICK/NJOIN/SAVE/EOB) and ts6-generic
ENCAP grafts (account login SU, SASL relay, SVSLOGIN, CHGHOST, K/D/X-line + RESV,
ENCAP * CAPAB :sasl). Standalone (not ts6-generic -- leveva is not TS6); UID via
protocol/base36uid (alphabet matches leveva). Full elemental rank set incl. +Y/!
official-join as CSTATUS_IMMUNE.
Verified live: compiles clean, links + syncs with leveva, services burst,
NickServ account login propagates via ENCAP * SU. Ships README + example leveva
and atheme link configs. Authored by Midori Yasomi.
Inbound S2S handlers resolved a user target via registry.uid_of (nick-only),
so a peer addressing a local client by its UID (':0ATH.. NOTICE 0LVA.. :..',
how every services package / IRC server addresses a user) was silently dropped.
Add one pub(crate) resolve_user_target (nick-first, then Uid::try_from accepted
only when it names a known user) and route inbound_message, inbound_tagmsg,
inbound_numeric (relay.rs), apply_encap_redact (redact.rs), and inbound_kill
(kill.rs -- services KILL by UID) through it. Each keeps its is_local_uid guard
so a remote UID is reached only by the onward split-horizon relay. UID-addressed
delivery now renders the client's display nick. inbound_numeric drops an unknown
well-formed UID (documented 'unknown target is dropped' contract).
15 TDD tests (inverse invariants) across s2s::{relay,redact,kill}; built via an
ultracode discover->implement->verify workflow.
Append the slice-289 XLINE/UNXLINE entry to the D-line claude-memory file:
the realname (gecos) ban that completes the runtime ban quartet K/D/X/RESV,
built bolt-for-bolt on the DLINE plane with a single-token gecos matcher. Notes
the key divergence — XLINE uses a dedicated xline_exempt (the now-lit-up
CFLAG_XEXEMPT), not the shared kline_exempt — and that the charybdis runtime
ban family is complete.
Assisted-by: Claude Opus 4.8 via Claude Code
Signed-off-by: Xe Iaso <me@xeiaso.net>
charybdis-style `CYCLE <channel>` (extensions/m_cycle.c): part and rejoin a
channel *without races*. Faithful to the real m_cycle, the effect is entirely
client-side for the issuer — `:source PART <chan> :Cycling`, `:source JOIN
<chan>`, then a fresh 353/366 — and nothing changes server-side, so the user
keeps op/voice and place and cannot be locked out by +i/+k/+l or lose a glare.
No new Channels seam (CYCLE never mutates): it composes the read-only
display_name/is_member/names_query accessors. Per-channel gate ladder:
461 no param → 403 nonexistent → 442 not-a-member → success, existence before
membership; the re-sent NAMES honours the issuer's multi-prefix/userhost-in-names.
Divergences (leveva-native, no oracle): self-only/observationally-pure (NOT
member-visible — that would drop ops, destroy a sole-member channel, and desync
peers), fixed :Cycling reason, local-only.
7 command units + cycle_proptest (observational-purity + arbitrary-args) +
golden_cycle; CYCLE.md help + COMMANDS allowlist + index.md.
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.
charybdis extensions/m_olist.c: the operator variant of LIST that bypasses the
secret (+s) / private (+p) channel-hiding so an oper enumerates every channel,
including the hidden ones a normal LIST skips. The read-only sibling of LIST and
the latest member of the charybdis operator diagnostic family
(TESTLINE/TESTMASK/MASKTRACE/CHANTRACE/FINDFORWARDS/OPME).
- Channels::list_all() (full folded sweep) + list_one_any(name) (named incl.
hidden) — the visibility-bypass seams, no filter; also the fuzz seam.
- command/olist.rs: plain oper-bit gate (non-oper 481 before any sweep), 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).
- Wired "OLIST" + mod olist; help/OLIST.md + COMMANDS allowlist + index.md.
Divergences (leveva-native, no oracle): plain oper-bit gate (not a per-command
privilege), no +s audit snomask (read-only diagnostic family precedent),
local-only.
Tests: list_all/list_one_any units; 6 command units; olist_proptest (full-set
+ superset-of-list model equality, arbitrary_args_never_panic); golden_olist
(LIST hides #secret, OLIST reveals it, non-oper 481). Regenerating
golden_help_users also picks up the pre-existing stale operator-index line
(OPME/FINDFORWARDS from slices 291/292).
cargo test -p leveva green; clippy clean; build --workspace 0 warnings.
The chantrace/masktrace golden tests snapshot the 262 RPL_TRACEEND line,
which embeds VERSION (concat!("leveva-", CARGO_PKG_VERSION)). The v0.3.0
bump changed that token from leveva-0.2.0 to leveva-0.3.0, breaking both
snapshots on every release.
Mask the version column to V, mirroring how these tests already mask the
volatile host/ip columns of 708 lines, so future version bumps no longer
break them.
Assisted-by: Claude Opus 4.8 via Claude Code
Signed-off-by: Xe Iaso <me@xeiaso.net>
Continue the leaf-crate carve-out (after leveva-casemap / leveva-string /
leveva-message / leveva-patricia / leveva-cidr / leveva-cloak) by moving the IRC
glob matcher + nick!user@host hostmasks out of the monolith into its own
workspace crate:
- leveva-matching: the C match.c cluster (matches / Pattern / collapse / HostMask
/ host_component_matches / allow_host_matches, 498 LoC). The survey-flagged
"natural next pick": once leveva-cidr split, its only intra-crate edges were
casemap::to_lower and cidr::cidr_match — both already crates — plus nom. Deps =
leveva-casemap + leveva-cidr + nom.
Body moved verbatim via git mv; five repoints only: `use crate::casemap;` ->
`use leveva_casemap as casemap;`, `crate::cidr::cidr_match` ->
`leveva_cidr::cidr_match`, the module-header intra-doc link (crate::casemap) ->
(leveva_casemap), and two doctests `use leveva::matching::…` ->
`use leveva_matching::…`. leveva re-exports it as leveva::matching
(`pub use leveva_matching as matching;`), so the flat
`pub use matching::{collapse, matches, HostMask, Pattern};` and all ~13 consumers
(xline / kline / resv / webirc / metadata / registration +
command/{oper,map,mask,hunt,dline,tkline}) keep their crate::matching::* paths.
No dependency cycle — the code edges all run into matching.
Fuzzing: stable toolchain only (no nightly / no cargo-fuzz), so proptest is the
runnable fuzz tier, matching the other leaves. leveva's matcher deliberately
diverges from match.c (RFC 1459 case-fold, not ASCII tolowertab; #=any-digit), so
there is NO byte-faithful differential oracle — the 13 proptest invariants ARE the
gate. Fed arbitrary (incl. non-ASCII) strings + glob masks: totality/no-panic over
every entry point, `*` is universal, `?`^k iff exact byte-length, case-insensitivity
under leveva_casemap::fold of the name, compile == matches, collapse is
matching-preserving + idempotent + non-growing, HostMask re-parses its own Display,
matches_target == matches_parts, allow_host_matches decomposes into user-glob &&
host-component, and a glob host component falls back to matches.
Gate: cargo build --workspace (0 warnings) · cargo clippy --workspace --tests
(clean) · cargo test -p leveva-matching (28 unit/collapse/escaping + 13 fuzz, + 2
doctests) green · leveva matching-consumer tests (xline/kline/resv/webirc, 181) green.
What else can move out: uid (only crate::ident, blocked on extracting ident — ident
itself blocked by a single crate::command reference worth auditing, the next unlock);
msgid pulls cap/clock/ident; clock is a small candidate leaf. Recorded in the plan +
progress log.
Continue the leaf-crate carve-out (after leveva-casemap / leveva-string /
leveva-message / leveva-patricia / leveva-cidr) by moving the +x hidden-host
transform out of the monolith into its own workspace crate:
- leveva-cloak: the deterministic FNV-keyed IP/host cloak (cloak / cloak_ip /
cloak_host), a faithful port of Elemental-IRCd's ip_cloaking.c. The cleanest
remaining leaf, exactly as the leveva-cidr survey predicted ("zero intra-crate
deps — perfect standalone leaf, strongest next pick"): it depends only on
std::net::IpAddr and on nothing in the workspace. Its only crate::-references
were doc comments — the actual code edges all run *into* it
(command::mode::apply_cloak_change at mode.rs:916 and session.rs:1786 call
crate::cloak::cloak), so no cycle.
leveva re-exports it as leveva::cloak (`pub use leveva_cloak as cloak;`), so every
crate::cloak::* / leveva::cloak::* path is unchanged across consumers. Body moved
verbatim via git mv — no doctest, and every intra-doc link (cloak/cloak_ip/
cloak_host/IP_CHARTABLE/HOST_ALPHABET + the FNV reference) resolves in-module, so
nothing repointed.
Fuzzing: stable toolchain only (no nightly / no cargo-fuzz), so proptest is the
runnable fuzz tier, matching casemap/string/cidr. And since the C 2.11 oracle has
no +x usermode, there is NO differential oracle here — the 10 proptest invariants
ARE the gate, not a supplement. Fed arbitrary (incl. non-ASCII) strings and the
full v4/v6 address space: determinism (the ban-on-a-cloak-keeps-biting contract),
the leading-colon wire-safety biconditional (a non-: input never grows a leading
colon, a :-input is returned verbatim), length preservation for both cloak_ip and
cloak_host, separator/dot byte-index invariance, IP-literal dispatch to cloak_ip,
the g-z IP alphabet (v4 keeps its first two octets; v6 is hex/:/g-z with no leading
colon), and fnv_hash_32 == charybdis's explicit shift-decomposition over arbitrary
bytes.
What else can move out: matching (now a clean casemap+cidr leaf, pulls nom — the C
match.c cluster, natural next pick); uid (only crate::ident, blocked on extracting
ident); msgid pulls cap/clock/ident. Recorded in the plan + progress log.
Gate: cargo build --workspace 0 warnings; cargo clippy --workspace --tests clean;
leveva-cloak 18 (8 unit + 10 fuzz) green; leveva +x consumer tests (command::mode
plus_x suite + session registers-cloaked + the s2s +s+x spy, 8) green.
Carve the pure CIDR network-mask helpers (parse_cidr / contains /
network_base / cidr_match) out of leveva into a self-contained
leveva-cidr leaf crate, re-exported as `leveva::cidr` — continuing the
post-P12 leaf-crate carve-out after casemap/string/message/patricia.
cidr is a genuinely pure leaf: zero workspace deps, only std::net. This
corrects the earlier leveva-string survey, which filed matching+cidr as
"mutually referential, not a clean single leaf yet" — cidr's references
to matching/connlimit are doc-comment-only; the real edge is one-way
matching -> cidr. Extracting cidr therefore *unblocks* matching, whose
only remaining intra-crate deps are now casemap + cidr (both crates).
git mv leveva/src/cidr.rs -> leveva-cidr/src/lib.rs verbatim except the
doctest import and two cross-crate doc links; leveva/src/lib.rs swaps
`pub mod cidr;` for `pub use leveva_cidr as cidr;`. All four call sites
use fully-qualified crate::cidr::*, so zero consumer churn.
Fuzzing (the user ask): 10 proptest invariants over the full v4/v6
address space and arbitrary in-range prefixes, all consistency/inverse
properties — parse round-trip + out-of-range reject, contains reflexive
through network_base, network_base idempotent, contains prefix-monotone,
/0 universal + full-length exact, family mismatch never contains, and
cidr_match == parse + contains.
Next extraction candidates: matching (now a clean leaf, the C match.c
cluster), cloak (zero intra-crate deps), uid (blocked on ident).
Gate: cargo build -p leveva 0 warnings; cargo clippy -p leveva-cidr
--tests clean; cargo test -p leveva-cidr 18 (8 unit + 10 fuzz) + 1
doctest green; leveva cidr-consumer tests (matching/connlimit/websocket,
54) green.
Continue the leaf-crate carve-out (after leveva-message / leveva-patricia /
leveva-casemap) by moving the casemapping-aware IRC string types out of the
monolith into their own workspace crate:
- leveva-string: IrcStr / IrcString (str/String wrappers whose equality,
ordering, and hashing fold under RFC 1459) + IrcError + IrcStringBuilder. The
architectural #2 leaf in the string cluster — now that casemap is its own crate,
string depends on exactly one workspace crate (leveva-casemap) plus core/std,
while ident/channel/registry/whowas all depend on it. No cycle: leveva-message
uses neither, leveva-casemap depends only on core.
leveva re-exports it as leveva::string (`pub use leveva_string as string;`), and
the existing `pub use string::{IrcError, IrcStr, IrcString, IrcStringBuilder};`
re-exports straight out of the new crate — so every crate::string::* /
leveva::string::* path and the four flat re-exports are unchanged across all
consumers (the full dependent build confirms zero churn). Body moved verbatim
except `use crate::casemap;` -> `use leveva_casemap as casemap;` and the two
doctests' `use leveva::...;` -> `use leveva_string::...;`.
Fuzzing: stable toolchain only (no nightly / no cargo-fuzz), so proptest is the
runnable fuzz tier, matching casemap/message. Added 6 consistency/inverse
invariants fed arbitrary (incl. non-ASCII) strings: eq lifts casemap (the wrapper
adds no equality of its own), Ord consistent-with-Eq + antisymmetric, Hash agrees
with Eq (the HashMap-key contract behind the nick/channel tables), Borrow<IrcStr>
round-trip (insert under one casing, find by a case-different borrowed key),
validation soundness (accept iff no NUL/CR/LF, FromStr agrees, storage
byte-identical on success), and builder = deferred try_from.
What else can move out: cloak (zero intra-crate deps — perfect standalone leaf,
strongest next pick); uid (only crate::ident); matching+cidr are a coupled pair;
msgid pulls cap/clock/ident. Recorded in the plan + progress log.
Gate: cargo build --workspace 0 warnings; cargo clippy --workspace --tests clean;
leveva-string 13 (7 unit + 6 fuzz) + 2 doctests green; full leveva suite (413
binaries) green.
Continue the leaf-crate carve-out (after leveva-message / leveva-patricia) by
moving the RFC 1459 case-folding module out of the monolith into its own
workspace crate:
- leveva-casemap: byte/slice fold, compare, hash, and the owned-key `fold`. It
imports only `core` (Ordering + Hasher) and is the lowest leaf in the string
cluster — string/ident/matching/channel/registry/whowas depend on it, it on
nothing. std-only lib, proptest dev-dep.
leveva re-exports it as leveva::casemap (`pub use leveva_casemap as casemap;`),
so every crate::casemap::* / leveva::casemap::* path across ~40 source and test
files is unchanged — no consumer churn.
Fuzzing: stable toolchain only (no nightly / no cargo-fuzz), so proptest is the
runnable fuzz tier, matching leveva-message. Added 7 consistency/inverse
invariants fed arbitrary bytes and arbitrary (incl. non-ASCII) strings:
fold ⇔ eq_ignore_case both directions, cmp==Equal ⇔ eq, cmp antisymmetry,
hash-agrees-with-eq (prefix-free 0xff marker exercised), fold idempotent +
UTF-8-safe + byte-length-preserving, high bytes (≥0x80) identity, and
to_upper∘to_lower identity on the uppercase domain over the full byte range.
Gate: cargo build --workspace 0 warnings; cargo clippy --workspace --tests
clean; leveva-casemap 13 (6 unit + 7 fuzz) and the full leveva suite (60
binaries) green.
Carve the two most self-contained, runtime-free modules out of the monolithic
leveva crate into their own workspace crates:
- leveva-message: the RFC 1459 / IRCv3 wire Message, its builder and parser.
The API is generic over impl Display, so it carries no dependency on leveva's
typed identifiers or server runtime (only std + unicode-segmentation + serde).
- leveva-patricia: the generic, safe, arena-backed Patricia (radix) trie for
longest-prefix matching. Zero intra-crate coupling, std-only.
leveva re-exports each as leveva::message / leveva::patricia (pub use ... as ...),
so every existing crate::message::* and crate::patricia::* path and the crate-root
re-exports are unchanged — no consumer churn. Doc intra-links to other leveva
modules became plain code spans and the doctest/test imports point at the new
crate names; the message test's typed-id stand-in is a local Display newtype.
Gate: cargo build --workspace 0 warnings; cargo clippy --workspace --tests clean;
leveva-message 40+3, leveva-patricia 14+1, and the full leveva suite all green.
Port charybdis extensions/chm_operpeace.c: a +M channel flag prohibiting a
non-operator from kicking an IRC operator. New ChanMode::OperPeace (letter M,
high-half bit, oper-only to set like +P/+L); pure seam channel::operpeace_blocks
+ Channels::operpeace_enabled; shared command::{operpeace_protects,
operpeace_refusal} enforced in KICK and REMOVE (482 + a +s audit snotice, victim
untouched). OKICK bypasses (oper-gated). Refusal reuses 482 not charybdis 484
(leveva 484 = ERR_RESTRICTED). isupport/registration asserts + 10 CHANMODES/
MYINFO snapshots regenerated for the new M token.
roleplay_proptest: compose is total/never-panics over spicy nick+text input, the
display nick keeps no control char but the deliberate \x1F underline pair, ACTION
bodies are \x01ACTION…\x01, the (author) attribution is always present, and
underline only wraps the same inner nick. golden_roleplay: NPC underlined fake
nick + SCENE =Scene= delivered to a co-member, inverse -N channel → 573.
Port of charybdis extensions/m_roleplay.c. On a +N channel a member speaks from a
fake nick <nick>!<author>@npc.fakeuser.invalid: NPC (underlined), NPCA (action),
SCENE/AMBIANCE (fixed =Scene=), FSAY/FACTION (clean nick for opers). The pure
roleplay::compose seam does the charybdis strip_unprintable + underline + ACTION
wrap; the gate ladder reuses display_name/is_member/roleplay_enabled/can_send,
fanning to all local members (incl. issuer) and relaying via s2s::roleplay. New
numeric 573 ERR_ROLEPLAY; six help pages + COMMANDS allowlist.
Wire ChanMode::Roleplay (letter +N, bit 0x1_0000_0000) through the channel-mode
checklist (enum/ALL/as_char/bit/mode_name/from_char) + the Channels::roleplay_enabled
accessor. +N appears in CHANMODES/MYINFO; isupport asserts + golden snapshots
regenerated for the new token. The commands that use it land next.
The IRCnet-derived 32-bit channel-flags space is full (ExtendLimit = 0x1, the
last free bit). Widen ChannelModes.flags, ChanMode::bit(), MemberStatus.bits and
the default-channel-modes plumbing (config/control/server/chants) to u64 so
leveva-native flags can be added past the 32-bit boundary. No behaviour change.
Port of charybdis extensions/extb_combi.c: two extban operator types whose
data is a comma-separated list of child extbans ([~]<type>[:<data>], no leading
$), optionally paren-wrapped, with paren-aware comma splitting and backslash
escaping. $& matches iff all children match; $| iff any does. Children may
nest (depth cap 5, <=10 children/node, data <= BANLEN 195).
A faithful byte-walk of eb_combi in extban/combi.rs dispatching to the existing
eval_type handlers — the C global recursion_depth is threaded as a depth param
(leveva runs many connections). Preserves the two charybdis subtleties: a child
type is validated for existence always (even when short-circuited, via the new
is_known_type), but child data only up to the short-circuit point, so combiban
validity is subject-dependent.
extban_chars() -> "&acjmorsxz|" so 005 advertises EXTBAN=$,&acjmorsxz|.
Divergence: leveva's $m is usermode (not charybdis's hostmask), so host
matching inside a combiban uses $x:<nick!user@host>#*.
Tests: 11 combi units (AND/OR/negated/nested/paren+escaped-comma + inverses for
every malformed shape, short-circuit type validation, the caps), extban_combi_
proptest (6 props: totality, AND==all/OR==any model vs an independent fold,
single-child==bare, De Morgan), golden_extban_combi (JOIN gates biting only
after OPER + the 005 token). isupport/elemental_extban/mod.rs asserts updated;
9 EXTBAN-token snapshots regenerated. EXTBANS.md documents the operators.
Port charybdis extensions/umode_noctcp.c to a leveva user mode +C: a
self-settable umode that refuses CTCP queries (other than ACTION) sent to the
user as a PRIVMSG, replying 531 ERR_CANNOTSENDTOUSER :+C set. NOTICE, CTCP
replies, and ACTION always pass. The recipient-side private-message counterpart
of the channel +C (slice 220), reusing the fuzzed channel::is_blocked_ctcp.
Pure seams mode::is_noctcp + mode::noctcp_user_blocks (the composite gate
decision), enforced home-server-local on both the local command/message.rs path
and the inbound-S2S s2s/relay::inbound_message path (new noctcp_reject_remote
routes 531 to the sender's uplink, the +R 248->249 split precedent). The bit
still propagates (slice-254 "no local-only umodes"), so WHOIS shows +C. Bit
0x80000, numeric 531; umode_diff/RENDERED/004-literal/snapshot wiring per the
user-mode checklist.
Tests: mode seam units, command self-settable + inverse, message gate +
inverses, s2s inbound + inverses, golden_noctcp_umode, noctcp_umode_proptest
(5 props). 5 welcome snapshots regenerated (004 gains C).
Close the slice-301 follow-on: a webirc {} trusted-gateway block was parsed
once at boot into ctx.stats_conf.webirc and never re-read, so adding, editing,
or removing a trusted webchat gateway required a restart. Carry the gateway
list through the control::ConfStore live-config chain (mirroring how slice 303
made alias {} reloadable) and read it live-or-ctx at session::handle_webirc, so
REHASH hot-applies an edited webirc set with no restart.
- control.rs: ConfBlocks.webirc + config_conf extraction; ConfStore.webirc
ArcSwapOption + new()/seed() + webirc() accessor + live_webirc() global.
- main.rs: seed the live store with config.webirc at boot.
- session.rs::handle_webirc: read gateways live-or-ctx (live store wins; a
session that never ran main falls back to the immutable boot list).
Tests (TDD; inverse invariants): control units (fresh none / no-block empty /
seeded reads back / empty-seed clears the stale gateway); golden_rehash_webirc
(no-block refuse -> add+REHASH spoof -> remove+REHASH refuse again); the
rehash_conf_proptest reload model extended with a generated webirc set
(live (label,host) == last successful reload; None while unseeded).
An alias { target "user@server" } block now resolves the server part to a
linked remote server and routes :<uid> PRIVMSG user@server :<text> to its
uplink, matching charybdis m_alias's find_server branch (was: 440). Unknown or
our-own server name -> 440, empty text -> 412. Pure fuzz seam
alias::remote_privmsg_wire (totality + round-trip proptest); S2S golden +
unit inverses.
Port charybdis's alias{} mechanism (modules/m_alias.c + newconf.c alias_entry):
an `alias "<name>" { target "<t>" }` block makes <name> a command that rewrites
`<name> <text...>` into `PRIVMSG <target> :<text...>`, delivered through the message
plane. A real command always takes precedence (the hook is the existing 421 fallback).
Found by comparing against a charybdis checkout: the 'configurable IDENTIFY nicks'
originally scoped does not exist in charybdis (the nicks are hardcoded #defines); the
alias{} block is the real, faithful, more general mechanism.
- alias.rs pure seam: parse_target (split on first @ -> nick vs user@server),
combined_args / reconstruct; fuzzed by tests/alias_proptest.rs.
- new top-level alias{} config block (model/parse/from_kdl), held live + REHASH-able
through ConfStore (live_aliases, the service-string chain); main.rs boot seed.
- command/alias.rs: deliver_to_service_nick shared core (target must be a present +S
service else 440 ERR_SERVICESDOWN; empty text -> 412; else synth PRIVMSG +
message::message, so a remote service is reached by UID routing). new numeric 440.
- folds in the slice-302 IDENTIFY faithfulness fix the comparison surfaced: empty args
-> 412 (was 461), absent/non-+S agent -> 440 (was 401), via the shared core.
- user@server target parsed/fuzzed but not yet routed (-> 440, documented follow-on).
Tests: alias seam + command + config-parse units; identify units updated; extended
rehash_conf_proptest alias reload; golden_alias (S2S +S burst: route + 440 + 421
inverses); golden_identify rewritten (S2S +S NickServ/ChanServ).
cargo test -p leveva green / clippy clean / build --workspace 0 warnings.
A port of charybdis's extensions/m_identify.c, the client-facing companion
to the services/SASL cluster (296-299): a registered client types
`IDENTIFY [<target>] <password...>` and the server rewrites it into a PRIVMSG
to a services pseudo-agent (ChanServ when the first argument is a channel,
else NickServ), delivered through the ordinary message plane.
- leveva/src/identify.rs: the pure, fuzzable rewrite seam
`rewrite(params) -> Option<Rewrite>` (target by params[0]'s '#', verbatim
args re-joined after the keyword, empty first arg -> 461).
- leveva/src/command/identify.rs: synthesizes the PRIVMSG and delegates to
message::message, so the agent sees the real source mask and an unlinked
services sender gets the plane's own 401. Dispatch + HELP IDENTIFY.
leveva-native sugar: hardcoded service nicks, registered-clients-only, no
state and no S2S of its own. Units + golden_identify + identify_proptest.
Port charybdis extensions/m_webirc.c: a trusted webchat gateway sends a
pre-registration WEBIRC <password> <gateway> <hostname> <ip> so the proxied
connection is recorded under the real client's host/IP, not the gateway's.
Every later admission gate (D/K/X-line, config-ban, per-class conn caps, +x
cloak) and WHOIS key off the spoofed self.host.
- new pure fuzz seam leveva::webirc {parse_request, spoof_host, authorize}
- webirc {} config block (host mask + password) -> StatsConf (boot-read)
- Session::handle_webirc dispatched phase-independently (CAP/AUTHENTICATE seam),
one-spoof-per-connection, +s audit snotice; registered -> 462, bad-pw /
untrusted host / malformed -> NOTICE, no spoof
- HELP WEBIRC topic + commented example/dist config block
Divergences (leveva-native, no oracle): single-host/no-DNS collapse of
charybdis host/sockhost; trust via webirc {} not auth{}; boot-read.
Tests: webirc/config/session units (inverses), golden_webirc end-to-end,
webirc_proptest (parse total + round-trip, spoof_host range, authorize
contract).
The read-state companion to draft/chathistory (slice 274). MARKREAD
<target> [timestamp=<iso8601>] records or queries how far a client has
read a target; a set stores max(existing, new) (monotonic — never
regresses), echoes the effective value, and on a genuine advance fans
the MARKREAD to the user's other capable connections (cross-device
read-state sync). A bare MARKREAD <target> queries the stored marker.
Markers are owned by the services account (synced across connections,
survives a reconnect) when logged in, else by the connection UID
(per-connection, dropped on Session::release). The cap is statically
advertised and valueless; the command is processed regardless of the
issuer's own cap — the cap gates only the cross-connection fan, per spec.
leveva-native: the C 2.11 oracle predates IRCv3, so the gate is unit +
golden + proptest (no differential).
- readmarker.rs: pure parse/format seam (parse_set_timestamp /
format_value) + the monotonic ReadMarkers store (account|UID owner key)
- command/markread.rs: the handler (NEED_MORE_PARAMS / query / set /
INVALID_PARAMS / advance + fan)
- cap.rs: DRAFT_READ_MARKER const + SUPPORTED entry + read_marker
ClientCaps bit; CAP LS snapshots refreshed (a long SASL mechlist now
tips the 302 LS into a spec-legal multiline split)
- server.rs: read_markers field; session.rs: forget_connection release hook
- help/MARKREAD.md + COMMANDS allowlist + index.md Queries line
- tests: readmarker/markread units (inverse invariants), golden_markread
(single-conn round-trip + cross-device push vs uncapped sibling),
readmarker_proptest (total parse + round-trip; advance keeps-max,
order-independent + idempotent)
Slice 295 added the CYCLE command and listed it on the help index
"Channel commands:" line, but the golden_help_users snapshot was not
regenerated, leaving the test red. Accept the current output (adds
CYCLE) to restore parity.
Assisted-by: Claude Opus 4.8 via Claude Code
Signed-off-by: Xe Iaso <me@xeiaso.net>
A linked SASL services agent announces its mechanisms via
`:<svcSID> ENCAP * MECHLIST :<comma-list>`; leveva learns the list, advertises
it (client-facing `sasl=` cap value + `908 RPL_SASLMECHS`) instead of the
hardcoded `PLAIN,EXTERNAL`, and relays whichever advertised mechanism a client
picks — faithful charybdis transparent relay (special-casing only EXTERNAL for
the CertFP; an unadvertised mechanism → 908+904, never relayed).
- sasl.rs: pure fuzzed `parse_mechlist`; `Mechanism` reworked
`{Plain,External,Other(Box<str>)}` with `from_advertised`/`name`/`is_external`.
- s2s/links.rs: PeerLinks learned-mechlist store, cleared when the last sasl
agent unlinks.
- s2s/mechlist.rs (new): `apply_encap_mechlist`, dispatched in forward.rs's
interpret-and-onward-relay block (broadcast, like SU).
- cap.rs: `apply`/`ls`/`req` `sasl_available: bool` → `sasl_mechs: Option<&str>`.
- session.rs: AUTHENTICATE gate uses `from_advertised`; 908 lists the advertised
set; relay_start takes &Mechanism.
leveva-native (the C 2.11 oracle predates SASL). Tests cover the happy path and
every inverse invariant (unadvertised rejected, all-junk keeps the prior list,
unlink clears, default fallback) + 2 proptests.
An inbound ENCAP <us> SVSLOGIN <cliUID> <nick> <user> <host> <account> previously
honored only <account>; <nick>/<user>/<host> were parsed but used merely cosmetically
in the 900 RPL_LOGGEDIN. Now non-* <user>/<host> are applied as a services vhost on SASL
login (the HostServ-vhost-on-login case); * / empty / IRC-invalid = unchanged.
- sasl.rs: pure fuzzed SvsIdentity::from_fields (validated via UserName/HostName::try_from);
SaslEntry.identity; SaslSessions::{set_identity,identity}; remove -> SaslResolution{account,identity}.
- s2s/sasl.rs apply_encap_svslogin: stash the identity alongside the account.
- session.rs finalize_registration: drain the resolution before the cloak block, commit the
vhost (overriding auto-cloak) as a DISPLAY identity after all host-based admission gates ran
on the real user@host, before local_introduce so UNICK/welcome carry it; orighost kept real.
The <nick> field is deliberately not applied (no pre-introduction rename primitive; real flows
send *; SANICK forces a nick). leveva-native; TDD + inverse invariants + golden + proptest.
leveva used UID sources + UID targets for member ops, but emitted the typed NICK
in the target slot of user-directed messages (PRIVMSG/NOTICE/TAGMSG). Flip the two
outbound emit call sites (command/message.rs route_message_to_remote,
command/tagmsg.rs route_tagmsg_to_remote) to pass the recipient's UID, making the
wire fully UID-consistent like TS6.
Deliberate protocol divergence (every peer must parse the new form). Receive side
already UID-ready (slice 296 resolve_user_target + nick_of final-hop render +
verbatim onward relay). Channel/opmod/$$/$# mask targets, the sender's local echo,
and the final-hop client rendering are unchanged -- clients still see nicks.
Tests/snapshots flipped to UID targets; docs/s2s.md updated. Live-verified: leveva
emits ':0LVAAAAAA PRIVMSG 0ATHAAAAG :HELP' to atheme; the client still sees
'NOTICE probe'. Built via an ultracode discover->implement->verify workflow.
An atheme protocol module (contrib/atheme/leveva.c) letting a services package
(NickServ/ChanServ/OperServ/SaslServ/...) link to a leveva server. Hybrid of
atheme's ircnet burst skeleton (PASS/SERVER/UNICK/NJOIN/SAVE/EOB) and ts6-generic
ENCAP grafts (account login SU, SASL relay, SVSLOGIN, CHGHOST, K/D/X-line + RESV,
ENCAP * CAPAB :sasl). Standalone (not ts6-generic -- leveva is not TS6); UID via
protocol/base36uid (alphabet matches leveva). Full elemental rank set incl. +Y/!
official-join as CSTATUS_IMMUNE.
Verified live: compiles clean, links + syncs with leveva, services burst,
NickServ account login propagates via ENCAP * SU. Ships README + example leveva
and atheme link configs. Authored by Midori Yasomi.
Inbound S2S handlers resolved a user target via registry.uid_of (nick-only),
so a peer addressing a local client by its UID (':0ATH.. NOTICE 0LVA.. :..',
how every services package / IRC server addresses a user) was silently dropped.
Add one pub(crate) resolve_user_target (nick-first, then Uid::try_from accepted
only when it names a known user) and route inbound_message, inbound_tagmsg,
inbound_numeric (relay.rs), apply_encap_redact (redact.rs), and inbound_kill
(kill.rs -- services KILL by UID) through it. Each keeps its is_local_uid guard
so a remote UID is reached only by the onward split-horizon relay. UID-addressed
delivery now renders the client's display nick. inbound_numeric drops an unknown
well-formed UID (documented 'unknown target is dropped' contract).
15 TDD tests (inverse invariants) across s2s::{relay,redact,kill}; built via an
ultracode discover->implement->verify workflow.
Append the slice-289 XLINE/UNXLINE entry to the D-line claude-memory file:
the realname (gecos) ban that completes the runtime ban quartet K/D/X/RESV,
built bolt-for-bolt on the DLINE plane with a single-token gecos matcher. Notes
the key divergence — XLINE uses a dedicated xline_exempt (the now-lit-up
CFLAG_XEXEMPT), not the shared kline_exempt — and that the charybdis runtime
ban family is complete.
Assisted-by: Claude Opus 4.8 via Claude Code
Signed-off-by: Xe Iaso <me@xeiaso.net>
charybdis-style `CYCLE <channel>` (extensions/m_cycle.c): part and rejoin a
channel *without races*. Faithful to the real m_cycle, the effect is entirely
client-side for the issuer — `:source PART <chan> :Cycling`, `:source JOIN
<chan>`, then a fresh 353/366 — and nothing changes server-side, so the user
keeps op/voice and place and cannot be locked out by +i/+k/+l or lose a glare.
No new Channels seam (CYCLE never mutates): it composes the read-only
display_name/is_member/names_query accessors. Per-channel gate ladder:
461 no param → 403 nonexistent → 442 not-a-member → success, existence before
membership; the re-sent NAMES honours the issuer's multi-prefix/userhost-in-names.
Divergences (leveva-native, no oracle): self-only/observationally-pure (NOT
member-visible — that would drop ops, destroy a sole-member channel, and desync
peers), fixed :Cycling reason, local-only.
7 command units + cycle_proptest (observational-purity + arbitrary-args) +
golden_cycle; CYCLE.md help + COMMANDS allowlist + index.md.
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.
charybdis extensions/m_olist.c: the operator variant of LIST that bypasses the
secret (+s) / private (+p) channel-hiding so an oper enumerates every channel,
including the hidden ones a normal LIST skips. The read-only sibling of LIST and
the latest member of the charybdis operator diagnostic family
(TESTLINE/TESTMASK/MASKTRACE/CHANTRACE/FINDFORWARDS/OPME).
- Channels::list_all() (full folded sweep) + list_one_any(name) (named incl.
hidden) — the visibility-bypass seams, no filter; also the fuzz seam.
- command/olist.rs: plain oper-bit gate (non-oper 481 before any sweep), 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).
- Wired "OLIST" + mod olist; help/OLIST.md + COMMANDS allowlist + index.md.
Divergences (leveva-native, no oracle): plain oper-bit gate (not a per-command
privilege), no +s audit snomask (read-only diagnostic family precedent),
local-only.
Tests: list_all/list_one_any units; 6 command units; olist_proptest (full-set
+ superset-of-list model equality, arbitrary_args_never_panic); golden_olist
(LIST hides #secret, OLIST reveals it, non-oper 481). Regenerating
golden_help_users also picks up the pre-existing stale operator-index line
(OPME/FINDFORWARDS from slices 291/292).
cargo test -p leveva green; clippy clean; build --workspace 0 warnings.