Audits · septembre 2026Les trous du Studio, de l'Avatar, du MCP et de l'API

Les trous du Studio, de l'Avatar, du MCP et de l'API

Lecture seule. Aucun fichier applicatif n'a ete touche. Ce document et les items de backlog qu'il ouvre sont le seul livrable. Date : 2026-09-19. Base : 23be2e2f (main). Perimetre : le Studio (marchand, agence, siege)…

Lecture seule. Aucun fichier applicatif n'a ete touche. Ce document et les items de backlog qu'il ouvre sont le seul livrable.

Date : 2026-09-19. Base : 23be2e2f (main). Perimetre : le Studio (marchand, agence, siege), la section Avatar, la porte MCP et la porte REST. Hors perimetre : le chat lui-meme, le marketplace, l'intelligence, la facturation.

Périmètre

Ce qui a ete lu, et c'est la liste complete : src/lib/security/mcp-scopes.ts, src/app/api/mcp/[storeId]/register-tools.ts, src/lib/security/studio-sections.ts, src/lib/security/permissions.ts, src/lib/security/platform-permissions.ts, src/features/studio/ops-routes.ts, src/features/studio/ops-boards/**, src/features/studio/creative-os*.ts, src/components/studio/**, src/services/creative/**, src/features/ai/tools/studio*.ts, src/app/api/organizations/[orgId]/studio/[section]/route.ts, content/docs/en/, content/runbooks/en/, content/handbook/fr/, src/lib/content/docs-surface.ts, prisma/schema.prisma, .claude/fleet/ownership.json, et la sortie de pnpm deadcode (knip).

Ce qui n'a PAS ete lu, et qui peut donc porter des trous que ce document ne voit pas : le canvas (features/ai/workflow/v2), le drop public (/drop/[token]) au-dela de sa garde, les six locales autres que en, et tout le pipeline Remotion hors du depot (creative/ a la racine n'a ete lu que par son catalogue).

Un avertissement qui change la lecture de la section 2

Au moment de l'audit, l'arbre de travail porte dix fichiers modifies et non commites, dont mcp-scopes.ts, register-tools.ts, studio-guard.ts et les six fichiers de messages/. Ce travail en vol ouvre exactement le trou que le proprietaire avait mesure : il fait passer boostecom:studio.read de un outil a quatre. Le HEAD a par ailleurs avance pendant l'audit (840045c2 puis 23be2e2f).

Tous les chiffres ci-dessous sont donc donnes au commit 23be2e2f, et le delta non commite est nomme la ou il s'applique. Un chiffre mesure sur l'arbre de travail aurait decrit un etat que personne d'autre ne voit.


1. Declare mais pas implemente

1.1 [P1] Une table de permissions marchande que personne ne lit, et sa copie vivante

STORE_STUDIO_SECTION_PERMISSION et le type StoreStudioSection (src/lib/security/studio-sections.ts:74) portent trois entrees (concepts, inFlight, deliveries) et zero lecteur : la seule autre occurrence dans le depot est le commentaire du fichier lui-meme, l. 24.

Ce que la surface marchande lit reellement est PRODUCTION_PERMISSIONS (src/features/studio/creative-os.ts:533), cinq entrees, dont trois portent les MEMES cles (concept, inFlight, deliveries, creative-os.ts:559-565).

Le cout n'est pas le code mort, il est dans l'en-tete du fichier de securite : « le mode d'echec que ce fichier existe pour empecher est l'evident : trois copies de la meme table, qui derivent, jusqu'a ce qu'une porte accorde ce qu'une autre refuse ». Il y a bien deux tables, et la morte est celle qui documente. Un agent qui boote sur studio-sections.ts apprend une porte qui ne s'ouvre nulle part.

→ item security-identity/2900.

1.2 [P1] Deux etats de generation sur six ne sont jamais ecrits

GenerationStatus declare six valeurs (prisma/schema.prisma:7584-7596). Les ecrivains vivent dans src/services/creative/generation-journal.ts :

EtatEcrivainAppelants hors du fichier
QUEUEDopenGeneration (l. 93)oui
SUBMITTEDmarkSubmitted (l. 127)4
PROCESSINGmarkProcessing (l. 135)0
COMPLETEDsettleDelivered (l. 152)5
FAILEDsettleFailed (l. 184)5
CANCELLEDsettleCancelled (l. 203)0

