feat(leveva): charybdis-style extended bans (P11 slice 164)
Add `+b`/`+e`/`+I` ban-list entries of the form `$[~]<type>[:<data>]`
that match a richer client predicate than a plain nick!user@host glob,
ported faithfully from Elemental-IRCd (doc/technical/extban.txt,
src/extban.c, extensions/extb_*.c).
A new `leveva::extban` module holds the framework (the $/~/: parse,
Verdict/ModeType/Subject/ChannelView, match_extban/valid_extban/
entry_matches/extban_chars) plus one handler file per type: $a account,
$o oper, $z ssl, $r realname, $x extgecos, $s server, $c on-channel,
$j banned-from-channel, $m usermode. The nine handlers were implemented
in parallel via a multi-agent workflow, one per extb_*.c.
Extbans are enforced at the JOIN gate: check_join's last param is now
&extban::SubjectAttrs (was is_oper: bool), and its +b/+e/+I iteration
runs through extban::entry_matches against a Subject built in place. A
private LiveChannels ChannelView adapter resolves $c/$j from the
already-locked channel map (no re-lock) with a depth-1 recursion guard
for nested $j. join.rs builds SubjectAttrs from the registry (account,
certfp->ssl, realname, modes->oper/umodes) plus the server name.
ISUPPORT advertises EXTBAN=$,acjmorsxz (re-chunking nine existing 005
golden bursts, content-identical).
Divergences (leveva-native, documented): JOIN-gate-only enforcement
(no PRIVMSG/quiet plane; +q is the slice-158 owner mode), $s matches
the joiner's server (= me.name for a local JOIN), $j caps at depth 1,
$x has no DynSpoof retry, and add-time validity rejection is deferred.
Tests: 97 extban unit tests, elemental_extban_proptest (parser
totality, negation duality, $a model, extban_chars invariant), and a
real-binary golden_extban.
Assisted-by: Claude Opus 4.8 via Claude Code
Signed-off-by: Xe Iaso <me@xeiaso.net>
feat(leveva): combination extbans $& (AND) / $| (OR) (P11 slice 307)
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.
feat(leveva): combination extbans $& (AND) / $| (OR) (P11 slice 307)
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.
feat(leveva): charybdis-style extended bans (P11 slice 164)
Add `+b`/`+e`/`+I` ban-list entries of the form `$[~]<type>[:<data>]`
that match a richer client predicate than a plain nick!user@host glob,
ported faithfully from Elemental-IRCd (doc/technical/extban.txt,
src/extban.c, extensions/extb_*.c).
A new `leveva::extban` module holds the framework (the $/~/: parse,
Verdict/ModeType/Subject/ChannelView, match_extban/valid_extban/
entry_matches/extban_chars) plus one handler file per type: $a account,
$o oper, $z ssl, $r realname, $x extgecos, $s server, $c on-channel,
$j banned-from-channel, $m usermode. The nine handlers were implemented
in parallel via a multi-agent workflow, one per extb_*.c.
Extbans are enforced at the JOIN gate: check_join's last param is now
&extban::SubjectAttrs (was is_oper: bool), and its +b/+e/+I iteration
runs through extban::entry_matches against a Subject built in place. A
private LiveChannels ChannelView adapter resolves $c/$j from the
already-locked channel map (no re-lock) with a depth-1 recursion guard
for nested $j. join.rs builds SubjectAttrs from the registry (account,
certfp->ssl, realname, modes->oper/umodes) plus the server name.
ISUPPORT advertises EXTBAN=$,acjmorsxz (re-chunking nine existing 005
golden bursts, content-identical).
Divergences (leveva-native, documented): JOIN-gate-only enforcement
(no PRIVMSG/quiet plane; +q is the slice-158 owner mode), $s matches
the joiner's server (= me.name for a local JOIN), $j caps at depth 1,
$x has no DynSpoof retry, and add-time validity rejection is deferred.
Tests: 97 extban unit tests, elemental_extban_proptest (parser
totality, negation duality, $a model, extban_chars invariant), and a
real-binary golden_extban.
Assisted-by: Claude Opus 4.8 via Claude Code
Signed-off-by: Xe Iaso <me@xeiaso.net>