← All docs

Architecture Decisions: Protocol Boundary and Moderation

1. The Two-Tier Problem

The moment we add capabilities that legacy clients can't see — DID-based identity, CRDT-synced state, iroh transport, E2EE channels — we create two tiers of experience. This is the central design tension.

Our Position

Two tiers are inevitable and correct. The question is whether the degradation is graceful.

Think of it like HTTP/1.1 and HTTP/2. Both work. One gets more features. Neither breaks the other. The protocol boundary must be explicit, and the degradation must be clean.

Where We Draw the Line

Feature Legacy Client Modern Client Mechanism
Connect, join, chat IRC protocol
Nick registration Server-local only DID-bound, portable SASL ATPROTO-CHALLENGE
Identity in WHOIS nick!user@host + DID + AT handle + iroh ID IRCv3 CAP
E2EE channels See ciphertext ENC1:... Decrypted messages Client-side
P2P DMs Not available Encrypted, server-free iroh transport
Channel state after netsplit Server authority CRDT merge Invisible to clients
Message tags (reactions, typing) Not available IRCv3 message-tags CAP

The Seam: IRCv3 CAP Negotiation

CAP negotiation is the right boundary. It was designed for exactly this:

Client: CAP LS
Server: CAP * LS :sasl message-tags
Client: CAP REQ :sasl message-tags
Server: CAP * ACK :sasl message-tags

We currently advertise: sasl message-tags

We should extend to:

CAP * LS :sasl message-tags did-identity channel-crdt p2p-dm

Where:
- did-identity — Server will include DID and AT handle in WHOIS numerics (672, etc.)
- channel-crdt — Channel state is CRDT-backed; client may see members from other servers
- p2p-dm — Server supports relaying iroh endpoint IDs for peer-to-peer DMs

A legacy client that doesn't REQ these caps sees a standard IRC server. No special cases in the protocol handler. The CAP bitmask determines what extra information flows.

What Legacy Clients Miss (Explicitly)

  1. Identity portability — Your nick is server-local. No DID, no proof of identity.
  2. E2EE — You see ENC1:nonce:ciphertext as the message body. You can't decrypt it. This is intentional — encrypted rooms are opt-in and participants must use a capable client.
  3. P2P DMs — Not available. DMs go through the server as standard PRIVMSG.
  4. Moderation context — You can't see that a ban is DID-based, not hostmask-based. It still works (the server enforces it), but you can't inspect why.

What Legacy Clients DO Get

  1. Full IRC — JOIN, PART, PRIVMSG, MODE, KICK, TOPIC, NAMES, WHOIS, all work.
  2. Guest access — Connect without SASL, get a nick, chat.
  3. Operator privileges — If granted by an authenticated user, work normally.
  4. Plaintext channels — Any channel without E2EE enabled works identically.

2. Moderation in a DID-First World

This is the harder problem. IRC's moderation model assumes server authority:

  • Chanops are granted by other chanops or the server
  • Server opers have god mode
  • Bans are hostmask patterns, tied to connection metadata
  • K-lines are server-level bans

When identity is portable (DIDs) and servers are peers (CRDT mesh), every one of these assumptions breaks.

2.1 What Breaks

Nick-based ops lose meaning across servers. If Alice is a chanop on Server A and Bob joins from Server B, who granted Alice's authority? Server A did. But Server B has never seen Alice.

Hostmask bans become meaningless with portable identity. A ban on alice!*@* doesn't work when Alice reconnects via iroh with a different transport address. DID-based bans work (did:plc:abc123), but they require the ban to be a semantic statement about identity, not connection metadata.

Server authority fragments. In classic IRC, the server you're connected to is the authority. In a mesh, there's no single authority for a channel.

2.2 What We're Building

DID-Based Bans (Already Implemented)

We already support banning by DID:

/ban did:plc:abc123

This bans the cryptographic identity, not a hostname. It works regardless of how the user connects (TCP, TLS, iroh, different IP). The ban entry is stored in BanEntry with both hostmask and DID matching.

Moderation Log as a CRDT

The feedback is exactly right: the moderation log itself must be a CRDT.

