echo/proto/fedserv.proto
Jean d4e2c905f2
Shared stats registry + gRPC Stats API
A single namespaced counter map on the engine that every service reports
into: ServiceCtx::count(key) is folded in after each command, the engine
bumps its own events (botserv.kick/ban/warn/greet/trigger), and a
per-service command counter (nickserv.command, chanserv.command, …) is
tallied in dispatch. stats_snapshot() merges those with live gauges from
the store (accounts/channels/bots/opers total).

Exposed over gRPC as a new Stats service (Get -> map<string,uint64>),
authorized by the same bearer token as Directory/Accounts — so any
consumer (a dashboard, Django) reads every module's stats through one
pipe. New service is additive to the proto; existing RPCs untouched.
2026-07-13 19:13:31 +00:00

152 lines
5.7 KiB
Protocol Buffer

// 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<string, uint64> counters = 1;
uint64 collected_at = 2; // unix seconds when this snapshot was taken
}