Trois consequences observables, pas une :

  • une generation dont le processus meurt entre markSubmitted et le reglage reste SUBMITTED pour toujours. Aucun chemin ne la ferme ;
  • RUNNING_STATUSES (generation-feed.ts:36) interroge PROCESSING, donc une des trois valeurs de la requete « ce qui tourne » est morte ;
  • l'outil MCP en vol listStudioGenerations annonce dans sa description « queued, submitted or processing ». Il promet un etat que le journal n'ecrit jamais.

→ item data-platform/2901.

1.3 [P1] Trois modes du composer sur quatre rendent une video de demonstration sous un prix reel

ComposerMode declare quatre modes (src/components/studio/types.ts:75), et MODE_TABS les affiche tous les quatre (data.tsx:229-232, nova portant meme un badge « New »).

Un seul produit quelque chose. gallery-context.tsx:282-338 : la branche mode === "image" appelle generateStudioImage (ligne reelle, solde, rendu, facturation) ; tout le reste tombe sur un setTimeout de 3,2 a 4,6 s qui resout sur DEMO_RESULT.videoSrc. Le commentaire l'assume (« these stay on the bundled demo asset until a polling-based flow replaces this timeout »), et rien n'est facture, ce qui est le bon choix.

Le trou est ailleurs : composerCosts (creative-os.ts:111-127) calcule et affiche un prix pour les quatre modes depuis la liste de prix media reelle, y compris replace a quinze secondes de video. L'ecran annonce donc un tarif exact sur trois boutons qui ne produisent rien. Un operateur ne peut pas distinguer « ca coute ce prix » de « ca ne marche pas encore ».

generateVideo existe pourtant et fonctionne, dans le chat (STUDIO_MEDIA_TOOL_NAMES, studio-media-tools.ts:72-80). L'asymetrie n'est pas un manque de moteur, c'est un manque de branchement.

→ item design-system/2902.

1.4 [P2] Deux modeles de voix sur trois sont inatteignables

VOICEOVER_MODELS declare trois modeles (voiceover.ts:56), dont eleven_flash_v2_5 que le fichier decrit lui-meme comme « le brouillon pas cher, moitie prix au caractere ». synthesizeVoiceover accepte input.model (l. 110) et retombe sur le defaut (l. 153).

Le seul appelant du depot est ops/content/_actions/index.ts:277, qui passe { text, slug } et jamais model ni language. Deux modeles sur trois, et la voie moitie prix, n'ont donc aucun chemin.

Pas d'item ouvert, et c'est delibere : le fichier vit dans src/services/creative/, que aucun pilier ne possede (cf. 2.3). Ouvrir un item dans un repertoire sans proprietaire produirait une PR que pnpm fleet:scope refuse. Il est bloque par platform-ops/2903 et sera ouvert apres lui.

1.5 [P3] Le cockpit se compte lui-meme a douze et a treize, il est onze

Derive : OPS_ROUTES porte 13 entrees (src/features/studio/ops-routes.ts), OPS_SECTION_IDS et OPS_BOARDS en portent 11 (types.ts:179, ops-boards/{creative,commercial,media,bulletin}.ts : 4 + 3 + 2 + 2). L'ecart de deux est correct et explique : /ops/creative/studio EST le cockpit, /ops/docs partage le corpus Academy.

Ce qui ne l'est pas : huit commentaires disent « douze » et sept disent « treize », pour la meme chose. Notamment ops-boards/index.ts:4 (« le registre des douze surfaces »), types.ts:146-148, ops-board-content.tsx:6-12, board-content.tsx:6-17, system-content.tsx:9-27. Seul README.md:151 dit onze.

→ item design-system/2907.

