Skip to content

chanserv: mark registered channels +R on the wire; split z (persist) from R (registered) - #60

Draft
MrLenin wants to merge 4 commits into
evilnet:masterfrom
MrLenin:feature/registered-mode-r
Draft

chanserv: mark registered channels +R on the wire; split z (persist) from R (registered)#60
MrLenin wants to merge 4 commits into
evilnet:masterfrom
MrLenin:feature/registered-mode-r

Conversation

@MrLenin

@MrLenin MrLenin commented Aug 30, 2026

Copy link
Copy Markdown
Contributor

Split out of #57 (which is now the channel-rename half only). Fixes X3's registered-channel wire marker so nefarious2's registered-channel logic can actually trigger.

What this does

  • Registered channels are now marked +R on the wire. X3's MODE_REGISTERED letter was the Bahamut-era z — which nefarious2 doesn't use for registered (on the fork z is the persist exmode). Remapped to R, nefarious2's actual server-settable registered mode. Announced unconditionally at register / unregister / move / db-load — previously all gated on off_channel > 0, so with a bot in the channel the ircd was never told the channel was registered. Without this, no ircd-side registered-channel logic (rename arbitration, registered-only history retention, etc.) could ever fire.
  • Heal-on-join: handle_join re-asserts the marker on a registered channel the ircd doesn't currently show as +R (ircd restarts, X3 restarts racing the first post-restart JOIN, channel re-creation). Only the first joiner triggers a wire MODE; later joins in the same burst see the bit set and no-op.
  • z (persist) split from R (registered) properly — new MODE_PERSIST bit tracks +z. ChanServ sets +z alongside +R exactly when off_channel > 0 (no bot presence to hold the channel open — +z's original purpose), clears both on unregister so an unregistered channel can die when it empties. cmd_burst strips both markers from a channel that isn't registered with us. User-typed mode strings (MCP_REGISTERED call sites: modelock / mode command) may not toggle either server-managed marker; server-origin strings parse both so X3's copy tracks the ircd.
  • wipeout_channel parses burst modes as server-origin (MCP_FROM_SERVER). Previously one unknown mode letter aborted the whole parse, stranding a channel's mode mirror at zero on every X3 restart.

Commit history note

The three z/R commits show the design being worked out in-branch (+R → "keep z on the wire for now" → proper split). Happy to squash them to one if preferred; kept as-is so the reasoning in each message survives.

Related

Verification

Live E2E on the Afternet testnet against the fork: +R visible on registered channels at register / db-load / rejoin after ircd restart; -R on unregister; +z only when off_channel > 0. Registered-channel-gated ircd behaviour (rename arbitration in #57) exercised end-to-end. Vitest coverage in the testnet repo (channel-rename-services.test.ts, persistence-profile.test.ts).

🤖 Generated with Claude Code

MrLenin and others added 4 commits August 30, 2026 02:04
…RED z->R)

X3's MODE_REGISTERED bit (hash.h, comment "Bahamut +r") was wired to the
channel-mode letter 'z' in the P10 parser/formatter. Two problems made
this dead weight:

- On the nefarious fork, '+z' means the persist exmode -- an unrelated
  server-settable mode. X3 was misreading incoming '+z' from the ircd as
  a MODE_REGISTERED toggle, and would happily set/clear its own
  registration bit off a mode letter the fork uses for something else.
- Announcing MODE_REGISTERED at all was gated behind `off_channel > 0`
  in every call site that set it (register, unregister, move, DB-load),
  and the deployed conf runs off_channel=no. So X3 never actually put
  +R (or, previously, +z) on the wire in the first place.

Both nefarious and nefarious-upstream's m_rename only ask services for
permission to rename a channel when it carries MODE_REGISTERED
(channel.c:2402/2113 -- server-settable only, MODE_PARSE_FORCE
required). With X3 never setting the bit, the whole services-arbitration
path for channel rename was unreachable.

Fix, at the root:

- Remap the wire letter from 'z' to 'R' everywhere MODE_REGISTERED is
  parsed or emitted (mod_chanmode_parse, mod_chanmode_announce,
  mod_chanmode_format, clear_chanmode/CLEARMODE, the cmd_burst
  registration-correction path). 'z' is no longer a recognized channel
  mode letter in X3 at all -- it now falls through to the same
  "unrecognized letter" handling every other unknown letter already
  gets (silently ignored from server origin, rejected from user-typed
  strings), so an incoming fork +z persist exmode is simply left alone
  instead of being misread.
- Announce +R/-R unconditionally on registration, unregistration, and
  channel move, instead of gating it behind off_channel (which governs
  only whether ChanServ itself occupies the channel, not whether the
  ircd should know the channel is registered). Also unconditional in
  the saxdb channel-load path for channels with a stored modelock.
- Self-heal in handle_join(): if a registered, non-suspended channel's
  ircd-side modes don't carry +R, re-announce it. Covers ircd restarts,
  X3 restarts racing the first post-restart JOIN, and channel
  re-creation. Not gated on burst state (unlike the nearby dynamic-limit
  and automode/greeting guards) since it sends no message and touches
  no timer -- mod_chanmode_announce applies the change to local state
  immediately, so only the first joiner per convergence gap triggers
  the wire MODE.

MCP_REGISTERED's guard semantics (block MODE_REGISTERED toggles from the
two user-facing ChanServ mode-string paths, cmd_mode and the SET MODES
modelock) are unchanged -- only the letter moved.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
(cherry picked from commit 719593e)
wipeout_channel() (called from AddChannel() whenever an incoming BURST's
channel timestamp predates X3's own -- the normal case on X3 restart,
since chanserv_channel_read() creates placeholder chanNodes at
timestamp=now before the real BURST arrives) reparsed the ircd's burst
mode string via mod_chanmode(NULL, cNode, modes, modec, 0), missing
MCP_FROM_SERVER. It was the only call site parsing server-origin mode
data without that flag; AddChannel()'s own two mod_chanmode() calls
already pass it.

