ADRADR-0024 · Un moteur creatif, deux profils : la plateforme est un tenant de son propre Studio

ADR-0024 — Un moteur creatif, deux profils : la plateforme est un tenant de son propre Studio

Prolonge l'ADR 0023 (une capacite, un contexte, N grammaires) et l'ADR 0017 (Remotion est le moteur video des planes A et C). Ne revoque ni l'ADR 0003 (Creative est un process vertical, pas un second produit), ni l'ADR…

Statut

Accepté · 2026-09-18

Piliers : ai-platform, growth-web, app-shell, data-platform, design-system, security-identity, integrations, billing

Prolonge l'ADR 0023 (une capacite, un contexte, N grammaires) et l'ADR 0017 (Remotion est le moteur video des planes A et C). Ne revoque ni l'ADR 0003 (Creative est un process vertical, pas un second produit), ni l'ADR 0004 (le Studio agence est un tenant), ni l'ADR 0013 (deux namespaces de permissions). Chacun est cite la ou il contraint.

Contexte

Fixe la cible le 2026-09-18, apres avoir relu les captures de reference d'un studio generatif du marche :

On ne doit pas construire quatre produits differents. Il faut construire un noyau Studio commun, puis deux contextes d'execution strictement separes : Founder Studio pour la plateforme elle-meme et Store Studio pour chaque utilisateur / store. Meme logique pour les avatars : un moteur commun, deux surfaces / contextes / permissions, pas deux implementations divergentes.

Et la contrainte de methode : la codebase est la source de verite, pas le brief ; on cherche avant de conclure, on fusionne avant de creer.

L'audit du meme jour (docs/audits/2026-09-18-creative-growth-os.md) mesure ce que le depot porte deja, et le resultat n'est pas « il manque un Studio ». Il y en a deux, et ils ne se parlent pas :

Plane B, le Studio venduPlane A, la plateforme se produit elle-meme
Entreele chat, toggle mediaMode du menu « + » (composer-plus-menu.tsx)/admin/creative/generate, formulaire derive de templates.json
MoteurNano Banana Pro et Veo 3.1 sur l'AI Gateway, appeles dans le tour (handler-tools-build.ts)Remotion sur GitHub Actions (render-dispatch.ts), ElevenLabs (voiceover.ts)
Ou vont les fichiersBlob stores/<storeId>/studio/Blob creative/library/<template>/<slug>/
EnregistrementStudioAsset : concept, angle, hook, variante, cout, QC, dropaucune ligne en base : le chemin Blob EST l'inventaire (generated-assets.ts)
ArgentcanAffordStudioMedia avant, trackStudioMediaUsage apres, ligne Credit avec providerCost (billing.ts)rien : minutes de runner et caracteres ElevenLabs hors ledger
Quiun membre d'une organisation, sur un store en contextele role ADMIN (requireAdmin), depuis growth-web/2758 aussi platform.content.operate
Verite produitcatalogue, kit de marque, concepts valides du storefacts.md de boostecom-content, garde check-plan.mjs