1.6 Ce qui a ete verifie et n'est PAS un trou

  • PLATFORM_PERMISSIONS : les cinq ids (permissions.ts:149-155) sont tous honores par au moins une porte reelle, y compris platform.hub.read qui ne passe pas par /ops mais par src/lib/hub/paywall-gate.ts. Rien a ouvrir.
  • StudioSectionId : les neuf sections marchandes plus les onze du siege sont toutes rendues (studio.tsx:56-96, la branche isOpsSection d'abord, puis le switch). Aucune section orpheline.
  • MAX_IN_FLIGHT_PER_ORG / inFlightCount : knip les signale, ils sont lus par overCapacity (generation-journal.ts:273) dans le meme fichier. Faux positif.
  • Les onze scopes MCP : chacun porte au moins un outil enregistre, et les 18 registerTool de register-tools.ts couvrent la totalite des outils declares. Aucun scope vide.
  • L'absence d'un scope boostecom:studio.write : ce n'est pas un oubli, c'est un refus ecrit. mcp-scopes.ts (arbre de travail) le motive en neuf lignes : un scope se justifie quand il permet de refuser quelque chose d'une autre nature, et la generation depensant de l'argent, elle aura sa propre famille le jour ou elle existera. Rien a ouvrir.
  • internal-pricing.ts : internalOrgSlugs et isInternalOrg sont signales par knip et sont lus par pricingForOrg et internalOrgIds dans le meme fichier, eux-memes lus par le cockpit. Faux positif.

2. Implemente mais pas atteignable

2.1 [Le focus] Ce qu'un agent externe peut faire sur le Studio, mesure

Au commit 23be2e2f, les trois portes du Studio, cote a cote :

PorteCe qu'elle ouvreCompte
Chat (features/ai/tools/)getStudioPipeline, getStudioClientGates, getStudioConcepts, getStudioQcQueue, getStudioGallery, getStudioCockpit (studio-tools.ts) plus les sept capacites media (studio-media-tools.ts:72-80)13
MCP (boostecom:studio.read)getStudioSection, cinq sections d'agence, lecture seule1 outil
REST (/api/organizations/[orgId]/studio/[section])les memes cinq sections, lecture seule1 route

Ce qu'un agent externe ne peut pas faire aujourd'hui via MCP, liste exhaustive et derivee :

  1. lire le journal des generations (GenerationRequest) : le lecteur existe (generation-feed.ts, quatre fonctions), son seul monteur est GenerationsPanel sur /collab/[storeId] et l'outil de chat getStudioGallery ;
  2. lire la production board d'une marque (buildStudioProduction, creative-os.ts:556) : gate, in-flight, rendus, livraisons, concepts, economie ;
  3. lire le prix d'une generation avant de la demander (composerCosts) ;
  4. lire l'une des onze surfaces du siege : readOpsBoard (ops-boards/index.ts:53) n'est appelee que par une server action ;
  5. declencher quoi que ce soit : aucune ecriture, par refus explicite.

Les points 1 a 3 sont en cours de fermeture dans cet arbre de travail, non commites. Le diff ajoute getStudioProduction, listStudioGenerations et getStudioPricing au meme scope, avec la porte relue a chaque appel (storeStudioPermissionsFor) et le cout retire de la reponse plutot que mis a zero sans studio.economics.read. Le scope passera de 1 a 4 outils et le serveur de 18 a 21.

Aucun item n'est donc ouvert pour les points 1 a 3. Ouvrir un item sur un travail en vol est le doublon le plus cher qu'un auditeur puisse produire. Les points 4 et 5 restent, et le 5 est un refus assume.

Le point 4 reste un vrai trou, mais il ne se traite pas par un outil MCP : une connexion MCP est scopee a une BOUTIQUE ([storeId]), alors qu'un board du siege est une lecture plateforme sous permission platform.*. Le loger sur le relais store reproduirait exactement le defaut que mcp-scopes.ts reconnait deja pour operator-docs.read (« la ressource est plateforme, l'URL est une boutique, la vraie maison est une route a elle »). Il est donc nomme ici et laisse sans item : c'est une route MCP operateur a part entiere, une decision d'architecture qui n'appartient pas a un auditeur.

2.2 [P1] La porte REST va rester en retrait de la porte MCP sur la meme ressource

src/app/api/organizations/[orgId]/studio/[section]/route.ts ouvre cinq sections, en lecture. C'est tout ce que find src/app/api -ipath "*studio*" rend, hors le cron studio-orphans-prune.

Quand le travail en vol atterrira, la MEME ressource sera lisible par un agent MCP sur trois surfaces de plus qu'elle ne l'est par un client HTTP authentifie. Les deux portes lisent les memes loaders et se reclament de la meme garde ; l'ecart n'a aucune justification ecrite nulle part, a la difference du refus d'ecriture qui, lui, en a une.

content/docs/en/api-overview.mdx ne nomme d'ailleurs pas le Studio une seule fois, donc la porte REST existante n'est pas davantage annoncee.

→ item app-shell/2904.

2.3 [P1] Le moteur creatif entier n'appartient a aucun pilier

Derive de .claude/fleet/ownership.json, en croisant ses globs avec les repertoires de premier niveau de src/services/, src/features/ et src/lib/ : deux repertoires sur l'ensemble ne sont couverts par aucun pilier, et l'un des deux est src/services/creative/ (9 fichiers : generation-journal.ts, generation-feed.ts, render-dispatch.ts, voiceover.ts, generated-assets.ts, template-catalog.ts, templates.json, studio-blob-cleanup.ts, plus un test). L'autre est src/lib/http/ (1 fichier).

