Application OrbitArchitecture de l’application

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 par apps/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/NN sont 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 »).

SectionSujetPage
0, 1, 5, 6, 9Orbit = le Brain, paquets, produits et modes, aucune plateforme parente, interfacecette page
2Modèle de données persistéModèle de données
3.1, 3.2, 3.4Connexion, deux portes, membresAccès et sessions
3.3Rôles et permissionsPermissions Studio
4Crédits par membre (ledger)Ledger & idempotence
7.1Une générationPipeline de génération
7.2, 7.3, 7.4Rendu de gabarit, balayage, vidéo asynchroneRemotion & jobs async
7.5Références et téléversementsRéférences et téléversements
7.6Rétention et purgeObservabilité & rétention
8L’application web, i18nL’application web
Projets, Brain, onboarding, équipesSurfaces de l’applicationBrain, 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-js est 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 dans node_modules/.prisma. Le dossier est ignoré par git et par ESLint, régénéré par pnpm db:generate, que le postinstall de 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'espace Prisma et chaque enum depuis ./generated/prisma/client (et les types par export type *). index.test.ts vérifie le générateur, sa sortie, qu'aucun fichier de src/ n'importe @prisma/client, et que chaque enum du schéma est réexporté. @prisma/client reste 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 (createPool dans src/client.ts) : un pg.Pool de 5 connexions au plus, 10 s d'attente au plus pour une connexion (le défaut de pg est « attendre toujours »), passé à PrismaPg(pool, { disposeExternalPool: true }) ($disconnect() le ferme encore, un script se termine). Sur Vercel seulement (VERCEL=1), attachDatabasePool(pool) de @vercel/functions le 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.ts prouve 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'appelle libre et workspaceFor(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.
ProduitStatutDépôtCapture
orbitregisteredaucun (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é par src/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, et routes.test.ts prouve que chaque lien résout une page, et qu'aucun code n'appelle router.refresh ni ne recharge la page.
  • Une porte par route : src/test/route-permissions.test.ts associe chaque route à sa permission.
  • Une liste de prix : le composer cite @orbit/engine/prices, que le ledger applique (src/config/studio-prices.ts n'en déclare aucun).
  • Studio SaaS : ni boutique, ni Shopify, ni marchand dans le module ; un produit du registre ou libre.