Runbooks OrbitRunbook — Back-office

Runbook — Back-office


title: Runbook — Back-office description: Exploiter l’application d’administration d’Orbit (apps/admin) : accès, leviers, audit.

Pour le fondateur. Le back-office d'Orbit est une application à part (apps/admin, paquet @orbit/admin), déployée comme son propre projet Vercel, protégé par Vercel Authentication sur « All Deployments ». C'est ce que Vercel recommande pour un outil interne : il n'est jamais mêlé à l'application client (apps/web). L'administration de la plateforme est sans rapport avec l'appartenance à un workspace (ADR 0017, ADR 0020 §4).

Le modèle de sécurité en bref

  1. La porte extérieure : Vercel Authentication. Avec « All Deployments », même le domaine de production exige une connexion à l'équipe Vercel. Personne hors de l'équipe n'atteint le code.
  2. Le code refuse par défaut. En production, l'application ne sert rien (page 503 « Back-office non protégé », sur toutes les adresses, fichiers statiques compris) tant que les deux conditions ne sont pas réunies : elle tourne sur Vercel (VERCEL=1, posé par Vercel) et ORBIT_ADMIN_PROTECTED=true. Elle refuse aussi si une clé interdite est présente (voir plus bas) ou si DATABASE_URL manque. En local (NODE_ENV=development), elle sert normalement.
  3. Chaque modification est tracée : une ligne StudioAuditLog (action admin.*, acteur vercel-team, source back-office), écrite dans la même transaction que la modification. Vercel Authentication ne transmet pas d'identité vérifiée à l'application : le journal d'audit de l'équipe Vercel dit qui était connecté.
  4. Aucune page publique, X-Robots-Tag: noindex partout, CSP stricte (frame-ancestors 'none').

Mise en place (une fois)

1. Créer le projet Vercel

Dans le dashboard Vercel, Add New… → Project, importer le dépôt d'Orbit une seconde fois (un projet distinct de celui de l'app) :

  • Root Directory : apps/admin ;
  • Framework Preset : Next.js ;
  • Node.js Version (Settings → Build and Deployment) : 24.x ;
  • commandes d'installation et de build : celles par défaut (pnpm install à la racine du monorepo, qui génère le client Prisma, puis next build) ;
  • laisser activée l'option « Include files outside the Root Directory in the Build Step » (Settings → Build and Deployment → Root Directory) : le back-office lit les paquets packages/* et le prototype de apps/web/internal/.

2. Les variables d'environnement

Settings → Environment Variables, uniquement :

VariableValeurEnvironnements
DATABASE_URLla base Postgres d'Orbit (celle de l'app : le back-office l'administre)Production (et Preview seulement si une base de preview est voulue)
ORBIT_ADMIN_PROTECTEDtrue, seulement après l'étape 3Production et Preview

Rien d'autre. Le back-office refuse de servir si l'une de ces clés est présente : AUTH_SECRET, STRIPE_*, INTERNAL_ORG_SLUGS, INTERNAL_SPEND_ALERT_USD, HIGGSFIELD(S)_*, MOTION_*, NEXTAUTH_SECRET, AI_GATEWAY_API_KEY, BLOB_READ_WRITE_TOKEN. Jamais le secret d'un autre produit (AGENTS.md, règle 2), et jamais la base d'un autre produit (règle 3). Ne pas brancher d'intégration du Marketplace qui injecterait d'autres secrets.

3. Activer Vercel Authentication sur « All Deployments »

Settings → Deployment Protection → Vercel Authentication : activé, portée « All Deployments » (et non « Standard Protection », qui laisse le domaine de production public). Enregistrer.

  • Vérifier dans le dashboard que l'option « All Deployments » est bien disponible sur l'offre de l'équipe : ce runbook ne présume pas de l'offre requise. Si elle ne l'est pas, ne pas poser ORBIT_ADMIN_PROTECTED : le back-office reste fermé (503), ce qui est le comportement voulu.
  • Ne créer ni « Shareable Link », ni « Protection Bypass for Automation », ni exception d'OPTIONS sur ce projet.
  • Vérifier depuis une fenêtre de navigation privée, non connectée à Vercel : l'URL de production doit afficher l'écran de connexion Vercel.

4. Seulement ensuite : ORBIT_ADMIN_PROTECTED=true

Ajouter la variable, puis redéployer (Deployments → … → Redeploy) : une variable ne s'applique qu'aux nouveaux déploiements. Sans elle, ou avec toute autre valeur que true exactement, la production répond 503.

Si la protection est un jour désactivée, retirer d'abord ORBIT_ADMIN_PROTECTED et redéployer.

5. Facultatif : un domaine dédié