C'est le journal des generations, le dispatch Remotion, la synthese vocale et le catalogue de gabarits : le coeur de production des quatre surfaces de cet audit. Ils tombent sur le conductor par defaut, ce que le $comment du fichier decrit comme « un signal que la carte est perimee ».

Effet de bord immediat et mesurable : le trou 1.4 (les modeles de voix) ne peut pas etre ouvert avant celui-ci, parce qu'une PR qui touche ce repertoire ne sait pas quel pilier declarer.

→ item platform-ops/2903.

2.4 [P3] Le barrel du module Studio n'a aucun importeur

pnpm deadcode rend deux fichiers entierement morts, dont src/components/studio/index.ts : le point d'entree public du module, qu'aucun fichier n'importe (tout passe par les chemins profonds). Le second est src/components/hooks/use-logs-count.ts, hors perimetre.

→ plie dans l'item design-system/2907.


3. Atteignable mais pas documente

3.1 [P0] Le Studio marchand, la surface vendue, n'a aucune page de documentation

C'est le trou le plus cher des trois sections, et il se mesure en une commande. content/docs/en/studio.mdx fait 82 lignes et cinq titres : la chaine a cinq etapes, les permissions, le score Go/No-Go, la livraison, et la difference agence / studio. Il decrit exclusivement le Studio d'agence. Aucune occurrence, dans tout le fichier, de : composer, mode, generation, image, video, cockpit, credit.

En face, ce qui est livre et qu'un client paie : le Studio est un MODE du cockpit de sa boutique, avec un rail a neuf sections, une galerie assise sur GenerationRequest, un composer a quatre modes, un selecteur de marque, un prix par generation lu sur la liste de prix media, et un solde debite a chaque image.

Les trois autres portes de doc sont muettes aussi : content/docs/en/credits.mdx ne contient ni « image » ni « video » ; api-overview.mdx ne contient pas « studio » ; et src/test/docs-corpus.test.ts derive bien les Systems, les roles, les plans, les scopes MCP, les webhooks et les connecteurs, mais rien de la surface Studio marchande, donc aucune garde ne pouvait signaler l'absence.

C'est la forme exacte du defaut que CLAUDE.md decrit pour /community/docs : un chiffre faux trompe un agent, une surface absente trompe le client qui decide d'acheter.

→ item growth-web/2905.

3.2 [P1] Le siege /ops n'est couvert par aucune garde de corpus, et trois de ses treize surfaces ne sont nommees nulle part

Derive route par route, en cherchant chaque href d'OPS_ROUTES dans content/runbooks/ :

RouteNommee dans le corpus operateur
/ops/creative/{qc,generate,library,cockpit,pipeline,clients,concepts}oui (creative.mdx)
/ops/contentoui (content.mdx)
/ops/bulletin/healthvia /ops/bulletin dans content.mdx
/ops/creative/studionon, aucune occurrence
/ops/growthnon, aucune occurrence
/ops/docsnon, aucune occurrence

Trois sur treize, dont /ops/creative/studio qui est le cockpit lui-meme, c'est-a-dire la surface la plus recente et celle par laquelle une recrue entre.

La cause est structurelle et c'est elle qu'il faut fermer : src/test/internal-docs-corpus.test.ts derive ses exigences d'ADMIN_CATEGORIES et ADMIN_ROUTES (src/config/admin-routes.ts), et jamais d'OPS_ROUTES. Or /ops existe precisement parce que ces surfaces ont ete SORTIES de /admin pour s'ouvrir sur une permission plutot que sur le role. Elles sont donc sorties de /admin et, par le meme mouvement, sorties du perimetre de la garde qui verifiait qu'elles etaient documentees. Personne ne pouvait le voir.

