// Directory replication API: lets a website (e.g. a Django backend) mirror the // account/channel directory this node owns, without touching IRC at all. // // Deliberately excludes anything credential-shaped (password hashes, SCRAM // verifiers, TLS cert fingerprints) — a subscriber gets identity and metadata // only, never a way to authenticate as an account. Channel events are the // events already scoped Local to this node (see Scope in engine/db.rs), so a // website only ever sees its own network's channels, matching what gossip // federates. syntax = "proto3"; package fedserv.v1; service Directory { // One-shot full read of current state, for a subscriber's initial load. rpc Snapshot(SnapshotRequest) returns (SnapshotResponse); // Live feed of directory changes from the moment of the call onward. A // dropped stream should be followed by a fresh Snapshot (this is at-least- // once delivery, not a resumable cursor) — appliers should be idempotent, // keyed by account/channel name. rpc Subscribe(SubscribeRequest) returns (stream ReplicationEvent); } message SnapshotRequest {} message AccountRecord { string name = 1; string email = 2; // empty = none on file bool verified = 3; uint64 registered_at = 4; // unix seconds string home = 5; // origin node sid that first registered it } message ChannelRecord { string name = 1; string founder = 2; // account name uint64 registered_at = 3; string description = 4; } message SnapshotResponse { repeated AccountRecord accounts = 1; repeated ChannelRecord channels = 2; } message SubscribeRequest {} // The gossip envelope (origin/seq/lamport) is carried alongside the payload so // a subscriber can log/dedupe by (origin, seq) if it wants to, even though the // stream itself is not resumable by cursor in this version. message ReplicationEvent { string origin = 1; uint64 seq = 2; uint64 lamport = 3; oneof kind { AccountRegistered account_registered = 10; AccountEmailSet account_email_set = 11; AccountVerified account_verified = 12; AccountDropped account_dropped = 13; NickGrouped nick_grouped = 14; NickUngrouped nick_ungrouped = 15; ChannelRegistered channel_registered = 20; ChannelDropped channel_dropped = 21; ChannelFounderSet channel_founder_set = 22; ChannelDescSet channel_desc_set = 23; } } message AccountRegistered { string name = 1; string email = 2; bool verified = 3; uint64 registered_at = 4; string home = 5; } message AccountEmailSet { string account = 1; string email = 2; } message AccountVerified { string account = 1; } message AccountDropped { string account = 1; } message NickGrouped { string nick = 1; string account = 2; } message NickUngrouped { string nick = 1; } message ChannelRegistered { string name = 1; string founder = 2; uint64 registered_at = 3; } message ChannelDropped { string name = 1; } message ChannelFounderSet { string name = 1; string founder = 2; } message ChannelDescSet { string name = 1; string description = 2; } // Account authority API: the write side, for a trusted caller (e.g. a website's // own backend, already having done its own login/session check) to manage // accounts the same way an IRC user does through NickServ — registration, // credentials, grouping, and forced logout. The bearer token IS the // authorization: unlike the NickServ commands these mirror, most calls here do // NOT re-check the account's own password (the caller is trusted, the same // model as an admin-level override) — Register and Authenticate are the two // exceptions, since the password is the actual input/question there. // A write committed here replicates to every other fedserv node exactly like // an IRC-originated one does — gossip doesn't know or care where it came from. service Accounts { rpc Register(RegisterRequest) returns (AccountReply); rpc Authenticate(AuthenticateRequest) returns (AuthenticateReply); rpc SetPassword(SetPasswordRequest) returns (AccountReply); rpc SetEmail(SetEmailRequest) returns (AccountReply); rpc Confirm(ConfirmRequest) returns (AccountReply); rpc Drop(DropRequest) returns (AccountReply); rpc ForceLogout(ForceLogoutRequest) returns (ForceLogoutReply); rpc GroupNick(GroupNickRequest) returns (AccountReply); rpc UngroupNick(UngroupNickRequest) returns (AccountReply); } enum Status { OK = 0; ALREADY_EXISTS = 1; NOT_FOUND = 2; RATE_LIMITED = 3; INVALID = 4; // bad password/code/input, or a name-shaped invariant violation INTERNAL = 5; } message AccountReply { Status status = 1; string message = 2; // human-readable detail, safe to show a user } message RegisterRequest { string name = 1; string password = 2; string email = 3; } message AuthenticateRequest { string name = 1; string password = 2; } message AuthenticateReply { Status status = 1; string account = 2; // canonical account name (resolves a grouped-nick alias) } message SetPasswordRequest { string account = 1; string password = 2; } message SetEmailRequest { string account = 1; string email = 2; } // empty = clear message ConfirmRequest { string account = 1; string code = 2; } message DropRequest { string account = 1; } message ForceLogoutRequest { string account = 1; } message ForceLogoutReply { Status status = 1; uint32 sessions_cleared = 2; } message GroupNickRequest { string nick = 1; string account = 2; } message UngroupNickRequest { string nick = 1; } // Stats API: a flat, namespaced counter map any service contributes to // (e.g. "nickserv.command", "botserv.kick", "channels.total"). Read-only. service Stats { rpc Get(StatsRequest) returns (StatsResponse); } message StatsRequest {} message StatsResponse { map counters = 1; uint64 collected_at = 2; // unix seconds when this snapshot was taken }