AGENTS.md n'autorise un sous-domaine dédié qu'avec une exigence de sécurité indépendante : c'est le cas ici (un projet isolé derrière sa propre protection). Proposition : admin.orbit.easyconnector.app (Settings → Domains). Ce domaine n'est jamais lié publiquement : ni depuis l'app, ni depuis la documentation publique, ni depuis un e-mail. L'URL *.vercel.app du projet suffit aussi.

Ce que fait chaque page

PageRôle
/ Vue d'ensembleWorkspaces (total, créés sur 7 et 30 jours : l'inscription est libre), membres ; Brain : Spaces, Memories, lectures du mois et ce qu'elles coûtent à Orbit (somme des coûts réels BrainUsage) ; Studio : dépense réglée du mois sur tous les workspaces (lignes SETTLE du ledger, nettes des remboursements) ; l'alerte de dépense de la plateforme du mois (StudioSpendAlert) si elle existe (son seuil, ORBIT_SPEND_ALERT_USD, est une variable de l'app web).
/workspacesListe : nom, slug, création, e-mail du propriétaire, membres, Spaces, Memories, CU utilisés / capacité, mode (standard ou sur mesure), revenu du mois, coût Brain du mois et part de coût variable (marquée « à examiner » au-dessus de 30 %), crédits Studio mensuels, montant alloué ce mois. Recherche par nom, slug ou e-mail d'un membre ; 25 par page.
/workspaces/[id]Détail du workspace (membres, Spaces, dépense du mois, garde-fou de marge, lectures API et MCP du mois, journal d'audit) et deux formulaires : le mode de facturation et les crédits Studio mensuels (studioMonthlyCreditsUsd, USD ≥ 0, six décimales au plus, virgule acceptée). Chacun exige de retaper le slug du workspace ; chaque changement est tracé avec l'ancienne et la nouvelle valeur. Un identifiant inconnu répond 404.
/adminsLes lignes PlatformAdmin : ajouter ou retirer une adresse (forme canonique), tracé. La table enregistre qui administre la plateforme (l'app web la lit) ; l'accès au back-office, lui, est Vercel Authentication.
/prototypeLe prototype interne du Brain, lu depuis apps/web/internal/orbit-brain-demo.html (jamais copié : les deux ne peuvent pas diverger), avec sa propre CSP (scripts en ligne autorisés pour cette seule page).

Le mode de facturation : standard ou custom

Le mode d'un workspace ne se règle que depuis cette application (jamais une page publique, jamais l'app client).

  • standard (le défaut) : l'offre publique, un seul Orbit. La capacité vient de l'abonnement (120 CU + 60 CU par pas, 0 avant l'activation) et le quota de lectures MCP/REST lui est proportionnel (3 000 lectures par mois et par 120 CU). Repasser en standard efface les surcharges et la note, et recalcule la capacité depuis l'abonnement.
  • custom : conditions B2B négociées (volumes, éventuelle clé du client). Le formulaire accepte une capacité imposée (CU, entier ≥ 0 ; vide : celle de l'abonnement), un quota de lectures par mois (vide : proportionnel à la capacité) et les conditions négociées, obligatoires : elles sont copiées dans la ligne d'audit admin.workspace.mode (avec la note précédente). Un workspace custom garde sa capacité imposée quels que soient les événements Stripe, et n'est jamais marqué par le garde-fou de marge (son revenu est négocié hors Stripe).
  • Le workspace du fondateur (compte interne d'Orbit) se règle ici aussi : après la migration 20261002140000_single_orbit_plan, il retombe à 0 CU tant qu'on ne lui rend pas sa capacité (pnpm db:seed avec FOUNDER_BRAIN_CAPACITY_CU, ou le mode custom avec une note « compte interne »).

Le garde-fou de marge

Cible : marge brute ≥ 70 %, c'est-à-dire un coût variable ≤ 30 % du revenu. Pour chaque workspace et le mois UTC en cours : revenu (l'abonnement Orbit entitlé ; un abonnement annuel compte un douzième par mois), coût fournisseur (somme des coûts réels BrainUsage) et leur part. Au-dessus de 30 % (ou un coût sans revenu), le workspace est marqué « à examiner » dans la liste, sur sa fiche et sur la vue d'ensemble. Que faire : lire ses sources (/workspaces/[id]), l'inviter à étendre sa capacité, ou négocier (mode custom). Détails : Économie & paywall.

En local

echo 'DATABASE_URL=postgresql://…/orbit' > apps/admin/.env.local
pnpm --filter @orbit/admin dev   # http://localhost:3100

Le fichier .env racine n'est pas chargé : seules les deux variables du back-office le concernent.