Without MCP_FROM_SERVER, mod_chanmode_parse()'s default case aborts the
whole parse on the first mode letter it doesn't recognize, leaving
cNode->modes stranded at the zero wipeout_channel just set -- losing
every mode, not just +R. This fires on the ordinary X3-restart path, and
in particular for any channel still carrying a stray +z on the live
ircd side (the fork's persist exmode, previously planted there by X3's
old z-as-registered announce before 129a0d8) -- exactly the population
the +R remap needed to heal.

Fix: pass MCP_FROM_SERVER, the semantically correct flag for burst data,
so an unrecognized letter is skipped instead of aborting the parse.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
(cherry picked from commit 01a4c4f)
Walk back the letter half of 129a0d8: X3 MUST keep emitting 'z' as the
registered-channel mode letter for now (user decision 2026-08-01; see the
testnet registered-mode z->R transition plan). The announce-gating half of
that commit (off_channel un-gating, handle_join self-heal) stays -- it was
the load-bearing fix; with off_channel=no, X3 had never emitted ANY
registered marker, which left every MODE_REGISTERED-gated fork feature
(rename arbitration, chathistory store-registered, metadata persistence)
silently dead.

- Emit conservatively: all four mod_chanmode format sites emit 'z'.
- Parse liberally: both 'z' and 'R' read as MODE_REGISTERED (the fork
  bursts +R alongside +z once its persist-mirror sets the bit).
- CLEARMODE parse accepts both; the cmd_burst unregistered-correction
  strips -zR.

On nefarious, 'z' is the persist exmode (services-settable only), so
X3 marking registered channels +z doubles as the empty-channel keep-alive
those channels want -- the semantics off_channel>0 networks always had.
The ircd-side companion (FEAT_REGISTERED_FROM_PERSIST mirror) makes the
fork derive MODE_REGISTERED from services 'z'.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
(cherry picked from commit d9aa287)
Supersedes the previous letter dance (z->R remap in 129a0d8, then the
z-primary walk-back): final decision keeps BOTH letters with their true
meanings, orthogonal from day one:

- 'R' = MODE_REGISTERED, the ircd's registration marker. X3 emits it
  unconditionally on register/unregister/move/DB-load (the off_channel
  un-gating and handle_join self-heal from 129a0d8 stand).
- 'z' = new MODE_PERSIST, nefarious's persist exmode, used as originally
  intended: set alongside +R only when off_channel>0 -- exactly the case
  where no ChanServ presence holds the registered channel open while
  empty. Cleared (with R) on unregistration so the emptied channel can
  destruct; moved on cmd_move; healed by handle_join.

Parse side: 'z' now reads as MODE_PERSIST (never MODE_REGISTERED), with
the same MCP_REGISTERED guard as 'R' so user-typed modelocks can't
toggle server-managed markers. CLEARMODE accepts both letters. The
cmd_burst correction for channels we don't recognize strips both (-zR).

No ircd-side companion needed: the fork's MODE_REGISTERED machinery
(rename arbitration, chathistory store-registered, metadata persistence)
keys off the R that X3 now actually emits. The FEAT_REGISTERED_FROM_PERSIST
mirror has been reverted from both ircd trees.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
(cherry picked from commit 1d80d7d)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant