feat(leveva): D-lines (DLINE/UNDLINE) — IP-level operator bans (P11 slice 284)
charybdis-style D-lines, the IP/CIDR sibling of the temporary K-line family
(slices 281–283): `DLINE <duration> <ip/cidr> [:reason]` bans a raw IP or CIDR
block, `UNDLINE <ip/cidr>` lifts it. Built as a near-clone of the K-line plane
keyed on a single IP/CIDR `mask` (matched with the CIDR-aware
`matching::host_component_matches`) instead of `user@host`.
- `dline.rs`: `DlineStore` mirrors `KlineStore` (Vec+Mutex, NOCASE dedup,
`find_active`, same `database {}` SQLite write-through + boot reload as slice
282); `Dline::covers` = `host_component_matches`. Reuses the kline duration
grammar/clock/clamp.
- `command/dline.rs`: `dline`/`undline` mirror `tkline`/`untkline` (461→481 gate,
empty/`*` mask → Incorrect format, no success reply); `reap_matching_ip` ejects
local matching clients by `orighost` (real connect IP), skipping remote +
`kline-exempt`. New `OperPrivilege::Dline` (bit 0x400000).
- `session.rs`: registration gate checked before the K-line gate, matching the
pre-cloak connect IP → 465+ERROR+REJ snomask, never counted/claimed.
- `s2s/dline.rs`+`forward.rs`+`burst.rs`: `ENCAP * DLINE`/`UNDLINE` propagation
(server-prefixed, remaining-seconds, slice-281 KLINE shape) + burst
re-assertion.
- `stats.rs`: `STATS d` → `250 RPL_STATSDLINE` via `dline_report`.
- `server.rs`: `ctx.dlines` opens against the same `database {}` file (persists
across restart); boot test + inverse extended.
- help `DLINE.md`/`UNDLINE.md` + COMMANDS allowlist.
Tests (TDD, inverse invariants, mandated fuzzing): unit+proptest per module;
`tests/golden_dline.rs` (real binary, `dline.kdl` fixture) and
`tests/dline_proptest.rs` (6 properties incl. model-lockstep registration gate
over `Session::feed`). leveva-native, no oracle differential.
`cargo test -p leveva` green (2530 lib); clippy clean; workspace 0 warnings.
feat(leveva): +j join throttle channel mode — P11 slice 234
Charybdis-style `+j n:t` (MODE_JOINTHROTTLE): a channel admits at most n
joins per t-second window; the next join bounces 480 ERR_THROTTLE and is
not added. The first parameter-bearing flag mode since +l/+k and the first
JOIN-rate gate; continues the channel-mode parity track (220 +C, 232 +c,
233 +S). leveva-native — gate is unit + golden + proptest.
- Pure core join_throttle.rs: parse_param(n:t) + format_param + the charybdis
fixed-window decide() (both fuzz seams); JoinThrottle counter store (reads no
clock, self-draining) housed inside Channels (zero ServerContext-literal churn).
- ChanMode::JoinThrottle ('j', bit 0x8000000, Param kind) threaded through both
mode parsers + the three ModeChange matches + 324/CHANMODES-type-C/004 render.
- JOIN gate in command::join::join_one: non-members, local path only (remote
NJOIN gated at its home server); SAJOIN bypasses; refusal -> 480 ErrThrottle.
- Tests: 12 core units + 2 channel + 1 mode units; jointhrottle_proptest (5,
incl. no-window-over-admits) + golden_jointhrottle; 8 005/004 snapshots
regenerated (CHANMODES ...,k,lj,... / 004 ...IRklj, token-only).
feat(leveva): burst empty +P permanent channel over S2S — stale-S2S-flag-allowlist fix — P11 slice 269
Closes slice 244's documented "Bursting empty permanent channels on
link" follow-on. A +P channel survives memberless so its topic/modes
persist, but a freshly-linked peer's NJOIN carries only the
empty-channel placeholder (.), so the receiver dropped the whole channel
block (CHANTS on an unknown channel was a no-op, the . NJOIN added
nobody, and the following MODE/TOPIC landed on a nonexistent channel).
Receiver-only fix: s2s::chants::apply_encap_chants now materializes the
empty channel from the burst CHANTS line via the new
Channels::create_permanent_from_burst, gated on +P (pure
channel::burst_creates_permanent, the inverse of destroy_when_empty) so
a leaky non-permanent empty channel is never created and a known channel
is never clobbered.
Also fixes a root-cause sub-bug surfaced while scoping: the S2S
burst/MODE parser s2s::mode::supported_flag carried a stale nmtipsO-only
flag-letter allowlist that silently dropped every leveva-native channel
flag (+C +c +S +z +T +g +r +Q +F +P +L) off a server burst/MODE, so
they never replicated across a link. Both the client and S2S parsers now
delegate to one canonical ChanMode::flag_from_char so they can never
drift again.
Tests (TDD + fuzz): golden_s2s_permanent (MODE->324 +Pkl, TOPIC->332,
inverse non-+P->403); unit tests in channel/chants/mode; permanent_proptest
extended (burst_creates_permanent totality+inverse, create_permanent_from_burst
gated/never-clobbers).
feat(leveva): TESTMASK mask-population diagnostic (P11 slice 288)
TESTMASK <[nick!]user@host> [<gecos>] counts how many connected clients match
a hostmask (and optional realname glob), split into local vs remote — the
read-only population sibling of TESTLINE (slice 285, which reports bans),
together completing charybdis's test* diagnostic family.
461 param gate before the plain oper-bit gate (481); a malformed mask (no @, or
empty user/host) yields a NOTICE :Invalid parameters (faithful to charybdis's
sendto_one_notice, not a numeric); a registry sweep glob-matches
nick/user/host(+orighost)/gecos (nick & gecos default *) and tallies
is_local_uid into one 727 RPL_TESTMASKGECOS reply. New numeric 727 (charybdis's
724 is dead — no format string, no caller). Pure parse_mask fuzz seam.
Confirmed against cloned charybdis m_testmask.c / messages.h / numeric.h.
leveva-native, local-only (no S2S, mirroring charybdis).
Tests: 13 units (inverse filters + quit-drops-count), golden_testmask (real
binary), testmask_proptest (4 properties incl. l+g <= population, never panics).
feat(p10): bootstrap leveva crate with RFC 1459 string layer
Stand up the greenfield idiomatic-Rust crate (named per blog 001 / the P10
plan) with its foundational string layer — pulled forward at the user's
request now that P7 (faithful port) is complete. Std-only, no oracle/FFI seam.
- casemap: textbook RFC 1459 case folding (A-Z<->a-z plus the Scandinavian
[<->{ \<->| ]<->} ~<->^), as const fns + slice eq/cmp/hash helpers. This is
the rule the RFC specifies, which deliberately differs from this daemon's
ASCII-only tolowertab in common/match.c.
- string: IrcStr/IrcString with RFC-1459 case-insensitive Eq/Ord/Hash (Display
preserves original case), Borrow<IrcStr> for HashMap keying, NUL/CR/LF
rejection, and IrcStringBuilder.
- ident: validating newtypes via an ident_newtype! macro — Nick, ChanName,
Sid/Uid/Cid (base-36), ServerName, UserName, HostName — each grounded in the
C grammar (struct_def.h lengths, char_atribs NVALID, do_nick_name, s_id.c).
- message: Message/MessageBuilder assembling 512-byte-safe CRLF wire lines;
typed idents pass through via impl Display.
27 unit + 4 doc tests green; clippy clean; 0 warnings.