Architecture de l’application
title: Architecture de l’application description: Vue d’ensemble du dépôt applicatif Orbit : le Brain et ses clients, les paquets, les produits, l’absence de plateforme parente et l’interface.
Ce document décrit le dépôt applicatif BoostEcom/orbit.easyconnector.app, reconstruit à partir d’une base figée de l’ancien dépôt Studio (voir la base source figée). Il est la source d’ingénierie que les commentaires du code citent ; la documentation produit publique vit dans le Centre d’aide.
- Orbit est le produit, Brain son cœur de contexte et de mémoire, Creative Studio (
/studio) un outil natif d’Orbit. Surfaces :https://orbit.easyconnector.app(/brain,/studio,/api/v1,/api/mcp), voir l’architecture des domaines. - « La plateforme » désigne dans ces textes l’ancienne application externe dont une partie du code a été copiée (provenance :
otp.ts, verrouillage, composer, gabarits…). Elle n’est pas le parent d’Orbit, ni une dépendance, ni une intégration : Orbit n’a pas de plateforme parente. Les ponts vers elle (P0 à P11, B1 à B10) sont retirés, et leurs variables (PLATFORM_*,CAPTURE_BRIDGE_SECRET,CINEMA_EXCHANGE_ORIGIN,PRODUCT_REPOS_READ_TOKEN,DERIVE_MJS,FILM_E2E) ne sont plus déclarées parapps/web/src/env/schema.ts. Une phrase « comme sur la plateforme », « la plateforme faisait… » est de la provenance : elle dit d’où vient un comportement d’Orbit, ou ce qu’Orbit a corrigé, jamais ce dont il dépend. - La Fabrique (la surface de production par lots de l’ancien cockpit) est retirée. Le rendu d’un gabarit Remotion reste servi (
renderTemplate, mode Motion du composer, §7.2). - Le registre des produits ne contient qu’un produit de démonstration,
orbit(section 5). - « La carte » (§4.4, §5, §8.1…) est l’ancienne carte de migration du dépôt hérité, archivée avec lui et non portée ici. Les références d’audit (G1-xx, G2-xx, G3-Bx, F.., SEC-.., LEDGER-..) et les jalons
studio/NNsont l’historique de ce code ; ils restent pour que les commentaires qui les citent gardent leur sens.
Le domaine SaaS cible est fixé par l’ADR-0017 : User → Workspace → WorkspaceMembership → Project → Memory, avec PlatformAdmin séparé. Les noms Prisma historiques (StudioMember, Product, StudioGeneration…) restent temporairement persistés pour éviter une migration big-bang (BrainEntry a, lui, disparu : ADR-0020).
Ce qui n’existe pas encore est marqué (phase N).
Où lire quoi
La numérotation des sections de l’ancienne architecture applicative est conservée : les commentaires du code et les runbooks la citent (« section 7.4 », « §4.4 »).
| Section | Sujet | Page |
|---|---|---|
| 0, 1, 5, 6, 9 | Orbit = le Brain, paquets, produits et modes, aucune plateforme parente, interface | cette page |
| 2 | Modèle de données persisté | Modèle de données |
| 3.1, 3.2, 3.4 | Connexion, deux portes, membres | Accès et sessions |
| 3.3 | Rôles et permissions | Permissions Studio |
| 4 | Crédits par membre (ledger) | Ledger & idempotence |
| 7.1 | Une génération | Pipeline de génération |
| 7.2, 7.3, 7.4 | Rendu de gabarit, balayage, vidéo asynchrone | Remotion & jobs async |
| 7.5 | Références et téléversements | Références et téléversements |
| 7.6 | Rétention et purge | Observabilité & rétention |
| 8 | L’application web, i18n | L’application web |
| Projets, Brain, onboarding, équipes | Surfaces de l’application | Brain, projets et équipes |
0. Orbit = le Brain, les outils = des clients
Orbit est headless. Son fossé est le Brain : le contexte que l'utilisateur y accumule, portable vers n'importe quel modèle. Les outils (Studio, aujourd'hui) sont des clients de ce Brain.
sources · notes · connecteurs · MCP/API brain:write · (plus tard : promotion explicite)
│ écritures GOUVERNÉES (droit, tenant, capacité, audit)
▼
┌────────────────────────────────────────────────────┐
│ BRAIN : Spaces → piliers → Memories │ facturé en CU, ABONNEMENT
│ seule vérité du contexte ; croît par ces écritures│ (un seul plan, +60 CU par pas)
└──────────┬──────────────────────┬──────────────────┘
lecture │ │ lecture (MCP, API REST)
native ▼ ▼
Studio (outil Orbit) Claude · ChatGPT · une plateforme tierce
facturé en CRÉDITS mêmes droits, mêmes scopes, même quota inclus
(usage, compteur séparé)
- Le Studio lit le Brain nativement (les Spaces liés à un projet,
SpaceBinding), mais n'est pas privilégié face à un client MCP ou API : les mêmes règles de tenant, de permission et de lecture s'appliquent partout. Une plateforme tierce consomme le Brain comme Claude le ferait. - Le Brain ne grandit que par des écritures gouvernées : sources (site, fichier), notes, connecteurs,
MCP/API avec le scope
brain:write, et plus tard une promotion explicite de ce qu'un outil a appris. Un outil n'écrit jamais dans le Brain de son propre chef (les apprentissages du Studio sont des observations de l'outil, ADR 0018). - Facturation : le Brain se facture en CU (un abonnement, Facturation Stripe) ; les outils en
crédits (l'usage, un compteur séparé) ; le B2B
custom= volumes négociés et, en option, la clé du client (BYOK), fixé depuis l'app d'administration.
1. Les paquets
apps/web Next 16 App Router, React 19, Tailwind 4, sombre, français
packages/db Schéma Prisma, migrations committées, client, seed, garde de cible
packages/remotion ex creative/ : React 18 + Remotion 4.0.242, projet pnpm à part (hors workspace)
packages/cinema moteur cinéma (cœur pur, couche React 19), contrat de capture v1, plateau local
packages/engine ledger par membre ; génération (journal, fournisseurs, Blob) ; rendus (dispatch, rappel, balayage)
products/ catalogue des produits (hydraté depuis la base) ; jeu de démonstration importé par le seed
Pourquoi des workspaces pnpm plutôt qu'une seule application : Remotion 4.0.242
est épinglé sur React 18, l'application et le moteur cinéma sont en React 19. Deux
versions de React ne cohabitent proprement que dans deux paquets distincts
(carte §8.1). packages/remotion va plus loin : il est exclu du workspace
(!packages/remotion), avec son lockfile committé et un node_modules à plat
(node-linker=hoisted, ce que Remotion attend pour son compositeur), installé
par pnpm remotion:install. Il ne partage avec le reste que des fichiers de
données lus par chemin relatif (le registre, le design dérivé, le catalogue des
gabarits, la règle d'horloge frame-times.mjs), jamais une dépendance. Le reste
suit la même logique : un paquet par propriétaire de données ou de contrat.
Les paquets exposent leurs sources TypeScript ("main": "./src/index.ts").
Next les compile via transpilePackages, Vitest et tsx les lisent directement.
Aucune étape de build intermédiaire.
Pourquoi Prisma vit dans packages/db, pas dans apps/web
Le client est consommé par l'application et par le moteur
(packages/engine, phase 3 : journal, ledger, callbacks de rendu), et plus tard
par des scripts hors Next (import des données fondatrices, crons). Un paquet unique
possède donc le schéma, les migrations, le client et le seed ; les autres
l'importent. apps/web/prisma aurait obligé le moteur à dépendre de l'application.
Le client généré et le pool (lot 10, G2-16)
- Le générateur
prisma-client, celui de Prisma 7 (prisma-client-jsest déprécié) :generator client { provider = "prisma-client", output = "../src/generated/prisma", runtime = "nodejs", moduleFormat = "esm" }. Le client est du TypeScript écrit dans le paquet, compilé comme le reste (par Next, Vitest,tsx), plus rien dansnode_modules/.prisma. Le dossier est ignoré par git et par ESLint, régénéré parpnpm db:generate, que lepostinstallde la racine lance (en CI et au build Vercel aussi). - Les consommateurs ne changent pas : ils importent
@orbit/db, qui réexporte par nom le client, l'espacePrismaet chaque enum depuis./generated/prisma/client(et les types parexport type *).index.test.tsvérifie le générateur, sa sortie, qu'aucun fichier desrc/n'importe@prisma/client, et que chaque enum du schéma est réexporté.@prisma/clientreste une dépendance : c'est le runtime que le client généré importe (@prisma/client/runtime/client), et le type que lit@auth/prisma-adapter. - Le pool est explicite (
createPooldanssrc/client.ts) : unpg.Poolde 5 connexions au plus, 10 s d'attente au plus pour une connexion (le défaut depgest « attendre toujours »), passé àPrismaPg(pool, { disposeExternalPool: true })($disconnect()le ferme encore, un script se termine). Sur Vercel seulement (VERCEL=1),attachDatabasePool(pool)de@vercel/functionsle confie au runtime Fluid, qui libère les connexions inactives avant de suspendre une instance au lieu de les laisser ouvertes contre Neon.client.test.tsprouve que le pool n'est attaché que là. - Aucune migration : le changement de générateur ne touche pas au schéma
(
pnpm db:drift: « No difference detected »).
5. Produits et modes
Deux modes de production :
- libre : aucune entrée de registre,
productId = null. Le lecteur refuse qu'un produit s'appellelibreetworkspaceFor(null | "libre")renvoie le workspace libre ; - connecté : un produit de
products/products.json, avec son dépôt, sa branche, un SHA épinglé pour les dériveurs, son design dérivé du code et son mode de capture.
| Produit | Statut | Dépôt | Capture |
|---|---|---|---|
orbit | registered | aucun (repo: null) | local-fixture, sans repli ni origine de production |
C'est le seul produit du registre (products/products.json, version 2,
default: "orbit") : le jeu de démonstration que le seed importe pour
Creative Studio. Les produits réels d'un client sont des données créées
depuis l'interface (section « Produits, onboarding et Brain legacy »). Les
anciens produits du registre hérité (le produit de l'ancienne plateforme, une
extension, un thème, une app Shopify, un connecteur) ont été retirés à la
migration (Inventaire de retrait du Studio legacy).
Le lecteur (products/src/index.ts) valide le fichier à l'import et lève une
erreur sur un id inconnu, au lieu de retomber sur un produit par défaut (règle
héritée de la plateforme, growth-web/3047). productFor n'a pas d'id par
défaut (un id absent n'est pas le produit de démonstration, constat F8) ;
workspaceFor résout aussi un produit planned, ouvrir la production reste la
décision de l'appelant. design.json vaut null tant qu'aucun jeton n'est
écrit (le registre nommait des fichiers absents, constat F7), et
index.test.ts exige que tout chemin non nul existe. Le produit orbit a le
sien : products/orbit/design/orbit.DESIGN.json, sans dériveur
(design.deriver: null). L'ancien dériveur, son contrôle --check contre un
checkout de la plateforme (E2E-18) et le workflow hebdomadaire
design-drift.yml ne sont pas portés : un contrôle de dérive propre à Orbit
sera écrit si un dériveur Orbit apparaît. Un produit local-fixture est filmé
sur la boucle locale seulement, à /stage/<scène> (plateau local de
@orbit/cinema).
6. Aucune plateforme parente (anciens ponts retirés)
Orbit n'a pas de plateforme parente. L'ancien studio s'appuyait sur les
ponts d'une application externe (P0 à P11 de l'ancienne carte §6.1 : un plateau
/ops/creative/cinema/<scène>, le contrat de capture v1, /api/og, des clés
bei_, un contrat UTM) et en prévoyait d'autres (B1 à B10,
Roadmap). Dans Orbit, ces ponts sont retirés,
avec leurs variables : PLATFORM_ORIGIN, PLATFORM_INTELLIGENCE_API_KEY,
PLATFORM_READ_TOKEN, CAPTURE_BRIDGE_SECRET, CINEMA_EXCHANGE_ORIGIN et
PRODUCT_REPOS_READ_TOKEN ne sont plus déclarées par
apps/web/src/env/schema.ts, ni par .env.example. Seules les variables
DataFast restent, pour le site DataFast d'Orbit (DATAFAST_*). Le contrat
de capture est désormais window.__ORBIT_CINEMA__ v1 (@orbit/cinema), servi
par le plateau local d'Orbit.
Le port PlatformBridge (capacité platform) qui portait la Veille de
l'ancienne application est supprimé de @orbit/connectors. Le port
CaptureSecret reste : c'est une capacité de produit (le mot de passe de
vitrine d'un site à filmer), sans rapport avec une plateforme, et sans
liaison déclarée aujourd'hui (Connecteurs Studio).
Les pages légales (/legal/*, liens de /auth) sont servies par la
documentation (LEGAL_ORIGIN = DOCS_ORIGIN de
apps/web/src/config/domains.ts, https://docs.easyconnector.app), par une
redirection 307 de next.config.ts (apps/web/src/lib/legal-pages.ts ; une
requête du routeur reçoit 204 du proxy). Un CTA de rendu ne doit jamais
pointer vers l'espace de travail authentifié.
9. Interface : émancipée de l'ancienne plateforme
Jusqu'à studio/15, l'interface était celle de l'ancienne application
d'origine, vendue octet pour octet (un manifeste, pnpm ui:sync, un garde
d'octets, une preuve au pixel). Elle appartient désormais à Orbit, qui la fait
évoluer comme le reste de son code : aucune parité au pixel avec une autre
application n'est recherchée. Ce qui la tient :
- Sombre intégral : tokens
--studio-*sur:root(src/styles/tokens.css), aucune classe de couleur hex dans un composant ou une page, une seule surface blanche, le stage de la galerie ; gardé parsrc/test/dark-only.test.ts. - Des pages qui existent : le menu compte, la barre latérale et les
cartes système lisent
src/features/studio/nav/routes.ts, etroutes.test.tsprouve que chaque lien résout une page, et qu'aucun code n'appellerouter.refreshni ne recharge la page. - Une porte par route :
src/test/route-permissions.test.tsassocie chaque route à sa permission. - Une liste de prix : le composer cite
@orbit/engine/prices, que le ledger applique (src/config/studio-prices.tsn'en déclare aucun). - Studio SaaS : ni boutique, ni Shopify, ni marchand dans le module ; un
produit du registre ou
libre.