Deux defauts structurels rendent la cible impossible sans decision :

  1. Les capacites media ne sont pas dans le registre. L'ADR 0023 a fait du registre src/features/ai/tools/ la liste unique de ce que la plateforme sait faire, et le canvas Workflow, le serveur MCP et l'API en derivent leur catalogue. Mais generateImage, composeProductImage, generateVector, generateVideo, saveStudioAsset, planCreativeVariants et saveCreativeConcept sont construits par tour de chat dans orchestrator/runtime/handler-tools-build.ts et assembles dans handler-tools-assembly.ts, hors de TOOL_FAMILIES. Consequence mesurable : le noeud ai.generate-image du canvas est planned (workflow-capabilities.ts), le catalogue servi par GET /api/workflow/v2/capabilities ne contient aucune generation, et le serveur MCP porte un seul outil Studio, le scope boostecom:studio.read, en lecture (un scope, pas un outil : l'ADR 0023 interdit de confondre les deux, et il vit dans lib/security/mcp-scopes.ts). Un agent peut parler d'une creative ; il ne peut en produire une que par le chat. « Manual et agent sur le meme pipeline » est donc vrai dans le chat et faux partout ailleurs, pour une raison de rangement.

  2. La plateforme n'a pas de profil. Quand elle se produit elle-meme, elle ne passe ni par StudioAsset, ni par le ledger, ni par la QC, ni par le lineage concept → variante que le Studio vendu porte deja. Elle a un second OS, avec ses propres refus, sa propre bibliotheque et aucun cout enregistre. billing/0613 en est le symptome comptable : quand l'equipe produit depuis son organisation d'agence, la depense apparait comme de la consommation client.

Trois faits deja tranches bornent la solution, et ce sont eux qui rendent la decision courte :

  • La plateforme n'est PAS nommee par un identifiant. acces-delegue-et-frontiere-studio.md a rejete PLATFORM_ORG_ID et Organization.kind ; features/studio/internal-scope.ts resout l'organisation interne par l'adhesion de l'admin, plus les organisations atteintes par un mandat org-scope. Le profil Founder existe donc deja comme un contexte : l'organisation que la plateforme opere, et rien d'autre.
  • Le Studio vendu et le Studio agence partagent deja leurs chargeurs (features/studio/boards.ts) et leurs composants (features/studio/components/), sous deux namespaces de permissions (ADR 0013). Le modele « un core, deux profils » est deja la forme du Studio ; il ne couvre simplement pas la generation.
  • Remotion est le moteur deterministe des planes A et C (ADR 0017), et la hierarchie « vraie UI > composants reels > captures > imitation generative » y est ecrite. Un moteur generatif ne remplace pas Remotion pour montrer le produit.
  • La liste des moteurs est fermee. AI Gateway (Vercel), ElevenLabs, Remotion, Playwright, Postiz, Datafast. Rappele le 2026-09-18, au moment ou les captures d'un studio generatif du marche servaient de reference : « on reproduit, en aucun cas on va l'utiliser ». Ce qu'on copie est la forme des controles ; le moteur derriere reste le notre.

Décision

Il y a un seul moteur creatif. Ses capacites vivent dans le registre, son contexte est le tenant, et « Founder » comme « Store » sont deux valeurs du contexte, jamais deux implementations. Concretement :

  1. Les capacites media entrent dans le registre. Une famille studio-media (etape studio, stages.ts) porte generateImage, composeProductImage, generateVector, generateVideo, saveStudioAsset, planCreativeVariants et saveCreativeConcept, avec le meme nom qu'aujourd'hui. Ce qu'elles exigent en plus du contexte commun (les images attachees au tour, l'enveloppe de facturation) devient un champ de AtlasToolContext rempli par chaque grammaire, et jamais un argument que seule la grammaire chat sait donner. Le chat ne perd rien ; le canvas, le MCP, l'API et le cockpit /ops gagnent la generation par derivation, sans qu'aucune liste ne soit recopiee.

  2. Toute generation est une requete, et une requete est une ligne. Un objet GenerationRequest (capacite, parametres valides, references resolues par leur identifiant, scope, demandeur, cout estime) est ce que le chat, un noeud du canvas, un appel MCP et le formulaire /ops construisent ; un enregistrement persiste avant l'appel provider et porte l'etat (queued, submitted, processing, completed, failed, cancelled), le cout observe et l'artefact produit. Les generations longues passent par les jobs QStash existants (services/jobs/), pas par le tour de chat : le clip qui depasse 180 s cesse d'etre la seule depense du systeme qui peut disparaitre entre le debit et la livraison (ai-platform/0377). L'appel synchrone reste possible pour ce qui tient dans le tour ; il ecrit la meme ligne.

  3. Un provider est un adaptateur derriere une capacite produit, jamais une capacite, et la liste des providers est fermee. Les moteurs de cette plateforme sont l'AI Gateway de Vercel (images et videos), ElevenLabs (voix), Remotion (composition deterministe), Playwright (captures du produit reel, la marche du haut de la hierarchie de l'ADR 0017), Postiz (distribution) et Datafast (mesure). Le proprietaire l'a redit le 2026-09-18 : un studio generatif tiers est une reference d'ergonomie, jamais un moteur de ce produit. Aucun nouveau fournisseur de generation n'entre sans une decision ecrite.

    Ce que l'abstraction achete n'est donc pas d'ajouter des fournisseurs : c'est que character-motion-transfer soit une capacite produit dont l'implementation (une option de Veo, une composition Remotion, ou rien) est un detail que l'UI, le canvas, le MCP et l'API ne connaissent pas. Une capacite que nos moteurs ne servent pas est declaree planned, visible et desactivee, avec la raison. config/ai-models.ts reste le seul catalogue de modeles.

  4. Le profil Founder est un tenant. La plateforme se produit elle-meme depuis l'organisation qu'elle opere, resolue par adhesion et mandat comme internal-scope.ts le fait deja, sur un Store qui porte sa marque. Ses rendus sont des StudioAsset de ce store, ses depenses sont des lignes du ledger de ce store, sa QC est la QC du Studio, et sa verite produit est facts.md monte comme source de connaissance de ce store, avec sa garde. Le generateur Remotion devient une capacite du registre (renderComposition) que ce tenant exerce comme les autres ; creative/library/ reste lisible le temps de la migration et cesse d'etre un second inventaire. Ce que ce profil a en plus est du contexte, pas du code : les mandats platform.*, la lecture du depot comme verite produit, les canaux Postiz de la plateforme. Ce qu'il n'a pas : un identifiant code en dur, une branche if (isFounder) dans une capacite, un stockage a part.

  5. Un store ne voit que son scope, a chaque couche, et un test le prouve. Le prefixe Blob, la ligne Credit, les lectures where: { id, storeID } et les portes de studio-authorization-doors tiennent deja pour les ecritures. Deux trous sont fermes par cette decision : la resolution des references (studio-references.ts) accepte aujourd'hui toute URL Blob publique, donc l'asset d'un autre store passe l'allowlist ; et le prefixe studio/unassigned facture une organisation sans donner de proprietaire au fichier. Une reference se resout par identifiant d'asset dans le scope, et un rendu sans store est refuse avant l'appel provider. Les tests de scope croise (store A ne lit ni ne reference ni ne sauve vers store B ; un mandat store n'ouvre aucune capacite du profil Founder) sont la garde de ce point, pas la relecture.

  6. Une identite visuelle est un objet, et elle ne s'appelle pas « avatar ». Dans ce depot, avatar designe deja l'avatar marketing, la cible d'une creative (CreativeConcept.marketingAvatar, la matrice Avatar × Angle du skill creative-ads, StudioAsset.persona). Le personnage reutilisable de la cible, le fondateur, un porte-parole, un personnage de marque, avec ses references, ses versions, ses attributs et sa voix, est un objet distinct, StudioIdentity, scope comme un asset (storeID), reference par @nom dans une requete, et resolu par le meme resolveur de references. Le mot « avatar » reste libre pour l'ecran, jamais pour un identifiant du moteur.

Ce que cette decision change dans la doctrine figee de creative-media-os.md §2, et il faut le dire plutot que le laisser deriver : la regle dure 1 (« un render A n'ecrit jamais un StudioAsset marchand ») et la regle 3 (« le chat B ne lit jamais facts.md comme verite pub ») etaient ecrites pour empecher un melange entre la production de la plateforme et celle d'un client. Le point 4 garde exactement cette intention et change son mecanisme : un render de la plateforme ecrit un StudioAsset de son propre store, jamais d'un store client ; et facts.md est la verite produit de ce store-la, montee comme source de connaissance de ce seul tenant, donc invisible a tout autre chat par construction. La regle 4 (ADR 0013) est inchangee.

La regle 2 (« un render C n'ecrit jamais le ledger Postiz Growth ») demande une reponse plus honnete que « inchangee », parce que ce qui la tenait etait la separation des planes, et que cette decision la remplace. Ce qui la tient desormais est une limite connue, pas un mecanisme : POSTIZ_API_KEY est une seule valeur d'environnement, celle de la plateforme, donc aucun profil Store n'a de file Postiz a atteindre. La distribution est mono-tenant, c'est mesure dans l'audit du meme jour, et la tranche 8 est l'endroit ou elle cesse de l'etre : le jour ou un store a son propre canal, la regle 2 devra etre portee par une capacite de distribution refusee hors du tenant qui possede le canal. Le dire maintenant coute une phrase ; le decouvrir apres coute un post publie sous le mauvais compte.

Le plane C (l'agency desk) n'est pas supprime et n'est pas un troisieme profil : c'est le profil Store, applique a un Store de l'organisation agence. C'est deja ce que l'ADR 0004 a decide, et rien ici ne le change.

La doctrine passe de trois planes a un moteur et des scopes, et creative-media-os.md le note en tete de son §2 dans la premiere PR de la tranche 4.

Ce que cette decision change dans l'ADR 0017, et qu'il faut nommer plutot que couvrir d'un « prolonge » : sa deuxieme puce de decision (« les contenus A vivent sous creative/ + skills + Postiz ») cesse de valoir pour l'enregistrement. Un rendu de la plateforme devient un StudioAsset du store de la plateforme, et creative/library/ est un chemin de transition. creative/ reste la source des compositions Remotion, et Remotion reste le moteur video : le reste de l'ADR 0017 est inchange. Le frontmatter garde supersedes: "" parce qu'il n'y a pas supersession totale, mais la clause amendee est nommee ici.

Un septieme chemin de generation existe dans le depot et il faut le nommer pour qu'il ne soit pas lu comme une exception a la liste fermee. src/config/native-mcps.ts declare le serveur MCP natif higgsfields (generate_image, generate_video, reframe), offert dans le menu « + » du composer. Qualifie le 2026-09-19 : c'est un wrapper des modeles que notre AI Gateway sert deja, donc il ne peut exister que comme possibilite de secours (leur MCP ou leur API, en cas de panne de notre chemin), et en aucun cas comme notre systeme. La liste des moteurs reste a six. Ce que le depot doit corriger, c'est la presentation : la page legale publique le declare sous « Creation d'actifs : Studio » et le composer l'offre au meme rang que nos capacites, pendant que pricing-copy-engines.test.ts le range, correctement, dans les moteurs que nous n'appelons pas. backlog/integrations/2794 aligne les trois endroits sur cette regle.

Ce que cette decision ne fait pas : elle ne renomme pas le canvas. Le depot a tranche en septembre 2026 que le canvas s'appelle Workflow et que Studio est le hub creatif (workflow/v2/README.md, ai-platform/2703) ; le brief appelait « Studio » le canvas, et c'est le brief qui s'aligne. La « creative surface » de la cible est le Studio : ses boards, sa bibliotheque et le composer de generation que la tranche 1 lui donne. Le canvas reste la grammaire « graphe » des memes capacites.

Alternatives écartées

OptionPourquoi non
Un Founder Studio a part, page admin dediee avec ses propres outilsC'est l'etat actuel (plane A), et c'est ce qui coute : deux inventaires, deux refus, aucun ledger, une QC absente. L'ADR 0003 l'avait deja refuse pour le Studio vendu (« aucun deuxieme Studio »).
Laisser les outils media dans l'orchestrateur et faire lire handler-tools-build.ts par le canvas et le MCPUne seconde porte vers les memes outils, sans TOOL_FAMILIES, sans etape, sans risque declare : exactement la liste locale que l'ADR 0023 interdit, et que the-studio-runs-what-it-declares ne pourrait pas deriver.
Nommer l'organisation de la plateforme (PLATFORM_ORG_ID, Organization.kind)Rejete par acces-delegue-et-frontiere-studio.md : un "" fail-soft change le comportement en preview, un flag se mint. L'adhesion et le mandat existent, sont revocables et audites.
Adopter un studio generatif tiers comme moteur (celui dont les captures servent de reference d'ergonomie, ou un autre)Refuse le 2026-09-18 : « on reproduit, en aucun cas on va l'utiliser ». Et l'ADR 0017 avait deja mesure pourquoi la demonstration du produit ne peut pas etre generative (fidelite de marque et d'interface). Les captures restent une reference d'ERGONOMIE : ce qu'on copie est la forme des controles, jamais le fournisseur.
Une capacite que nos moteurs ne servent pas, rendue quand meme par un fournisseur ajoute a la voleeUn fournisseur est une facture, une cle, une surface d'attaque et une dependance de disponibilite. Une capacite sans moteur est planned, visible et desactivee, avec sa raison : c'est la regle que the-studio-runs-what-it-declares applique deja au canvas, et elle vaut ici.
Les captures de reference comme spec exhaustive de l'UI, reproduites une par uneLes captures ne sont pas dans le depot au jour de cette decision. Un squelette de matrice existe dans l'audit ; la parite d'interaction attend les captures reelles. Decider l'UI sans elles serait deviner.
Appeler « Studio » le canvas, comme le briefRenomme un produit deja nomme, contre ai-platform/2703 et le README du canvas. Deux mots pour une chose est le defaut que l'ADR 0023 refuse (« si trois surfaces nomment differemment la meme chose, il y a trois produits »).
Reutiliser « avatar » pour l'identite visuelleCollision avec l'avatar marketing dans le schema, le skill et les boards. Un nom qui designe deux choses casse la regle « une capacite porte le meme nom partout ».
Attendre AI SDK 7 pour l'asynchroneL'ADR 0019 tient la 6 jusqu'a une eval verte, et il nomme lui-meme le signal qui rouvre la question : « une fonctionnalite de la 7 dont un item a besoin ». Le contrat start / status de la 7 en est une, pour Veo. Mais une ligne GenerationRequest et un job QStash ne dependent d'aucune version du SDK : l'asynchrone se gagne cote plateforme avant de se gagner cote SDK. La migration reste un item ; elle n'est plus un prealable.

Conséquences

Ce que ca coute, et on l'accepte :

  • Le registre grossit de sept outils et d'un champ de contexte. AtlasToolContext gagne les attachements du tour et l'enveloppe de facturation ; chaque grammaire doit les remplir, et le canvas doit apprendre a attacher une image a un noeud. C'est la tranche 1, et elle touche ai-platform en profondeur.
  • Une table de plus (GenerationRequest), additive, aucune colonne requise sans defaut sur un modele existant ; le plafond de not-null-column-adds.test.ts monte de ses colonnes neuves, chacune nommee.
  • La plateforme paie ses propres credits, visiblement. Le point 4 fait entrer la production interne dans le ledger. Comment la distinguer du revenu consomme (une ligne interne, un compte separe, un taux) est billing/0613, une decision de gouvernance, et cet ADR ne la prend pas : il rend seulement la depense visible la ou elle etait invisible.
  • creative/library/ et /admin/creative/generate deviennent des chemins de transition. Rien n'est supprime avant que la meme capacite soit atteignable par le registre et que la bibliotheque du store de la plateforme liste les memes fichiers.
  • Des capacites resteront planned longtemps. Le mouvement d'un personnage, le remplacement d'un personnage dans une video source et l'enchainement de scenes cinematiques ne sont servis par aucun de nos moteurs aujourd'hui. L'abstraction les rend nommables et desactivees plutot qu'absentes ; elle ne les rend pas disponibles, et aucun fournisseur n'est ajoute pour les obtenir sans une decision ecrite.

Ce que ca achete :

  • une capacite creative existe une fois et s'exerce depuis cinq grammaires ; en ajouter une (Motion d'un personnage) en ajoute une partout ;
  • la production de la plateforme est mesuree, relue et lignee comme celle d'un client, donc les apprentissages (CreativeLearning) la couvrent aussi ;
  • un store client obtient, le jour ou son plan l'inclut, exactement le Studio avec lequel la plateforme a fait sa propre croissance, sans qu'une seconde copie ait ete ecrite.

Le signal qui dirait de revisiter : une capacite creative que le registre ne sait pas exprimer parce qu'elle est un flux long avec un humain dans la boucle (un montage a plusieurs allers-retours). Ce jour-la on etend le registre a des capacites a etats, comme l'ADR 0023 le prevoit, on ne recree pas un editeur a cote.

Comment c'est appliqué

Les trois premieres tranches sont livrees, et c'est ce qui fait passer cet ADR de proposed a accepted : chaque point est desormais tenu par une garde qui echoue, pas par une relecture. Les trois dernieres lignes du tableau restent des gardes ATTENDUES, et elles sont ecrites au futur pour cette raison.

PointGarde qui le tiendra
1, capacites media dans le registreLivre. src/test/the-studio-runs-what-it-declares.test.ts derive executeur et catalogue, ET son dernier bloc lit les sept capacites media dans le registre, verifie que six sont planned derriere la porte d'approbation du canvas et que planCreativeVariants, seule lecture, est available — depuis leur RISQUE, pas depuis une liste. src/test/surfaces-import-their-capabilities.test.ts, annoncee par l'ADR 0023 et absente du disque au jour de cette decision, existe. Reste une moitie que ce point ne peut pas gagner seul : le scope MCP d'ECRITURE media, qui depend d'une decision de gouvernance (integrations/0714) et pas d'une garde
2, une requete = une ligneLivre : src/test/a-generation-is-a-row.test.ts
3, provider = adaptateurLivre : scripts/check-model-catalogue.mjs + src/test/model-catalogue.test.ts — config/ai-models.ts reste le seul endroit ou un identifiant de modele est ecrit
4, Founder = tenantanalytics-planes-style : aucun if (isPlatformAdmin) dans une capacite studio-media ; le store de la plateforme est resolu par internal-scope.ts, jamais par une constante
5, scope a chaque coucheles tests de scope croise nommes ci-dessus, dans src/test/
6, StudioIdentitydocs-corpus et le schema : un modele, un nom, reference @ resolue par studio-references.ts

Tant qu'une garde n'existe pas, la ligne qu'elle tient est une regle tenue a la main, donc une regle qui sera violee. Les lignes 4 a 6 sont dans cet etat ; les lignes 1 a 3 n'y sont plus.