Audits · septembre 2026Inventaire des surfaces de generation creative

Inventaire des surfaces de generation creative

Lecture seule. Rien n'a ete supprime, deplace ni renomme. Ce document est la carte a partir de laquelle le cockpit « Creative Intelligence OS » rebranchera l'existant. Date : 2026-09-19. Base : f7176ec4 (main)…

Lecture seule. Rien n'a ete supprime, deplace ni renomme. Ce document est la carte a partir de laquelle le cockpit « Creative Intelligence OS » rebranchera l'existant.

Date : 2026-09-19. Base : f7176ec4 (main). Perimetre : tout ce qui produit ou affiche une image, une video, un pictogramme, du motion ou une voix, hors toolbar du chat.

Ce que l'inventaire a trouve, en une phrase

Le depot n'a pas un Studio eparpille : il en a quatre, qui ne se connaissent pas, plus une cinquieme surface qui n'existe pas du tout.

Le quatrieme a manque a la premiere passe de cet inventaire, et la correction est gardee visible en §2bis plutot que fondue ici : un inventaire qui se corrige en silence n'apprend rien a son prochain lecteur.

#MoteurProduitEcrit ouTrace en baseAtteignable depuis
1Remotion / GitHub Actionsmp4, gif, webm, png deterministescreative/library/ (Blob)aucune ligne/admin/creative/generate, /ops/content, /ops/growth
2AI Gateway (Google)image, vecteur, video generativesstores/<id>/studio/ (Blob)GenerationRequest + StudioAssetle chat uniquement
3ElevenLabsmp3 + alignementBlobaucune ligne/ops/content uniquement
4Satori / next/ogPNG og, square, portrait, storyrien, rendu a la voleeaucune lignechaque page marketing, le Spy
5Avatar / identite———n'existe pas

Les quatre premiers utilisent cinq prefixes de stockage differents, quatre vocabulaires differents, et un seul des quatre laisse une trace en base. C'est la reunification que le cockpit doit operer.


1. Les pages

1.1 Ce qui n'est PAS un doublon, contrairement a l'apparence