Current state: our crdt.rs uses flat keys like ban:{channel}:{mask} → set_by. This handles add/remove cleanly via Automerge's put/delete semantics.

But it doesn't capture:
- Who performed the action
- When (causal ordering)
- Why (reason)
- Conflict resolution for concurrent ban + unban

Proposed: Moderation Events in the CRDT

modaction:{channel}:{ulid} → {
  "action": "ban",
  "target": "did:plc:abc123",
  "by": "did:plc:operator",
  "reason": "spam",
  "timestamp": 1707500000
}

Using ULIDs (time-sortable unique IDs) as keys means:
- No two moderation actions conflict (unique keys)
- Temporal ordering is preserved
- The full moderation history is auditable
- Concurrent ban + unban produces both events in the log

To determine current ban state, you fold the moderation log: replay actions in causal order, applying bans and unbans. The last action wins.

This is more expensive than a simple set, but it gives us:
- Auditability — Full history of who did what and when
- Conflict resolution — Concurrent actions are both recorded; latest wins
- Portability — The moderation log travels with the channel state across servers
- Accountability — Every moderation action is attributed to a DID

Authority Model

The hard question: who is allowed to moderate?

Options, from simplest to most sophisticated:

Option A: Channel founder model (simplest)
- The DID that creates a channel is the founder
- Founders can grant/revoke operator status
- Operator grants are themselves CRDT entries: op:{channel}:{did} → granting_did
- Only operators can add moderation events
- Server validates operator status before accepting ban/kick commands

Option B: Threshold model
- Moderation actions require N-of-M operator agreement
- A ban proposed by one operator becomes effective only when K others confirm
- The CRDT stores proposals and confirmations separately
- More complex, but resistant to rogue operators

Option C: Reputation/stake model (future)
- Moderation weight tied to identity reputation
- AT Protocol social graph as a trust signal
- Out of scope for now, but the CRDT structure supports it

Our choice: Option A first, with the CRDT schema designed to support B later.

2.3 The Merge Problem

What happens when two servers in a mesh have conflicting moderation state?

Scenario: Server A bans did:plc:evil. Server B unbans did:plc:evil. Both happen concurrently before sync.

With our moderation log approach:
1. After sync, both events exist in the CRDT
2. Both have timestamps (and Automerge causal ordering)
3. Fold the log: the temporally-later action wins
4. If truly simultaneous: Automerge's deterministic conflict resolution picks one
5. Both servers converge to the same state

Scenario: Server A's operator bans a user. Server B doesn't recognize A's operator.

Resolution:
1. Operator grants are also in the CRDT
2. If the grant hasn't synced yet, Server B will reject the ban locally
3. Once the grant syncs, Server B can re-evaluate
4. Alternatively: accept all moderation events and validate authority during fold

This is why the moderation log is better than a simple ban set — you can retroactively validate authority as state converges.

2.4 What's Implemented Now vs. What's Next

Now:
- DID-based bans (server-local, BanEntry with DID matching)
- Automerge CRDT with ban:{channel}:{mask} → set_by
- Channel ops granted per-session

Next:
- [ ] Moderation event log in CRDT (ULIDs, full attribution)
- [ ] Operator grants as CRDT entries (DID → DID)
- [ ] Authority validation during ban fold
- [ ] Channel founder registration (first DID to create channel)
- [ ] CAP extension for moderation transparency (mod-log capability?)


3. Why This Matters (BlueYard Thesis Alignment)

This project sits at the intersection of:

  1. Open protocol infrastructure — IRC is the original open chat protocol. We're not replacing it; we're upgrading the substrate.

  2. Crypto-native identity — DIDs from the AT Protocol give us portable, self-sovereign identity without a new identity system. Your Bluesky account IS your IRC account.

  3. P2P transport — Iroh gives us encrypted, NAT-traversing, relay-capable connectivity without centralized infrastructure.

  4. Local-first data — Automerge CRDTs mean server state is replicated, conflict-free, and doesn't require a consensus leader.

The thesis: you can modernize a 37-year-old protocol without breaking it, by upgrading identity, transport, and state management independently. Each layer is separable. Each layer degrades gracefully. No single point of failure.

If this works for IRC, it's a template for upgrading any open protocol infrastructure.