fix(server): инстанс-админ видит все серверы инстанса в READY

Чек-лист §11.5 требует, чтобы READY и список серверов инстанс-админа
содержали все серверы инстанса, включая чужие и приватные. Фактически
список строился из членства (`ListGuildsForUser`), поэтому в интерфейсе
админа чужого сервера не было ни в рейке, ни в подстраховочном
`GET /users/@me/guilds` — войти в него без инвайта было нельзя, хотя права
на чужом сервере у него максимальные (AGENT.md 7.18, 7.19).

- `Snapshot.userGuilds`: админу — `ListAllGuilds`, остальным — членство;
- `GET /users/@me/guilds` отдаёт админу тот же список, иначе REST-ответ
  выкидывал бы чужой сервер из рейки при перечитывании сводки;
- Go-тест `TestReadyListsAllGuildsForInstanceAdmin`: READY и REST содержат
  чужой приватный сервер с полными правами, у обычного пользователя его нет.
This commit is contained in:
2026-09-26 11:06:24 +03:00
parent 61cad0fdfc
commit 5f21c9d069
4 changed files with 118 additions and 5 deletions
+22 -2
View File
@@ -104,9 +104,12 @@ func (s *Snapshot) Build(ctx context.Context, user *store.User) (*Ready, error)
ready.DMChannels = append(ready.DMChannels, channel)
}
guilds, err := s.store.ListGuildsForUser(ctx, user.ID)
// Инстанс-админ видит в снапшоте все серверы инстанса, включая чужие и
// приватные, где он не участник: без этого он не мог войти в чужой сервер
// из интерфейса, хотя права у него есть везде (AGENT.md 7.19, 11.5).
guilds, err := s.userGuilds(ctx, user)
if err != nil {
return nil, fmt.Errorf("list user guilds: %w", err)
return nil, err
}
for _, guild := range guilds {
resolved, err := s.calculator.Guild(ctx, guild.ID, user.ID, user.IsInstanceAdmin)
@@ -126,6 +129,23 @@ func (s *Snapshot) Build(ctx context.Context, user *store.User) (*Ready, error)
return ready, nil
}
// userGuilds выбирает серверы для снапшота: обычному пользователю — только
// свои, администратору инстанса — все серверы инстанса (AGENT.md 7.19, 11.5).
func (s *Snapshot) userGuilds(ctx context.Context, user *store.User) ([]store.Guild, error) {
if user.IsInstanceAdmin {
guilds, err := s.store.ListAllGuilds(ctx)
if err != nil {
return nil, fmt.Errorf("list instance guilds: %w", err)
}
return guilds, nil
}
guilds, err := s.store.ListGuildsForUser(ctx, user.ID)
if err != nil {
return nil, fmt.Errorf("list user guilds: %w", err)
}
return guilds, nil
}
func (s *Snapshot) buildGuild(ctx context.Context, guild store.Guild, user *store.User, resolved permissions.Resolved) (ReadyGuild, error) {
readyGuild := ReadyGuild{
ID: formatID(guild.ID),