/admin/creative/{clients,cockpit,concepts,pipeline,qc} et /[orgSlug]/~/studio/{clients,cockpit,concepts,pipeline,qc} portent les memes cinq noms. Ce ne sont pas deux implementations. Les deux importent les MEMES loaders (features/studio/boards.ts, features/studio/cockpit.ts) et les MEMES composants (features/studio/components/*). La seule difference est le scope, resolu par features/studio/internal-scope.ts : l'admin voit le Studio de l'organisation dont il est membre.

C'est deja le motif « noyau partage, deux profils » que le cockpit vise, applique au Studio d'agence. Il ne faut ni le defaire ni le dupliquer : il faut l'etendre aux trois moteurs media.

Un detail qui compte : le scope repose sur l'appartenance, pas sur un PLATFORM_ORG_ID. Le fichier explique pourquoi (un "" fail-soft change le comportement en preview, et « l'org ou quelqu'un tient agency.* » est circulaire). La plateforme est donc deja un tenant comme un autre.

1.2 Les surfaces de generation reelles

RouteRoleMoteurGarde
/admin/creative/generatele generateur : 10 formes x 6 toiles x 4 types de fichierRemotionrequireAdmin()
/admin/creative/librarybibliotheque designers, lecture seule : StudioAsset de l'org interne + kit de marque public/ + tokens + creative/library/—requireAdmin()
/ops/contentl'atelier de la recrue : meme catalogue, plus la voix offRemotion + ElevenLabsplatform.content.operate
/ops/growthlance un rendu sur le chemin du plan (u18, compositions legacy)Remotionplatform.growth.manage
/ops/creative/qcle verdict sur une creative—platform.creative.review
/[orgSlug]/[storeSlug]/studiole Studio du marchand, lecture seule—studio.*.read
/[orgSlug]/~/studio/*le Studio agence—studio.*
/drop/[token]livraison publique par jeton—jeton 192 bits

Trois surfaces lancent un rendu Remotion et elles convergent bien sur la meme fonction (services/creative/render-dispatch.ts), ce qui est sain. Mais elles ne partagent ni formulaire, ni catalogue affiche, ni retour d'etat : generator.tsx (admin) et creative-workshop.tsx (ops) sont deux formulaires distincts sur le meme catalogue JSON.

1.3 Ce qui n'a aucune surface

  • la generation generative (image, vecteur, video par AI Gateway) n'est atteignable que par le chat. Aucune page ne l'expose en manuel. C'est exactement l'asymetrie manuel/agent que le cockpit doit fermer ;
  • les rendus Remotion n'apparaissent dans aucune vue de generations : GenerationRequest ne les connait pas.

2. Les moteurs, un par un

2.1 Remotion — le moteur deterministe

  • catalogue : services/creative/templates.json, 10 templates (hook-statement, steps, feature-showcase, chart-reveal en motion ; stat-card, quote, comparison, announcement, og-card, icon-tile en still) x 6 formats (reel-9x16, landscape-16x9, square-1x1, portrait-4x5, og-1200x630, tile-512) ;
  • lecture : services/creative/template-catalog.ts, lu par le formulaire ET par Remotion, donc une forme ajoutee apparait sans code ;
  • dispatch : services/creative/render-dispatch.ts → launchTemplateRender (generateur) et launchPlanRender (plan Q4) → workflow GitHub creative-render.yml ;
  • sources : creative/ a la racine (compositions, primitives, scenes, templates, skills, pipeline) ;
  • sortie : creative/library/ sur Blob, listee par services/creative/generated-assets.ts ;
  • etat : listRenderRuns() interroge l'API GitHub a chaque chargement de page. Une seule implementation, ré-exportee par services/growth/creative-render.ts.

Ce qui manque : aucun GenerationRequest. Un rendu Remotion n'a ni cout enregistre, ni etat en base, ni lien vers une boutique. Il vit dans l'historique GitHub Actions et nulle part ailleurs.

2.2 AI Gateway — le moteur generatif

  • capacites : sept, dans le registre depuis ai-platform/2780 (features/ai/tools/studio-media-tools.ts, famille studio-media) ;
  • fabriques : toutes dans un seul fichier, features/ai/orchestrator/runtime/handler-tools-build.ts — buildImageTools (l. 498), buildVectorTools (l. 964), buildVideoTools (l. 1134), buildStudioDeliveryTools (l. 1629), buildStudioConceptTools (l. 2292) ;
  • journal : services/creative/generation-journal.ts → GenerationRequest (etat, cout estime et facture, idempotence, plafond de concurrence) ;
  • lecture : services/creative/generation-feed.ts → la section « Generations » du Studio marchand ;
  • sortie : stores/<storeId>/studio/ sur Blob, plus une ligne StudioAsset quand c'est sauvegarde ;
  • retention : services/creative/studio-blob-cleanup.ts (suppression au delete) + cron studio-orphans-prune (30 j pour les non sauvegardes).

Ce qui coince : les cinq fabriques vivent dans un fichier de 2300+ lignes qui appartient au runtime du chat. Le registre les compose sans les avoir deplacees — volontairement, pour ne pas casser les vingt gardes src/test/studio-*.test.ts. C'est la dette a solder quand le cockpit aura besoin de les appeler hors chat.

2.3 ElevenLabs — la voix

  • services/creative/voiceover.ts : script → MP3 + alignement au caractere (/with-timestamps), plus le quota lu avant le clic ;
  • appele depuis /ops/content uniquement ;
  • aucune trace en base, aucun lien avec une generation ou un asset.

L'alignement est deja recupere alors que rien ne le consomme : c'est la matiere premiere des sous-titres et du timing de scenes que le master prompt decrit (§48), deja payee et deja stockee.

2.4 Avatar — absent

Aucune entite. Ce qui porte le mot dans le schema n'en est pas :

  • CreativeConcept.marketingAvatar : texte libre, une persona marketing (« situation, job-to-be-done, langage »), explicitement pas un avatar visuel ;
  • StudioAsset.persona : texte libre ;
  • agents/identity-registry.ts : les identites des agents @Atlas, sans rapport.

Il n'y a ni Avatar, ni AvatarVersion, ni AvatarReference, ni voix attachee a une identite. Les quatre surfaces Avatar du master prompt sont donc a construire entierement, sans rien a reutiliser sauf le journal de generation et le systeme de references.


2bis. Ce que la premiere passe de cet inventaire avait MANQUE

Ecrit ici plutot que fondu dans les tableaux ci-dessus, parce qu'un inventaire qui se corrige sans le dire n'apprend rien a son prochain lecteur.

Le moteur oublie : Satori / next/og

Un quatrieme producteur d'images, qui ne passe par aucun des trois precedents :

FichierProduitDeclenche par
src/app/api/og/route.tsxPNG 1200x630, plus square 1080, portrait 1080x1350, story 1080x1920chaque page marketing, via buildMarketingMetadata
src/app/opengraph-image.tsxl'image OG par defautles crawlers sociaux
src/app/api/intelligence/og-image/route.tsproxy signe HMAC d'une og:image captureele Spy

Il rend en Edge, ne coute aucun credit, n'ecrit nulle part et n'apparait dans aucune bibliotheque. Il produit pourtant deja les quatre formats sociaux que le composer du cockpit veut proposer, et le generateur Remotion refait og-card et icon-tile a cote. Deux chemins pour la meme image sociale, et l'inventaire ne le voyait pas.

Le Remotion ecrit sous DEUX prefixes, lus par DEUX services

C'est la duplication la plus couteuse de tout cet inventaire, et la premiere passe l'avait ratee :

Lance depuisFonctionPrefixe BlobListe par
/admin/creative/generate, /ops/contentlaunchTemplateRendercreative/library/services/creative/generated-assets.ts
/ops/growthlaunchPlanRendercreative/out/<unit>/services/growth/media-desk.ts

Le meme moteur, le meme workflow GitHub, deux destinations et deux inventaires. Un fichier rendu depuis /ops/growth est invisible dans /admin/creative/library, et reciproquement. media-desk.ts ajoute meme une troisieme source, locale (creative/out/ sur la machine du proprietaire, plus creative/pipeline/ledger.json), toujours vide sur Vercel puisque gitignoree.

Le vocabulaire plane A / plane B / plane C existe deja dans le code et dans trois ADR (0017, 0024) : plane A la plateforme qui se produit elle-meme, plane B le Studio vendu, plane C le Studio agence. media-desk.ts ecrit noir sur blanc « Plane A only. Does not touch StudioAsset (plane B). » La separation est donc deliberee et documentee, pas accidentelle. C'est exactement ce que le cockpit doit reunir, et c'est une decision a prendre, pas un bug a corriger.

Les autres ecrivains de Blob que l'inventaire ne nommait pas

grep @vercel/blob rend 24 fichiers. Au-dela des trois moteurs deja listes :

FichierCe qu'il ecritRapport au Studio
src/app/api/upload/route.tsupload generiquesource de references
src/app/api/vendor/marketplace/upload/route.tscaptures d'annoncehors Studio
src/app/api/chat/upload/route.tspieces jointes du tourles references positionnelles du Studio
src/app/api/stores/[storeId]/assets/route.tsstores/<id>/<fichier>bibliotheque du marchand, prefixe DIFFERENT de stores/<id>/studio/
features/ai/tools/browser/screenshot.tsstores/<id>/agent-screenshots/captures de l'agent
services/algorithms/intelligence/probes/storefront-screenshot.tscaptures de storefronts tiersSpy
features/tracking/lib/artifacts.tsartefacts de scantracking

Donc cinq prefixes Blob differents portent des images qu'un composer voudrait pouvoir attacher : creative/library/, creative/out/, stores/<id>/studio/, stores/<id>/, stores/<id>/agent-screenshots/. Aucun selecteur d'asset unique ne les traverse.

Ce que cela change au classement

ElementStatut corrige
Generateur Satori / next/ogEXISTE, absent de l'inventaire initial
Deux prefixes Remotion, deux inventairesREDONDANT, la duplication principale
Cinq prefixes Blob sans selecteur communABSENT (un AssetPicker unique)
og-card / icon-tile Remotion vs /api/ogREDONDANT

3. Les composants

ComposantOuUtilise par
components/creative/generated-grid.tsxpartage/admin/creative/library, /ops/content
admin/creative/generate/_components/generator.tsxlocal admin1 page
ops/content/_components/creative-workshop.tsxlocal ops1 page
features/studio/components/* (18 fichiers)partageStudio agence + admin
features/studio/components/store/* (6 fichiers)partageStudio marchand + /collab

Un seul composant reellement partage entre les deux surfaces de generation : GeneratedGrid. Les deux formulaires sont distincts alors qu'ils lisent le meme catalogue et appellent la meme fonction.

Aucune primitive de type InputSlot, ParameterChip, ParameterPopover, AssetPicker n'existe : chaque formulaire reimplemente ses champs.


4. Ce qui est deja bon et qu'il ne faut pas defaire

  1. Le registre de capacites (ADR 0023) : une capacite porte le meme nom dans le chat, le canvas, le MCP et l'API. Le cockpit doit l'importer, jamais ecrire un second catalogue ;
  2. La plateforme est un tenant, resolu par appartenance ;
  3. GenerationRequest : etat reel, cout estime et facture, idempotence par Credit.idempotencyKey, plafond de concurrence ;
  4. Le scope dans la signature : listStoreGenerations(storeId) et resolveReferences({ scope }) rendent l'oubli impossible ;
  5. La file QStash (services/jobs/) : un seul broker, deja la ;
  6. Aucun faux pourcentage, garde par a-render-in-flight-has-a-surface.test.ts ;
  7. Le catalogue Remotion lu par les deux bouts : une forme ajoutee au JSON apparait dans le formulaire sans code.

5. Classement

ElementStatut
Studio agence (pipeline, gates, concepts, QC, cockpit)EXISTE
Studio marchand (lecture seule + generations)EXISTE
Registre de capacites mediaEXISTE
Journal de generation + cout + retentionEXISTE
Remotion : catalogue, dispatch, bibliothequeEXISTE
Voix off ElevenLabsEXISTE PARTIELLEMENT (aucune trace en base, un seul appelant)
Generation generative en manuelABSENT (chat uniquement)
Rendus Remotion dans GenerationRequestABSENT
Deux formulaires pour un meme catalogueREDONDANT
Les cinq fabriques dans le runtime du chatMAL PLACÉ
src/services/creative/** sans proprietaire dans ownership.jsonMAL PLACÉ
Primitives de composer (InputSlot, AssetPicker, …)ABSENT
Avatar / identite reutilisableABSENT
Lignée, verrous, presets, rejeu, comparaisonABSENT
StudioAsset.storeID (capitale) vs storeId ailleursMAL NOMMÉ

Rien n'est classe SAFE TO REMOVE : l'inventaire n'a trouve aucune surface creative morte.


6. Ce que le cockpit devra rebrancher, dans l'ordre

  1. un seul composer derive du catalogue, la ou il y en a deux ;
  2. les rendus Remotion dans GenerationRequest, pour que les trois moteurs aient un seul journal, un seul etat, un seul cout ;
  3. les cinq fabriques sorties du runtime du chat, pour etre appelables par une surface manuelle ;
  4. les primitives de slot, pour arreter de reimplementer l'upload ;
  5. l'avatar, qui n'a rien a reutiliser et doit etre concu ;
  6. le lineage et les locks, qui conditionnent la regeneration partielle.

Les cinq decisions de gouvernance (docs/ops/operator-decisions.md §8) restent prealables aux points 5 et 6.