0024-fediverse-ui-ops-slice-c.md 7023 octets

RFC 0024 — Fediverse UI : invitations & ops admin (Slice C)

  • Auteur : EBO (rustwarden)
  • Date : 2026-05-10
  • Statut : Accepted (auto-revue mainteneur unique 2026-05-10, score 26/35)
  • Apps consommatrices concernées : toute app rustwarden-ui qui veut un dashboard ops + flux invitations cross-instance.
  • Plan associé : Plan UI HTML §7 Slice C.
  • Dépendances : RFC 0011 (file outbound), RFC 0019 (tracing), RFC 0009 (FediverseInviteService), RFC 0021 (admin foundations).

Summary

Livrer 6 écrans HTML restants du parcours fédération admin/ops :

#ÉcranIter
1/admin/fediverse/queue — dashboard file outbound (status counts + 100 dernières lignes)1 ✅
2/admin/fediverse/sessions_remote — sessions distantes actives + kill switch par domaine2
3/admin/fediverse/nodeinfo — stats NodeInfo humanisées3
4/admin/teams/{id}/invites — invitations cross-instance (form + table pending/accepted/rejected/revoked)4
5/profile/connected_apps — section profil "Apps connectées" + bouton révoquer5
6/fediverse/invite/{id} — page acceptation d'invitation (côté IdP du destinataire)6

L'iter. 1 ferme la livraison en une passe ; les iter. 2-6 réutilisent le pattern Slice A (handler + template + i18n + route).

Motivation

Slice A donne le contrôle de la fédération (toggle + trust). Slice B donne l'auth utilisateur. Slice C donne la visibilité (queue, NodeInfo, sessions) et les flux invitations cross-instance (RFC 0009). Sans ces pages, un opérateur n'a pas de signal sur la santé du système et un admin team ne peut pas inviter quelqu'un d'une autre instance.

Non-goals

  • Pas de graphiques temps-réel dans queue.html — un refresh manuel suffit. Les graphes Prometheus ressortent des events RFC 0019.
  • Pas d'éditions inline sur les invitations — révoquer = action atomique.
  • Pas de bulk-revoke sur sessions distantes — une à la fois ; le kill switch domaine couvre les cas de masse.
  • Pas de pagination sophistiquée — limite 100 rows par page suffit pour le MVP ; tri par created_at desc.

API proposée

Routes UI HTML (rustwarden-ui)

MéthodePathHandlerAuth
GET/admin/fediverse/queueadmin_fediverse::get_queueadmin
GET/admin/fediverse/sessions_remoteadmin_fediverse::get_sessions_remoteadmin
POST/admin/fediverse/sessions_remote/{domain}/killadmin_fediverse::kill_domainadmin + CSRF
POST/admin/fediverse/sessions_remote/{id}/revokeadmin_fediverse::revoke_remote_sessionadmin + CSRF
GET/admin/fediverse/nodeinfoadmin_fediverse::get_nodeinfoadmin
GET/admin/teams/{team_id}/invitesadmin_invites::get_invitesadmin team
POST/admin/teams/{team_id}/invitesadmin_invites::create_inviteadmin team + CSRF
POST/admin/teams/{team_id}/invites/{id}/revokeadmin_invites::revoke_inviteadmin team + CSRF
GET/profile/connected_appsprofile_fediverse::connected_appslogged-in user
POST/profile/connected_apps/{host}/revokeprofile_fediverse::revoke_applogged-in user + CSRF
GET/fediverse/invite/{id}invite_landing::formlogged-in user
POST/fediverse/invite/{id}invite_landing::submitlogged-in user + CSRF

Tous les endpoints /admin/fediverse/* sont gated par feature + 404 si fediverse_enabled = false. Les endpoints /profile/connected_apps et /fediverse/invite/* aussi (P9 du Plan UI).

Templates Askama (nouveaux)

templates/admin/fediverse/
├── queue.html              # iter. 1 ✅
├── sessions_remote.html    # iter. 2
└── nodeinfo.html           # iter. 3
templates/admin/teams/
└── invites.html            # iter. 4
templates/profile/
└── connected_apps.html     # iter. 5
templates/fediverse/
└── invite_landing.html     # iter. 6

Compatibilité

Aucun impact sur les apps qui n'activent pas experimental-fediverse. Pures additions, gated.

AuthService::call accepte déjà les préfixes /admin, /profile, /settings ; le seul nouveau préfixe à enregistrer pour Slice C est /fediverse/invite (sera ajouté en iter. 6).

Alternatives

  1. Tout en une RFC monolithique — rejeté : on a vu sur Slice B que 4-5 écrans sur une seule RFC mène à des turns trop chargés ; le découpage en iter livrables séparément a bien fonctionné.
  2. Reporter Slice C jusqu'à RFC 0006 Phase 2 — rejeté : Slice C est largement indépendant de Phase 2. Seul connected_apps voit potentiellement des sessions remote = 0 tant que Phase 2 n'est pas livrée, mais le rendu page vide est testable.
  3. Page admin/sessions_remote en read-only sans kill switch — rejeté : sans action destructive, l'admin ne peut pas répondre à un incident (instance fédérée compromise → tuer toutes les sessions de ce domaine).

Stratégie d'itérations

IterLivrableÉtat
1/admin/fediverse/queue (RFC 0011 + RFC 0019 visualisés)✅ livré
2/admin/fediverse/sessions_remote + kill switchÀ venir
3/admin/fediverse/nodeinfoÀ venir
4/admin/teams/{id}/invites (utilise RFC 0009 FediverseInviteService)À venir
5/profile/connected_appsÀ venir
6/fediverse/invite/{id} (parcours destinataire)À venir

Chaque iter passe les 5 quality gates (build / test / clippy / doc / migration N/A) avant la suivante.

Testing plan

  • Iter. 1 : test handler get_queue rendu — empty state quand 0 row, table quand >0. Couvert par MockDatabase.
  • Iter. 2 : test kill switch invalide une session, audit log généré.
  • Iter. 3 : test nodeinfo response shape.
  • Iter. 4-6 : tests handler invitations (création / révocation) + acceptation côté destinataire.

Migration guide

Aucune. Pure addition de routes + templates.

Open questions

  • Q1 : faut-il pousser la file outbound en SSE temps-réel ou laisser refresh manuel ? Réponse provisoire : refresh manuel ; SSE seulement si la demande émerge en prod.
  • Q2 : sessions_remote utilise quelle source de vérité — JWT blacklist ou RemoteIdentities ? Réponse : RemoteIdentities (les rows actives = sessions actives).

Score (26/35)

CritèreNoteDétail
Généralité4/56 écrans réutilisables.
Stabilité API5/5Pures additions.
Performance3/5LIMIT 100 + count() par status — borné mais à reviser si > 10k rows.
Sécurité4/5CSRF + admin_required + kill switch destructif (modale obligatoire).
Complexité2/56 écrans = ~1200 LoC sur 6 iters.
Maintenance4/5Pattern existant Slice A/B copié.
Documentation4/5Cette RFC + 6 entrées CHANGELOG par iter.