Commits
Update the series skill to match the de-asided posts and the audit
findings.
- Replace the "character asides as blockquotes" convention with an
explicit no-asides rule; both technical tangents and reaction beats
go inline in one first-person voice.
- Drop the now-obsolete blockquote clause from the hard paragraph rule.
- Expand the sound-like-a-person step with the five recurring tells the
series-wide audit surfaced, each with its fix.
- Add reaching-for-asides and the five tells to the anti-patterns.
Assisted-by: Claude Opus 4.8 via Claude Code
Signed-off-by: Xe Iaso <me@xeiaso.net>
Two voice changes applied across all 22 posts in the series.
First, a sound-like-a-person pass: a series-wide audit found five
recurring AI-shaped tells clustered at section seams — balanced
"not X, it's Y" antitheses, restate-the-point closers, recap
tricolons, honesty-signaling, and meta-narration. Flattened the
constructed instances while keeping the lived-in, opinionated spine.
Second, removed the Cadey/Mara blockquote asides (70 of them) and
folded each into the surrounding first-person narrative. Technical
tangents (file:line anchors, symbol names, the C-vs-Rust details)
and reaction beats now read as continuous prose. Re-checked the
no-successive-same-opening-letter rule after both passes.
Assisted-by: Claude Opus 4.8 via Claude Code
Signed-off-by: Xe Iaso <me@xeiaso.net>
The next post in the C->Rust port series. Covers leveva's
charybdis-style extbans: how a ban stops being a static
nick!user@host string and becomes a predicate the server
evaluates fresh each time. Walks the three-state Verdict
(Invalid as a property of the mask), the JOIN-gate plus
speech-plane enforcement, the questions too unsafe to invert
into an exception ($r/$c), the $&/$| combinators, and the
documented divergences ($m=usermode, no $q quiet list). No
oracle behind it: ported from Elemental-IRCd by reading.
Assisted-by: Claude Opus 4.8 via Claude Code
Signed-off-by: Xe Iaso <me@xeiaso.net>
The native-async-iauth post: the standalone iauth (post 005) and the
fork/execl launcher (post 013) both existed only to keep blocking auth
probes off the C ircd's single-threaded select loop. Under leveva's tokio
runtime a blocking probe is just an `.await`, so the whole process boundary
— pipe protocol, socketpair, launcher — is deleted, not ported. Covers the
two-method Module trait (the pipe-protocol baggage that fell off), the
concurrent-spawn + two-stash try_finalize seam (order-independent finalize),
the `~` username rule, the process-vs-task isolation trade given up on
purpose, and (like 019/020) trusting it with the oracle already switched off.
Assisted-by: Claude Opus 4.8 (claude-opus-4-8)
Signed-off-by: Xe Iaso <me@xeiaso.net>
The P8v finale: the actual switch-off of the differential oracle. Two
commits (P8u drops all C compilation, P8v deletes the C source tree +
freezes bindings.rs into ircd-common via the extern-crate-self alias),
the irreversibility of running the oracle for the last time, the final
run's one red being the reference's own uninitialized-sendq STATS bug
(Rust is the correct side), and the honest downgrade from differential
to snapshot (weaker net, sufficient once faithfulness stops being the
goal). Grounded in docs/progress-log/p8.md + commits c1f72bb6/b88264f3.
Assisted-by: Claude Opus 4.8 (claude-opus-4-8)
Signed-off-by: Xe Iaso <me@xeiaso.net>
Post 19 — the first leveva-feature post with no oracle behind it. Netsplit-merge
arbitration: channel-TS and user-TS, oldest (lowest) timestamp wins by every
server independently comparing the same two integers (no coordinator, the whole
consensus protocol is `their_ts < our_ts`). The dark turn: the loser is wiped, not
merged — younger chanops deopped, the loser's +b/+e/+I AND +R lists wiped wholesale
(the reop-list hard rule I overruled myself on, plus the EOB reop sweep that fix
forced), and a younger nick renamed to its UID via the cross-task ForceNick
primitive. The equal-timestamp SaveBoth case (tie = mutual destruction, nick
vacated). Honest close: no differential here — confidence comes from proptests,
the boot-golden, and matching the decades-old TS6 design intent.
Assisted-by: Claude <noreply@anthropic.com>
Signed-off-by: Xe Iaso <me@xeiaso.net>
Post 018 narrates the roadmap pivot: with all C deleted (P8v), the planned
in-place idiomatic cleanup (P9) is retired in favor of a greenfield rewrite
(leveva) pinned differentially against the now-finished faithful port, which
becomes the behavioral oracle. The strangler pattern re-aimed one level up.
Assisted-by: Claude (claude-opus-4-8)
Signed-off-by: Xe Iaso <me@xeiaso.net>
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.
Update the series skill to match the de-asided posts and the audit
findings.
- Replace the "character asides as blockquotes" convention with an
explicit no-asides rule; both technical tangents and reaction beats
go inline in one first-person voice.
- Drop the now-obsolete blockquote clause from the hard paragraph rule.
- Expand the sound-like-a-person step with the five recurring tells the
series-wide audit surfaced, each with its fix.
- Add reaching-for-asides and the five tells to the anti-patterns.
Assisted-by: Claude Opus 4.8 via Claude Code
Signed-off-by: Xe Iaso <me@xeiaso.net>
Two voice changes applied across all 22 posts in the series.
First, a sound-like-a-person pass: a series-wide audit found five
recurring AI-shaped tells clustered at section seams — balanced
"not X, it's Y" antitheses, restate-the-point closers, recap
tricolons, honesty-signaling, and meta-narration. Flattened the
constructed instances while keeping the lived-in, opinionated spine.
Second, removed the Cadey/Mara blockquote asides (70 of them) and
folded each into the surrounding first-person narrative. Technical
tangents (file:line anchors, symbol names, the C-vs-Rust details)
and reaction beats now read as continuous prose. Re-checked the
no-successive-same-opening-letter rule after both passes.
Assisted-by: Claude Opus 4.8 via Claude Code
Signed-off-by: Xe Iaso <me@xeiaso.net>
The next post in the C->Rust port series. Covers leveva's
charybdis-style extbans: how a ban stops being a static
nick!user@host string and becomes a predicate the server
evaluates fresh each time. Walks the three-state Verdict
(Invalid as a property of the mask), the JOIN-gate plus
speech-plane enforcement, the questions too unsafe to invert
into an exception ($r/$c), the $&/$| combinators, and the
documented divergences ($m=usermode, no $q quiet list). No
oracle behind it: ported from Elemental-IRCd by reading.
Assisted-by: Claude Opus 4.8 via Claude Code
Signed-off-by: Xe Iaso <me@xeiaso.net>
The native-async-iauth post: the standalone iauth (post 005) and the
fork/execl launcher (post 013) both existed only to keep blocking auth
probes off the C ircd's single-threaded select loop. Under leveva's tokio
runtime a blocking probe is just an `.await`, so the whole process boundary
— pipe protocol, socketpair, launcher — is deleted, not ported. Covers the
two-method Module trait (the pipe-protocol baggage that fell off), the
concurrent-spawn + two-stash try_finalize seam (order-independent finalize),
the `~` username rule, the process-vs-task isolation trade given up on
purpose, and (like 019/020) trusting it with the oracle already switched off.
Assisted-by: Claude Opus 4.8 (claude-opus-4-8)
Signed-off-by: Xe Iaso <me@xeiaso.net>
The P8v finale: the actual switch-off of the differential oracle. Two
commits (P8u drops all C compilation, P8v deletes the C source tree +
freezes bindings.rs into ircd-common via the extern-crate-self alias),
the irreversibility of running the oracle for the last time, the final
run's one red being the reference's own uninitialized-sendq STATS bug
(Rust is the correct side), and the honest downgrade from differential
to snapshot (weaker net, sufficient once faithfulness stops being the
goal). Grounded in docs/progress-log/p8.md + commits c1f72bb6/b88264f3.
Assisted-by: Claude Opus 4.8 (claude-opus-4-8)
Signed-off-by: Xe Iaso <me@xeiaso.net>
Post 19 — the first leveva-feature post with no oracle behind it. Netsplit-merge
arbitration: channel-TS and user-TS, oldest (lowest) timestamp wins by every
server independently comparing the same two integers (no coordinator, the whole
consensus protocol is `their_ts < our_ts`). The dark turn: the loser is wiped, not
merged — younger chanops deopped, the loser's +b/+e/+I AND +R lists wiped wholesale
(the reop-list hard rule I overruled myself on, plus the EOB reop sweep that fix
forced), and a younger nick renamed to its UID via the cross-task ForceNick
primitive. The equal-timestamp SaveBoth case (tie = mutual destruction, nick
vacated). Honest close: no differential here — confidence comes from proptests,
the boot-golden, and matching the decades-old TS6 design intent.
Assisted-by: Claude <noreply@anthropic.com>
Signed-off-by: Xe Iaso <me@xeiaso.net>
Post 018 narrates the roadmap pivot: with all C deleted (P8v), the planned
in-place idiomatic cleanup (P9) is retired in favor of a greenfield rewrite
(leveva) pinned differentially against the now-finished faithful port, which
becomes the behavioral oracle. The strangler pattern re-aimed one level up.
Assisted-by: Claude (claude-opus-4-8)
Signed-off-by: Xe Iaso <me@xeiaso.net>
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.