P11 slice 140 — account-notify (IRCv3)#
Area. account-notify / SASL track — the direct behavior-cap follow-on to slice 139
(services account identity via ENCAP * SU). Slice 139 built the read plane (a user carries
an account name, surfaced on WHOIS 330 / WHO %a); this slice builds the push plane.
Scope#
When a user's services account changes — i.e. an inbound :<sid> ENCAP * SU <uid> [:<account>]
logs <uid> in or out — deliver :nick!user@host ACCOUNT <account> to every local channel
co-member of the changed user that negotiated account-notify, where <account> is the account
name on login and the literal * on logout (the IRCv3 account-notify wire form). Also push to
extended-monitor watchers (gated account_notify + extended_monitor), closing the documented
gap in the [EXTENDED_MONITOR] doc ("leveva has no account-notify, so that one sub-notification
never fires").
Account changes in leveva originate only from ENCAP * SU (leveva keeps no account DB), so
that is the only trigger. The login-on-JOIN case (extended-join) and the @account message tag
(account-tag) are separate later caps per the framework-vs-behavior rule — out of scope here.
Mechanism#
-
cap.rs— register the cap (one entry, mirrorsaway-notify):pub const ACCOUNT_NOTIFY: &str = "account-notify";with a doc comment.- A
Capability { name: ACCOUNT_NOTIFY, value: None }row inSUPPORTED. - A
pub account_notify: boolfield onClientCaps+ its line inclient_caps()snapshot. - Amend the
EXTENDED_MONITORdoc: leveva now does pushACCOUNTto extended-monitor watchers, so drop the "never fires" caveat.
-
s2s/su.rs— add anotify_accountfan-out (mirrors [s2s::away::notify_co_members]):- Resolve the changed user's mask from its registry record (
record_of(uid)→ nick/user/host; the registry is the SoT for both local and remote users). If the user is unknown, no-op (nothing visible to notify). - Build
:nick!user@host ACCOUNT <account>where<account>is the account name on login or*on logout (a single middle param, per the account-notify spec — not a trailing). - Deliver to each local
co_members(uid)whosecaps_of(member).account_notifyis set (self is excluded byco_members). crate::monitor::notify_watchers(ctx, nick, uid, |c| c.account_notify, &wire)for the extended-monitor watchers that share no channel.- Call it from
apply_encap_suafterset_account, with the resolved new value (Some(name)→ that name;None/logout →*). The invalid-account branch stays a no-op and notifies nothing (state untouched). The caller's verbatim split-horizon relay is intact.
- Resolve the changed user's mask from its registry record (
Divergences (documented, leveva-native)#
- Single-server-local push effect. Only our local cap co-members see the
ACCOUNTline; the rest of the net learns of the login via the onwardENCAP * SUrelay (each server applies + does its own account-notify push to its locals). The standings2s::relaysingle-uplink divergence. - No
leveva-integrationdifferential — the C 2.11 oracle has noSU/account concept and emits noACCOUNT, so there is nothing to pin against (as with slice 139). - Logout wire form is
*(account-notify convention), distinct from WHO%a's0(WHOX not-logged-in convention). Both are leveva-native; the read plane (139) and push plane (140) use the form each spec mandates. - Trigger is
SU-only. A registered local client cannot self-ACCOUNT(leveva has no client SASL yet); the only authority is a servicesSUbroadcast.
Tests (TDD — RED first) + fuzz#
- Unit (
s2s/su.rs):su_login_notifies_a_local_capable_co_member— peer SU-login → a localaccount-notifyco-member receives:rover!… ACCOUNT RoverAcct.su_logout_notifies_with_a_star(inverse) — a subsequent logout pushes… ACCOUNT *.su_does_not_notify_a_non_cap_co_member— a co-member without the cap gets nothing (state still applies).su_for_unknown_uid_notifies_nothing— unknown target → no push, no panic.su_with_invalid_account_does_not_notify— invalid name → state + push both untouched.
- Golden (
golden_s2s_account.rs, extended): add a second local clientcarolwhoCAP REQ :account-notifys and joins#hub; assert she receives the liveACCOUNT RoverAccton login andACCOUNT *on logout (a new snapshot section; the existing WHOIS/WHO assertions and their snapshot are unchanged — account defaults toNone). - Proptest (
account_proptest.rs, extended): a property that drives arbitrarySUlogin/logout sequences throughs2s::dispatchwith a capable co-member attached and asserts the deliveredACCOUNTparam always tracks the last valid account (name on login,*on logout), never panics, and a non-SUENCAP delivers nothing.
Gate#
cargo test -p leveva (touched binaries) green; new su unit tests + extended golden snapshot +
extended proptest pass; cargo clippy -p leveva --tests clean; cargo build --workspace 0
warnings; no existing snapshot churned.