Runbook — Membres et crédits
Inviter, plafonner, révoquer, allouer des crédits Studio et lire les coûts d’équipe.
Pour le fondateur. Ce que l'on fait, où, et ce que le studio fait tout seul. La mécanique est décrite dans Architecture de l’application, sections 3 et 4.
Statut (migration Orbit, 2026-10) : runbook d'Orbit (
https://orbit.easyconnector.app), repris de l'ancien dépôt Studio ; les écrans d'équipe sont ceux de l'espace authentifié (groupe(dashboard)).
Avant la première connexion
FOUNDER_EMAILporte ton adresse, puispnpm db:seed: ton membre fondateur existe, actif. Relancer le seed ne rétrograde ni ne révoque personne.NEXTAUTH_SECRET: une valeur propre à Orbit (openssl rand -base64 32), jamais celle d'une autre application (ni l'ancienne plateforme, nieasyconnector.app, produit distinct : voir Architecture des domaines). La changer déconnecte tout le monde et invalide les codes en cours.- Sur Vercel : l'intégration Resend du Marketplace connectée au projet
(Production et Preview, elle injecte
RESEND_API_KEY), et l'expéditeur dansconfig.fromdestudio.mailer(products/connectors.json). Voir Runbook connecteurs, « Messagerie ». Sans eux, aucun code ne part, et aucun code n'est jamais écrit dans le journal d'un déploiement. - Recommandé : l'intégration Upstash attachée au projet (Production et Preview), pour que les limites et le verrouillage des codes valent entre instances.
- Facultatif :
ORBIT_SPEND_ALERT_USD(seuil de dépense réglée de l'équipe sur le mois) etADMIN_EMAIL(qui reçoit l'alerte).
Hors d'un déploiement Vercel (pnpm dev, next start sur un poste), chaque email (code, invitation,
alerte) s'imprime dans le terminal de pnpm dev sur une ligne
[mail:console].
Se connecter
/auth (la page de connexion, groupe (minimal) ; l'ancienne adresse
/connexion n'est plus redirigée) : l'adresse, « Continuer », puis
le code à six chiffres reçu par email (valable 10 minutes, le dernier demandé
annule les précédents), qui part tout seul au sixième chiffre. La session dure
7 jours à compter de la connexion, même si tu utilises le studio tous les
jours : au bout d'une semaine, un nouveau code. Case « Mémoriser cet appareil
pendant 7 jours » décochée : 24 heures seulement.
Cinq codes faux en 15 minutes verrouillent l'adresse 15 minutes. Pendant ce
temps, même le bon code est refusé, y compris un code redemandé : il faut
attendre la fin du verrou. Chaque nouveau verrou dans les 24 heures qui
suivent la fin du précédent dure deux fois plus : jusqu'à 24 heures depuis
une même adresse IP, jusqu'à une heure au plus pour l'adresse elle-même
(ce que voit quelqu'un qui essaie depuis une autre IP). Une journée calme, ou
une connexion réussie, remet le compteur à zéro.
Limite connue et assumée : quelqu'un qui connaît ton adresse peut, depuis
n'importe quelle IP, te verrouiller avec cinq codes faux, puis renouveler ce
verrou (une heure au plus) tant qu'il insiste. C'est le prix du verrou par
adresse, sans lequel un code se devine en changeant d'IP. La borne d'une heure
est volontaire : un verrou par adresse de 24 heures permettrait de te fermer
le studio une journée entière pour quelques requêtes. Tu rentres donc au plus
une heure après qu'il arrête. Pour lever un verrou
à la main : supprimer dans Upstash les clés studio:lockout:<adresse>*
(console Upstash, ou redis-cli --scan --pattern 'studio:lockout:<adresse>*'
puis DEL). Sans Upstash (développement), redémarrer le serveur suffit.
Une adresse qui n'est pas invitée reçoit exactement le même écran qu'une
adresse invitée, et rien ne part : c'est voulu, personne ne peut sonder qui
fait partie de l'équipe. Une adresse inconnue n'écrit rien au journal
d'audit (sinon n'importe qui pourrait le remplir d'adresses de son choix) :
seules les limites de débit en gardent la trace. Un membre révoqué ou échu qui
redemande un code est, lui, tracé (auth.otp_refused).
Où sont les écrans
Après la connexion, le retour se fait vers ?from= (sinon l'accueil /,
qui est la page marketing publique d'Orbit). L'espace de travail est sous
/brain (le Brain) et /studio (Creative Studio, le cockpit repris de
l'ancien Studio). Tout se trouve depuis lui :
| Pour | Où | Qui le voit |
|---|---|---|
| Lire les soldes, la dépense de l'équipe et l'historique | /costs : le solde de la barre latérale y mène | tout membre (l'équipe : avec costs.view-team) |
| Inviter, changer un rôle, plafonner, révoquer | /members ; la fiche d'un membre : /members?member=<id> | members.manage (le fondateur) |
| Allouer des crédits | la fiche du membre, section « Allouer » | credits.allocate (le fondateur) |
| Suivre la dépense | /costs (aussi la ligne Crédits du menu compte) | tout membre |
| Sa propre fiche | menu compte, Profil | tout membre |
| Écrire au fondateur | Votre avis, en haut à droite | tout membre |
Les anciennes adresses françaises (/connexion, /membres, /cout) ne sont
plus redirigées dans Orbit : elles rendent la 404 (la seule redirection de
next.config.ts est /legal/* vers la documentation). Sous 1 024 px de
large, ces pages affichent l'écran « Conçu pour un ordinateur », comme le
cockpit.
Inviter quelqu'un
/members, section « Inviter » : l'adresse, le rôle, et si besoin une fin
d'accès (l'accès vaut jusqu'à la fin de ce jour, heure UTC). Il reçoit un email
qui l'envoie sur /auth. Tant qu'il ne s'est pas connecté, il est « En
attente ».
| Rôle | Pour qui |
|---|---|
| Créateur | génère (et travaille les concepts), dans la limite de ses crédits |
| Relecteur | QC et relecture |
| Diffuseur | distribution et brouillons Postiz |
| Lecteur | consulte, voit son solde |
Le rôle Fondateur ne se donne pas depuis l'interface.
Allouer des crédits
Sur la fiche du membre (/members, puis sa ligne ; l'adresse est
/members?member=<id>), section « Allouer » : un montant en dollars (au
coût fournisseur, six décimales au plus, la virgule est acceptée), le mois (le
mois en cours, ou le suivant pour le préparer), une échéance plus tôt si tu
veux, un motif.
- Pas de report : ce qui n'est pas dépensé à la fin du mois UTC est perdu. Chaque mois se réalloue.
- Un complément en cours de mois est simplement une deuxième allocation.
- Plusieurs allocations : la dépense prend d'abord celle qui expire la première.
- Un dépassement, lui, se reporte. Si un coût réel dépasse ce que le
solde couvre (un fournisseur a facturé plus que l'estimation), la différence
devient une dette que les allocations suivantes remboursent d'abord, y
compris le mois suivant. Le membre voit « Dépassement reporté » sur
/costs.
Plafonner
« Plafonds » : par génération (sur l'estimation, avant l'appel) et par mois (réglé + réservé du mois). Vide = pas de plafond. Un membre au plafond, ou à court de solde, est refusé avant tout appel fournisseur, avec un message, et rien n'est débité.
Toi, fondateur (propriétaire de l'espace), tu ne dépenses, comme tout
membre, que ce qui t'est alloué : tu t'alloues des crédits depuis ta propre
fiche, et tes plafonds s'appliquent comme ceux des autres (ADR 0017). Il n'y a
plus de membre illimité. Le total alloué de l'espace sur un mois UTC, ta part
comprise, ne peut pas dépasser ses crédits Studio mensuels
(Workspace.studioMonthlyCreditsUsd, 0 à l'inscription, accordés par
l'administration de la plateforme) : au-delà, l'allocation est refusée
(« budget dépassé »). Un membre sans espace ne reçoit aucune allocation.
Les plafonds portent sur l'estimation, avant l'appel. Le coût réel est
toujours inscrit tel que le fournisseur l'a facturé, jamais rogné : s'il
dépasse l'estimation ou le plafond par génération, une ligne
credits.settle_overrun est écrite et un email part à ADMIN_EMAIL.
Changer un rôle, révoquer
- Changer le rôle : effet à la page suivante du membre, sans qu'il se reconnecte (le rôle est relu en base à chaque requête).
- Révoquer (avec un motif, puis une confirmation) : l'accès est coupé à sa page suivante, il est
renvoyé sur
/auth, où un toast dit « Votre accès au studio a été retiré. ». Ses réserves en cours sont libérées, son allocation est gelée, il ne peut plus recevoir de code. Si une génération était en cours à ce moment, son coût réel est tout de même inscrit quand le fournisseur répond (credits.settled_after_release). - Une fin d'accès échue coupe l'accès au premier passage après l'échéance.
Ses réserves sont libérées à ce passage-là (ou à sa prochaine tentative
de génération) : un membre échu qui ne revient jamais garde ses réserves
jusqu'au balayage planifié (
releaseStaleHolds, dont la tâche cron arrive en phase 3). - Une réserve restée ouverte plus de 2 heures (une génération tombée entre la
réserve et le règlement) est libérée à la prochaine génération du même
membre, note
hold-stale. - Réinviter un membre révoqué ou échu : l'inviter à nouveau avec la même adresse. Son ancienne session ne revient pas à la vie : il doit se reconnecter.
Suivre la dépense
/costs : ton solde, et le tableau de l'équipe (alloué, dépensé, réservé,
disponible par membre, totaux), le mois en cours ou le précédent. Chaque membre
voit son propre solde, ses plafonds et l'historique de ses lignes.
L'alerte d'équipe part une fois par mois, quand la dépense réglée de
l'équipe franchit ORBIT_SPEND_ALERT_USD (un montant décimal, six décimales
au plus : 250 ou 250.50, jamais 1e3). Cette alerte porte sur la dépense
de toute la plateforme : elle n'apparaît plus sur le /costs d'un espace, elle
relève de l'administration de la plateforme. Un email qui n'a pas pu partir
repart au prochain passage (voir ci-dessous). Les dépassements du fondateur
partent par email comme ceux de tout membre.
Les trois emails au fondateur (l'alerte du mois, un coût au-delà de
l'estimation ou du plafond par run, un coût illisible débité à l'estimation)
passent par une boîte d'envoi, la table StudioNotice (revue phase 4).
Le grand livre y écrit la notification dans la MÊME transaction que le fait
(la ligne SETTLE, la ligne d'alerte). Avant, l'email dépendait du résultat en
mémoire de l'appelant : un règlement fait par le balayage, rejoué ou suivi
d'une erreur perdait l'alerte du mois pour de bon. La requête qui règle, le
webhook vidéo, la lecture de statut et le cron generations-sweep (toutes
les dix minutes) vident la boîte ; une notification est réservée avant
l'envoi, donc elle part une fois, et un envoi raté est retenté (dix essais au
plus, lastError dit pourquoi). Sans ADMIN_EMAIL rien n'est envoyé : les
notifications attendent. pnpm db:studio, table StudioNotice, colonne
sentAt vide : ce qui n'est pas parti.
Le journal d'audit
Chaque geste écrit une ligne StudioAuditLog : member.invited,
member.activated, member.role_changed, member.caps_changed,
member.revoked, credits.allocated, credits.holds_released,
credits.settle_overrun, credits.settled_after_release,
credits.team_alert, credits.team_alert_unsent, auth.signed_in, auth.signed_out,
auth.session_cut, auth.otp_refused, auth.refused. Pas encore d'écran
dédié : pnpm db:studio, table StudioAuditLog.
En cas de problème
| Symptôme | Cause probable | Que faire |
|---|---|---|
| Aucun code n'arrive (production) | intégration Resend non connectée (RESEND_API_KEY absente), clé révoquée, expéditeur absent du registre, domaine non vérifié | Les journaux serveur disent [auth/send-otp] deferred send failed et la raison (not-configured, rejected, forbidden, ...) ; personne ne peut se connecter : Runbook connecteurs, « Si personne ne peut plus se connecter » |
| « Trop de tentatives. Réessayez plus tard. » | cinq codes faux en 15 minutes pour l'adresse | Attendre la fin du verrou (15 minutes, doublé à chaque récidive : une heure au plus pour l'adresse, 24 heures au plus depuis la même IP), puis demander un nouveau code. Un code demandé pendant le verrou est refusé aussi. Verrou qui se renouvelle sans raison : voir « Se connecter », lever le verrou à la main |
| « Trop de demandes. Réessayez dans une minute. » | plus de 5 codes demandés en une minute pour la même adresse | Attendre une minute |
| « Code invalide » | code faux, expiré, déjà utilisé, ou adresse qui n'est pas membre (la page ne dit jamais laquelle) | Redemander un code ; vérifier l'invitation sur /members |
| Un membre ne voit pas une section, ou est renvoyé au cockpit | son rôle n'ouvre pas la section | Changer son rôle sur sa fiche (/members, puis la ligne du membre) |
Un membre est renvoyé sur /auth avec un toast | révoqué (« retiré »), échu (« expiré »), ou session antérieure à sa réactivation ou trop vieille (« a pris fin ») | Le réinviter, ou lui demander de se reconnecter |
| Tout le monde est déconnecté | NEXTAUTH_SECRET a changé | Normal : chacun se reconnecte |