Runbooks OrbitRunbook — Membres et crédits

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

  1. FOUNDER_EMAIL porte ton adresse, puis pnpm db:seed : ton membre fondateur existe, actif. Relancer le seed ne rétrograde ni ne révoque personne.
  2. NEXTAUTH_SECRET : une valeur propre à Orbit (openssl rand -base64 32), jamais celle d'une autre application (ni l'ancienne plateforme, ni easyconnector.app, produit distinct : voir Architecture des domaines). La changer déconnecte tout le monde et invalide les codes en cours.
  3. Sur Vercel : l'intégration Resend du Marketplace connectée au projet (Production et Preview, elle injecte RESEND_API_KEY), et l'expéditeur dans config.from de studio.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.
  4. Recommandé : l'intégration Upstash attachée au projet (Production et Preview), pour que les limites et le verrouillage des codes valent entre instances.
  5. Facultatif : ORBIT_SPEND_ALERT_USD (seuil de dépense réglée de l'équipe sur le mois) et ADMIN_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 :

PourOù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ènetout 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éditsla 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 fichemenu compte, Profiltout membre
Écrire au fondateurVotre avis, en haut à droitetout 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ôlePour qui
Créateurgénère (et travaille les concepts), dans la limite de ses crédits
RelecteurQC et relecture
Diffuseurdistribution et brouillons Postiz
Lecteurconsulte, 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ômeCause probableQue 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'adresseAttendre 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 adresseAttendre 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 cockpitson rôle n'ouvre pas la sectionChanger son rôle sur sa fiche (/members, puis la ligne du membre)
Un membre est renvoyé sur /auth avec un toastré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