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.
| # | Moteur | Produit | Ecrit ou | Trace en base | Atteignable depuis |
|---|---|---|---|---|---|
| 1 | Remotion / GitHub Actions | mp4, gif, webm, png deterministes | creative/library/ (Blob) | aucune ligne | /admin/creative/generate, /ops/content, /ops/growth |
| 2 | AI Gateway (Google) | image, vecteur, video generatives | stores/<id>/studio/ (Blob) | GenerationRequest + StudioAsset | le chat uniquement |
| 3 | ElevenLabs | mp3 + alignement | Blob | aucune ligne | /ops/content uniquement |
| 4 | Satori / next/og | PNG og, square, portrait, story | rien, rendu a la volee | aucune ligne | chaque page marketing, le Spy |
| 5 | Avatar / 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
| Route | Role | Moteur | Garde |
|---|---|---|---|
/admin/creative/generate | le generateur : 10 formes x 6 toiles x 4 types de fichier | Remotion | requireAdmin() |
/admin/creative/library | bibliotheque designers, lecture seule : StudioAsset de l'org interne + kit de marque public/ + tokens + creative/library/ | — | requireAdmin() |
/ops/content | l'atelier de la recrue : meme catalogue, plus la voix off | Remotion + ElevenLabs | platform.content.operate |
/ops/growth | lance un rendu sur le chemin du plan (u18, compositions legacy) | Remotion | platform.growth.manage |
/ops/creative/qc | le verdict sur une creative | — | platform.creative.review |
/[orgSlug]/[storeSlug]/studio | le 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 :
GenerationRequestne 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-revealenmotion;stat-card,quote,comparison,announcement,og-card,icon-tileenstill) 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) etlaunchPlanRender(plan Q4) → workflow GitHubcreative-render.yml; - sources :
creative/a la racine (compositions, primitives, scenes, templates, skills, pipeline) ; - sortie :
creative/library/sur Blob, listee parservices/creative/generated-assets.ts; - etat :
listRenderRuns()interroge l'API GitHub a chaque chargement de page. Une seule implementation, ré-exportee parservices/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, famillestudio-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 ligneStudioAssetquand c'est sauvegarde ; - retention :
services/creative/studio-blob-cleanup.ts(suppression au delete) + cronstudio-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/contentuniquement ; - 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 :
| Fichier | Produit | Declenche par |
|---|---|---|
src/app/api/og/route.tsx | PNG 1200x630, plus square 1080, portrait 1080x1350, story 1080x1920 | chaque page marketing, via buildMarketingMetadata |
src/app/opengraph-image.tsx | l'image OG par defaut | les crawlers sociaux |
src/app/api/intelligence/og-image/route.ts | proxy signe HMAC d'une og:image capturee | le 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 depuis | Fonction | Prefixe Blob | Liste par |
|---|---|---|---|
/admin/creative/generate, /ops/content | launchTemplateRender | creative/library/ | services/creative/generated-assets.ts |
/ops/growth | launchPlanRender | creative/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 :
| Fichier | Ce qu'il ecrit | Rapport au Studio |
|---|---|---|
src/app/api/upload/route.ts | upload generique | source de references |
src/app/api/vendor/marketplace/upload/route.ts | captures d'annonce | hors Studio |
src/app/api/chat/upload/route.ts | pieces jointes du tour | les references positionnelles du Studio |
src/app/api/stores/[storeId]/assets/route.ts | stores/<id>/<fichier> | bibliotheque du marchand, prefixe DIFFERENT de stores/<id>/studio/ |
features/ai/tools/browser/screenshot.ts | stores/<id>/agent-screenshots/ | captures de l'agent |
services/algorithms/intelligence/probes/storefront-screenshot.ts | captures de storefronts tiers | Spy |
features/tracking/lib/artifacts.ts | artefacts de scan | tracking |
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
| Element | Statut corrige |
|---|---|
Generateur Satori / next/og | EXISTE, absent de l'inventaire initial |
| Deux prefixes Remotion, deux inventaires | REDONDANT, la duplication principale |
| Cinq prefixes Blob sans selecteur commun | ABSENT (un AssetPicker unique) |
og-card / icon-tile Remotion vs /api/og | REDONDANT |
3. Les composants
| Composant | Ou | Utilise par |
|---|---|---|
components/creative/generated-grid.tsx | partage | /admin/creative/library, /ops/content |
admin/creative/generate/_components/generator.tsx | local admin | 1 page |
ops/content/_components/creative-workshop.tsx | local ops | 1 page |
features/studio/components/* (18 fichiers) | partage | Studio agence + admin |
features/studio/components/store/* (6 fichiers) | partage | Studio 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
- 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 ;
- La plateforme est un tenant, resolu par appartenance ;
GenerationRequest: etat reel, cout estime et facture, idempotence parCredit.idempotencyKey, plafond de concurrence ;- Le scope dans la signature :
listStoreGenerations(storeId)etresolveReferences({ scope })rendent l'oubli impossible ; - La file QStash (
services/jobs/) : un seul broker, deja la ; - Aucun faux pourcentage, garde par
a-render-in-flight-has-a-surface.test.ts; - Le catalogue Remotion lu par les deux bouts : une forme ajoutee au JSON apparait dans le formulaire sans code.
5. Classement
| Element | Statut |
|---|---|
| Studio agence (pipeline, gates, concepts, QC, cockpit) | EXISTE |
| Studio marchand (lecture seule + generations) | EXISTE |
| Registre de capacites media | EXISTE |
| Journal de generation + cout + retention | EXISTE |
| Remotion : catalogue, dispatch, bibliotheque | EXISTE |
| Voix off ElevenLabs | EXISTE PARTIELLEMENT (aucune trace en base, un seul appelant) |
| Generation generative en manuel | ABSENT (chat uniquement) |
Rendus Remotion dans GenerationRequest | ABSENT |
| Deux formulaires pour un meme catalogue | REDONDANT |
| Les cinq fabriques dans le runtime du chat | MAL PLACÉ |
src/services/creative/** sans proprietaire dans ownership.json | MAL PLACÉ |
Primitives de composer (InputSlot, AssetPicker, …) | ABSENT |
| Avatar / identite reutilisable | ABSENT |
| Lignée, verrous, presets, rejeu, comparaison | ABSENT |
StudioAsset.storeID (capitale) vs storeId ailleurs | MAL 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
- un seul composer derive du catalogue, la ou il y en a deux ;
- les rendus Remotion dans
GenerationRequest, pour que les trois moteurs aient un seul journal, un seul etat, un seul cout ; - les cinq fabriques sorties du runtime du chat, pour etre appelables par une surface manuelle ;
- les primitives de slot, pour arreter de reimplementer l'upload ;
- l'avatar, qui n'a rien a reutiliser et doit etre concu ;
- 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.