Troisieme corpus, meme silence : content/handbook/fr/ (4 pages, le guide qu'une recrue non technique lit le premier jour) ne contient pas une seule occurrence de « studio ».

→ item growth-web/2906.

3.3 Ce qui est documente et n'est PAS un trou

  • Les treize routes /ops sont toutes presentes dans le tableau Dashboard de CLAUDE.md, avec leur permission. La carte de demarrage est juste.
  • Les cinq permissions platform.* sont couvertes par grant-poles-cover-platform-permissions.test.ts et ops-pages-name-their-permission.test.ts : une page livree sans porte echoue en CI. La garde d'ACCES existe, c'est la garde de DOC qui manque.
  • content/docs/en/mcp-tools.mdx documente getStudioSection, sa famille native et la raison de sa colonne Shopify vide. Quand les trois outils en vol atterriront, docs-corpus.test.ts exigera leur couverture depuis MCP_SCOPES : le mecanisme fonctionne, il n'y a rien a ajouter.

4. Avatar : ce que le depot porte deja, et le mot qui bloque

4.1 Une correction au constat de depart

« La section existe dans le rail et ne rend rien » n'est plus vrai depuis design-system/2804. studio.tsx:85-96 rend un etat vide qui NOMME le manque : « A reusable character for this brand, so a face stays the same across every render. Nothing is built behind this row yet ». Le rectangle noir muet a ete remplace.

4.2 Le substrat qui existe, derive

Ce qui existeOuCe que ca donne a un avatar
CreativeConcept.marketingAvatarprisma/schema.prisma:7973l'avatar au sens PERSONA, avec son garde-fou explicite (:7972, « women 25-34 n'est pas un avatar »)
La grille Avatar Card du SOPsrc/features/ai/skills/creative-ads/prompt.md §3a-3b2 a 4 cartes reellement distinctes, puis la matrice Avatar x Angle
L'ecran qui valide un avatar/ops/creative/concepts et /[orgSlug]/~/studio/concepts« Valider l'avatar et l'angle dont une production part » (ops-routes.ts)
Les images de reference en entreefeatures/ai/orchestrator/runtime/studio-references.tsun personnage peut deja etre DONNE a une generation, il ne peut pas etre RETENU
La voix, avec alignementservices/creative/voiceover.tsla piste audio d'un avatar parlant, caractere par caractere, existe deja et tourne
Le compositing deterministeservices/creative/templates.json10 gabarits x 6 toiles, dont 4 en motion
StudioAsset + variantOfId / variantAxisschemale lineage ou une serie de rendus d'un meme personnage se rangerait

4.3 Le trou reel, et il n'est pas technique

Deux sens du mot « avatar » cohabitent dans ce depot sans jamais se croiser : le persona marketing (partout : schema, SOP, concepts, gates) et le personnage visuel reutilisable (nulle part, sauf dans la copie de l'etat vide qui le promet).

Et sur le second, le depot porte deja son propre verdict, en archive : backlog/_archive/ai-platform/0345 note que « aucun modele de l'AI Gateway ne fait d'avatar parlant », et l'audit du 2026-09-18 pose que la liste des moteurs est fermee (AI Gateway, ElevenLabs, Remotion, Playwright, Postiz, Datafast). Aucun d'eux ne produit un personnage coherent.

Donc : la section Avatar ne peut pas etre cadree comme une fonctionnalite a construire tant que la question « lequel des deux sens » n'est pas repondue. Si c'est le persona, la surface existe deja trois fois et il faut relier le rail aux concepts plutot qu'ouvrir une page. Si c'est le personnage, il faut decider si on le derive de nos moteurs (references persistantes + lineage, ce que le substrat permet en partie) ou si on achete, et la seconde branche engage une ligne de depense qui n'appartient qu'au proprietaire.

→ item ai-platform/2908, de type research : il cadre, il ne tranche pas. Il deviendra un decision le jour ou la branche « acheter un moteur » sera retenue, et pas avant.


Les trois trous les plus couteux

  1. Le Studio marchand n'a aucune documentation publique (3.1). C'est la surface vendue, elle n'est decrite nulle part, et aucune garde ne pouvait le dire.
  2. Deux etats de generation sur six ne sont jamais ecrits (1.2). Une generation interrompue reste SUBMITTED pour toujours, et l'outil MCP en vol promet deja un de ces deux etats.
  3. Le moteur creatif n'appartient a aucun pilier (2.3). Neuf fichiers qui portent le journal, le dispatch et la voix, hors de la carte : c'est le trou qui en bloque d'autres.

Items ouverts

ItemPilierTypeTailleTrou
2900security-identityfixM1.1
2901data-platformfixM1.2
2902design-systemfixM1.3
2903platform-opschoreS2.3
2904app-shellfeatureM2.2
2905growth-webdocL3.1
2906growth-webtestM3.2
2907design-systemchoreS1.5, 2.4
2908ai-platformresearchM4.3

Trois trous reels sont volontairement sans item, chacun pour une raison ecrite ci-dessus : les points 1 a 3 du 2.1 (travail en vol), le point 4 du 2.1 (decision d'architecture), et le 1.4 (bloque par 2903).