Rename the project to Echo (was fedserv)

Rebrand the daemon and workspace: the binary is now `echo`, every crate
is `echo-*` (`echo-api` and the module crates), Rust imports use
`echo_*`, the gRPC proto is `proto/echo.proto` with package `echo.v1`,
and the log filter, gossip peer-name default, and on-disk store default
(`echo.db.jsonl`) follow. Docs updated throughout. No behaviour change;
crate directories and the config schema are untouched.
This commit is contained in:
Jean Chevronnet 2026-07-14 15:24:41 +00:00
parent e7037572e5
commit 993f5b2eea
No known key found for this signature in database
159 changed files with 660 additions and 660 deletions

157
proto/echo.proto Normal file
View file

@ -0,0 +1,157 @@
// 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 echo.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 echo 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);
// Create-or-update an account from pre-derived SCRAM verifiers instead of a
// plaintext password. For bulk backfill from an authority (e.g. a website)
// that stores verifiers, not passwords. scram512 may be empty.
rpc Provision(ProvisionRequest) 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 ProvisionRequest { string name = 1; string scram256 = 2; string scram512 = 3; string email = 4; }
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
}