ArchitectureBoostEcom Studio — l'OS d'agence dans l'app (plan C retire, archive)

BoostEcom Studio — l'OS d'agence dans l'app (plan C retire, archive)

NOTE POST-NETTOYAGE (8 octobre 2026) : ce texte documente une architecture ou un audit historique, avant les PR #1717–#1724 et #1728. Les anciennes interfaces Growth/Cinema et l'ancien Studio marchand dans boostecom.app…

NOTE POST-NETTOYAGE (8 octobre 2026) : ce texte documente une architecture ou un audit historique, avant les PR #1717–#1724 et #1728. Les anciennes interfaces Growth/Cinema et l'ancien Studio marchand dans boostecom.app ont été retirés. Le contrat CURRENT est docs/architecture/shopify-media-boundary.md ; les capacités média Shopify et Orbit Studio sont distinctes. La validation des builds/tests/E2E reste déléguée à un autre agent.

Comment l'agence creative quitte Notion et vient s'operer dans la plateforme, sans construire un deuxieme produit.

Le pourquoi vit dans docs/decisions/0004-le-studio-agence-est-un-tenant-de-la-plateforme.md. Le comment de la chaine creative vit dans docs/ops/creative-drop-runbook.md. Ce fichier-ci est la carte : ce qui existe, ce qui manque, dans quel ordre.

Plan C retire le 2026-09-26 — lire ce document comme l'histoire. ADR 0043 remplace l'ADR 0004 : BoostEcom n'a ni agence ni client externe. Sont supprimes du code tout ce que ce document decrit autour d'une marque CLIENTE : le pipeline de prospects (Prospect, ProspectEvent), le portefeuille et la gate Go/No-Go (colonnes de production de Store), la cadence et les drops (StudioDrop, cron creative-drop-tick, job open-creative-drop), la livraison publique /drop/[token], le Studio d'organisation /[orgSlug]/~/studio/**, /ops/creative/{pipeline,clients}, les presets sales et client_ops et les permissions agency.prospect.*, agency.client.*, agency.cadence.manage, studio.drop.*, studio.gate.read. Restent vivants : les concepts (CreativeConcept, CreativeLearning, /ops/creative/concepts) comme briefs d'angle Growth, la QC /ops/creative/qc comme revue avant Postiz, le cockpit /ops/creative/cockpit, la taxonomie, la feedback loop, et le Studio marchand (plan B). Les chemins de fichiers supprimes que ce document cite encore ne sont pas des erreurs a corriger : ils datent ce qui a existe. Les tables retirees du schema restent orphelines en base ; leur DROP est une action operateur ulterieure.

Ou vit le code (depuis app-shell/0144). Les loaders, guards, server actions et composants du Studio vivent dans src/features/studio/. Ils ont vecu jusqu'en septembre 2026 dans src/app/(dashboard)/_studio/ : un dossier prive du App Router, donc sans URL, mais importe par features/ai/tools/studio-*.ts et deux route handlers d'API. Un module metier lu depuis quatre points hors du groupe de routes n'est pas une colocation de route, et pnpm fleet:boundaries ne pouvait pas voir ce couplage tant qu'il vivait sous src/app/. Les items archives anterieurs a ce deplacement citent encore les chemins _studio/… : lire features/studio/…. Les regles pures restent a part, dans src/lib/studio/ — voir §« Une seule orthographe par terme » pour pourquoi.


1. Le mapping, ligne a ligne

L'agence = une Organization. Une marque cliente = un Store. Une recrue = un OrganizationMember. Tout le reste en decoule.

NotionCodebase aujourd'huiEtat
Hub — Control Hub/[orgSlug] (vue d'ensemble org)✅ existe
01 — Operations quotidiennes (5 files de role)/[orgSlug]/~/me + 4 sections Studio gatees par permission✅ chaque role a sa porte
02 — SOP & Centre de connaissancesdocs/ops/creative-drop-runbook.md + SOP Notion🟡 partiel, reste en doc
🧩 Bibliotheque systeme (24 composants)registre : studio-system-library.md✅ 14/24 executes, 24/24 sortis de Notion
99 — Administration du systemeprisma/schema.prisma + /admin✅ existe
🎯 Prospects & VentesProspect + /[orgSlug]/~/studio/pipeline✅ existe
🏷️ Clients & MarquesStore + brandKit + /[orgSlug]/~/studio/clients✅ existe
🧠 Avatars & Angles (concepts)CreativeConcept + /[orgSlug]/~/studio/concepts✅ existe
🎬 Production creativeStudioAsset + Task (assignataire, echeance, blocage)✅ existe
📈 Performance & ApprentissagesCreativeLearning + la section Feedback Loop de /~/studio/concepts✅ la boucle revient depuis l'item 0031
Brand System (brandsystem.md)Brand Kit wizard → StoreContext.brandKit✅ existe
Scoring prospect /100features/studio/prospect.ts (grille pure, priorite A/B/C derivee)✅ existe
Score onboarding /100 + Go/No-Goproduction-gate.ts + /~/studio/clients (score recalcule, jamais saisi ; 11 conditions pour les 12 lignes du SOP)✅ existe
Cadence contenu (Quotidien → Sur mesure)Store.creativeCadence (NONE/W/BW/M)🟡 4 valeurs vs 5
Socle one-shot / Content FactoryStore.oneShotFoundation[] / Store.contentFactory[]✅ existe
QA checklist + 9 motifs de rejetStudioAssetRejection + /[orgSlug]/~/studio/qc✅ existe, gate par permission
Livraison clientStudioDrop + /drop/[token] + /[orgSlug]/[storeSlug]/studio✅ existe, et la marque retrouve ses drops sans qu'on lui envoie le lien
Type de livrable (Video / PDP / Icon / Top-PDP)StudioAssetKind (media, pas livrable)❌ manque
Veille concurrentielleIntelligence (getCompetitorAds, AdCreativeAnalysis)✅ existe, plus riche que Notion
Statuts + Bloque / BlocageTask.blockedReason + Prospect/CreativeConcept bloquables✅ existe
KPIs Creative Engine/[orgSlug]/~/studio/cockpit, gate agency.economics.read✅ 11 KPIs sur 11

Lecture du tableau, au 21 aout 2026 (fin de la phase 1.5) : la chaine complete — prospecter → qualifier → onboarder → valider un concept → produire → livrer → apprendre — existe dans l'app, avec son vocabulaire en base, ses regles en fonctions pures testees, et un ecran par etape. Une recrue se connecte, voit sa file, et travaille depuis l'interface.

Ce qui reste dans Notion releve maintenant de la methode (§5), plus de l'etat operationnel. Les chantiers ouverts (phases 2 et 3) ne comblent plus un trou : ils font tourner l'usine toute seule (3.8, 3.9), l'ouvrent au marchand qui l'achete (3.11, livre le 22 aout 2026), rendent la Bibliotheque systeme executable (3.10, 19 composants sur 24 au 24 aout 2026, et les 24 sortis de Notion) et referment la boucle sur la memoire (3.12).


2. Le blocage numero un, leve depuis la phase 0

Depuis septembre 2026, /admin/creative/* n'est plus une vue cross-org. C'est le Studio interne : les cinq memes sections que /[orgSlug]/~/studio/*, lues pour l'organisation dont l'admin connecte est membre (src/features/studio/internal-scope.ts, regle pure dans src/lib/studio/internal-scope.ts). La vue « toutes les organisations » que decrit le paragraphe ci-dessous a existe d'aout a septembre 2026 ; elle melangeait la production interne et celle des clients, et le proprietaire n'avait rien a approuver dans la file d'un client. loadQcBoard() sans orgId reste possible dans le code, mais plus aucune page ne l'appelle ainsi.

/admin/creative/qc etait la seule surface de QC, et elle est derriere requireAdmin(), qui teste User.role === "ADMIN" : un flag plateforme global.

Ce que ce flag ouvre, en plus de la QC : /admin/revenue/billing-ledger, /admin/people/organizations (toutes les orgs clientes), /admin/people/users, /admin/settings/feature-flags, /admin/settings/security, /admin/marketplace/*. Soit l'integralite de la plateforme et les donnees de tous les marchands.

Conclusion, telle qu'elle a ete ecrite : aucune des trois recrues ne pouvait valider une creative sans devenir administrateur de toute la plateforme. Ce n'etait pas un manque de fonctionnalite, c'etait une frontiere de securite, et tant qu'elle n'etait pas deplacee le reste du plan n'avait pas de porte d'entree. Elle l'a ete en phase 0 (§3.1) : la QC de l'agence vit sur /[orgSlug]/~/studio/qc, gatee par agency.qc.review, et /admin/creative/qc ne reste que la vue cross-org de la plateforme. Ce paragraphe est reste au present pendant des mois apres la levee du blocage qu'il decrit.


3. Les chantiers, par ordre de deblocage

Phase 0 — ouvrir la porte (les 3 recrues peuvent travailler) : ✅ complete

3.1 Les permissions Studio : ✅ livre (backlog/_archive/security-identity/0012) PERMISSION_REGISTRY porte les permissions du process en DEUX familles depuis l'ADR 0013 : sept agency.* (agency.prospect.*, agency.client.*, agency.qc.review, agency.cadence.manage, agency.economics.read) qui sont le metier de la plateforme, et les studio.* (studio.concept.*, studio.asset.create, studio.qc.read, studio.drop.read|publish, studio.gate.read, studio.economics.read) qui sont la production, vendue au marchand. Plus quatre presets de role metier : sales, client_ops, strategist, producer : references par id depuis OrganizationMember.permissions.studioRoles. Aucun enum, aucune migration.

Ce « ✅ livre » a ete a moitie faux pendant un an, et c'est la moitie qui comptait. Les quatre presets etaient LUS par le resolveur (permissions.ts, studioPresetPermissions) et ECRITS par aucun code de production : grep studioRoles hors tests ne rendait que le resolveur et ce paragraphe. Pire, leur unique ecrivain possible, setMemberPermissions, reconstruisait {allow, deny} et detruisait le champ au passage — poser un preset a la main en base puis cocher une case dans la matrice admin faisait disparaitre le metier de la personne sans un mot.

Le mecanisme cense debloquer le recrutement n'avait donc pas de porte d'entree, pendant que cette page annoncait que les recrues pouvaient travailler. La destruction est corrigee (setMemberPermissions fusionne desormais), et le fait qu'une permission puisse etre accordee par l'organisation elle-meme plutot que par un admin plateforme reste a livrer : c'est la phase 3 de acces-delegue-et-frontiere-studio.md, et c'est un manque juridique autant qu'ergonomique — l'autorisation ecrite prealable du responsable de traitement ne peut pas etre donnee par un tiers.

Trois invariants sont verrouilles par les tests (src/lib/security/permissions.test.ts, studio-guard.test.ts) :

  • aucune permission studio.* n'est dans une baseline de role non-owner : ni member, ni meme admin. Une organisation qui ne fait pas de Studio ne peut pas donner des pouvoirs creatifs a ses membres par le seul fait de l'appartenance ;
  • agency.cadence.manage et agency.economics.read ne sont dans aucun preset : abonner une marque a une cadence est un engagement commercial, et la marge par marque n'est pas une donnee de production. Owner, ou allow explicite ;
  • deny bat un preset : retirer un acte a une personne ne demande pas de demonter son metier. Les presets se cumulent, et le preset est porte par l'adhesion, pas par la personne : le meme utilisateur peut etre producteur dans une org et simple membre dans une autre.

L'entree de navigation suit (backlog/_archive/design-system/0019) : l'onglet Studio du contexte organisation n'apparait que pour un membre portant au moins une permission studio.*. La reponse est resolue serveur, dans le layout d'org qui connait deja le membre, et voyage sur le ServerOrgSnapshot : le shell est un composant client et ne paie pas un aller-retour par rendu pour decider de dessiner un onglet. Cacher l'entree reste de l'ergonomie : la page se garde elle-meme. La liste de permissions existant a deux endroits (layout et page), un test structurel (src/test/studio-nav-conventions.test.ts) verifie qu'elles restent identiques : une divergence ferait mentir l'onglet dans un sens ou dans l'autre.

Le gate serveur est requireStudioPermission(orgId, permission) (src/lib/security/studio-guard.ts), compose sur getOrgAccess + hasPermission. Il repond notFound() (jamais un redirect, jamais un 403) pour qu'un sondage d'URL ne distingue pas « mauvaise org » de « droit manquant ». getStudioPermissions() resout un jeu de permissions en une seule lecture d'adhesion, pour les pages qui affichent plusieurs sections appartenant a des metiers differents.

3.2 Sortir la QC de l'admin plateforme, ✅ livre (backlog/_archive/app-shell/0013) Le board de production vit a /[orgSlug]/~/studio/qc, scope a l'organisation, et chaque section y est gatee separement : une section que l'acteur ne porte pas est absente, pas grisee. Un producteur voit la file de revue et la file de livraison ; la marge par marque n'est pas sur sa page.

La route /admin/creative/qc survit en vue cross-org pour l'operateur plateforme. Les deux surfaces partagent un seul chargeur (features/studio/board.ts, parametre par orgId) et un seul jeu d'actions (features/studio/actions.ts) : une seconde copie serait l'endroit exact ou le filtre org disparaitrait en silence.

Les actions ne font plus confiance au garde de la page. Chacune re-resout l'organisation depuis le store cible (features/studio/guard.ts) avant de toucher Prisma, et accepte un admin plateforme comme repli nomme, enregistre sur la ligne d'audit (changes.via) avec targetOrgId. Neuf verifications structurelles dans src/test/creative-qc-conventions.test.ts empechent la frontiere de se rouvrir : dont « aucune action n'appelle requireAdmin » et « chaque action ouvre sur un garde requireStudioActor* », verifiees corps par corps pour qu'une nouvelle action ne puisse pas se reposer sur le garde de sa voisine.

3.3 Le travail assignable (✅ livre (backlog/_archive/data-platform/0014) Task porte desormais assigneeUserId (un humain, distinct de assigneeAgent qui nomme le specialiste IA) une tache peut avoir les deux : l'agent redige, la personne repond du resultat), dueAt, et blockedReason.

blockedReason n'est pas un statut : bloque est orthogonal a l'avancement. Une tache peut etre IN_PROGRESS et bloquee, et un statut qui aurait avale l'information aurait perdu ou le travail en est reellement. Une raison vide debloque au lieu d'enregistrer un blocage sans contenu, « bloque, raison : (rien) » est exactement l'etat que le SOP existe pour empecher.

Les lectures de « Ma file » vivent avec les actions (listMyTasks, listUnassignedTasks), pas dans la page : le travail non assigne est une question differente (« qu'est-ce que personne ne tient ? »), posee par qui assigne : les melanger transforme une file personnelle en to-do partagee.

Task.orgId et storeId nullable sont reportes : ils ne servent qu'aux taches org-level, qui n'existeront qu'avec 0016 et 0018, et les rendre possibles aujourd'hui toucherait cinq piliers a vide. Le filtre org passe par la relation (where: { store: { orgId } }), donc sans colonne denormalisee ni derive possible.

3.4 « Ma file », ✅ livre (backlog/_archive/app-shell/0015) /[orgSlug]/~/me, l'equivalent de 01 — Operations quotidiennes : mes taches ouvertes triees par echeance puis priorite, chaque blocage affiche avec sa raison (un badge sans la raison deplacerait la question au lieu d'y repondre), l'attente Studio en compteurs, le tas non assigne, et ce que j'ai cloture sur 7 jours.

Tout est derive : aucune table, aucun statut tenu a la main. Deux gates : le tas non assigne derriere workspace.write (« qu'est-ce que personne ne tient » est la question de qui assigne, pas de tout le monde), l'attente Studio derriere les permissions du board.

Une section dont la source n'existe pas encore ne s'affiche pas : prospects a relancer (0016), concepts a valider (0017), clients incomplets (0018) arriveront avec leur modele. Un panneau vide laisserait croire que la donnee manque alors que c'est l'entite qui n'existe pas.

Phase 1 — encoder ce que seul Notion sait

3.5 Le pipeline commercial, ✅ modele livre (backlog/_archive/data-platform/0016) Modele Prospect (org-scoped) : marque, site, produit, contact, canal, angle personnalise, score /100 avec la grille du SOP (produit demontrable 20, creas ameliorables 20, potentiel UGC 15, angles 15, site 10, preuves 10, contact 10), priorite A/B/C derivee, 8 statuts, prochaine action, prochaine relance, blocked/blockedReason. Conversion Prospect → Store en une operation : setProspectStatus refuse WON, parce qu'un statut gagne atteignable seul produirait une marque sans store et un handoff reduit a une etiquette. L'angle personnalise et le score survivent au passage : c'est le contexte dont l'onboarding a besoin, et une version en deux temps le perd.

La surface est arrivee en phase 1.5 : /[orgSlug]/~/studio/pipeline (backlog/_archive/app-shell/0021).

3.6 Le concept creatif et l'apprentissage, ✅ modeles livres (backlog/_archive/data-platform/0017) CreativeConcept : le concept cesse d'etre une string libre sur StudioAsset et devient une entite validable (avatar, awareness, funnel, angle, douleur/desir/objection, declencheur, hooks, formats, score, statut Recherche → Brouillon → A valider → Valide). StudioAsset.conceptId la reference. CreativeLearning ferme la boucle et devient le writer que StudioAsset.performance n'a jamais eu : le snapshot est derive du learning, jamais passe a cote, donc la colonne et la ligne ne peuvent pas raconter deux histoires. Toutes les metriques sont optionnelles, le SOP interdit d'inventer un chiffre, mais un learning doit finir sur une hypothese ou un prochain test, sinon c'est un post-mortem et la boucle ne tourne pas.

La surface est arrivee en phase 1.5 : /[orgSlug]/~/studio/concepts (backlog/_archive/app-shell/0022), d'ou se saisit aussi le learning.

3.7 Le gate Go/No-Go client (✅ modele + regle livres (backlog/_archive/data-platform/0018) Sur le Store : score onboarding /100 derive d'une grille pure (brand data 15, produit/offre 20, assets 15, >=2 avatars 15, >=3 angles 15, claims verifiables 10, CTA+plateforme+objectif 10), assetsReady, claimsValidated, capacityValidated, marginViable, oneShotFoundation[] vs contentFactory[], et goProduction) le seul horodate et attribue, parce que les cases sont un etat de travail et le GO est un engagement.

Deux regles font la difference entre un gate et une formalite :

  • Les lignes comptables sont comptees, pas declarees. Les avatars et les angles se lisent depuis les CreativeConcept valides : en distinct, donc trois concepts ecrits pour la meme persona valent un avatar. Sous le plancher du SOP la ligne vaut zero, pas la moitie : « un avatar » n'est pas « la moitie de deux avatars », c'est une marque pour laquelle on ne sait pas varier un message.
  • setCreativeCadence refuse une cadence sans GO, en nommant chaque condition manquante. Couper une cadence reste toujours possible : le gate qui dit qu'une marque doit s'arreter ne doit jamais etre ce qui l'y enferme. Retro-compatible par construction — toutes les colonnes sont optionnelles ou defaultees, le cron creative-drop-tick ne lit toujours que creativeCadence, donc une marque deja livree continue de l'etre.

La surface est arrivee en phase 1.5 : /[orgSlug]/~/studio/clients (backlog/_archive/app-shell/0023).

Phase 1.5 — les trois ecrans que les modeles n'avaient pas : ✅ complete

Les items 0016-0018 ont livre des modeles sans page, deliberement. Les items 0021-0023 sont ces pages, plus le cadre qui les relie.

Le Studio devient quatre sections sous /[orgSlug]/~/studio, chacune gatee par sa permission et absente, jamais desactivee, pour qui ne la porte pas. L'onglet d'en-tete pointe desormais sur /~/studio, une porte d'entree qui redirige chaque membre vers la premiere etape qu'il detient : il pointait droit sur /qc, ce qui devenait un cul-de-sac des qu'un sales cliquait dessus.

SectionRouteOuvre avec
Pipeline/~/studio/pipelineagency.prospect.read
Clients/~/studio/clientsagency.client.read
Concepts/~/studio/conceptsstudio.concept.read
Creative QC/~/studio/qcagency.qc.review · drop.publish · cadence.manage · economics.read

Trois regles tenues par des tests structurels (src/test/studio-surfaces-conventions.test.ts) :

  • Aucune page ne charge avant d'autoriser. Le test compare les appels, pas les imports : sa premiere version mesurait l'ordre des lignes import et passait quoi que fasse la page.
  • Les scores restent derives. Ni onboardingScore ni la priorite prospect n'ont de champ de saisie ; les formulaires importent les grilles pures plutot que de recopier une formule qui divergerait.
  • Aucun controle n'offre un choix que l'action refuse. WON est absent du selecteur de statut : setProspectStatus le rejette, la conversion passe par convertProspect qui cree le store dans la meme operation.

Les metriques d'un learning restent optionnelles dans le formulaire : le SOP interdit d'inventer un chiffre, et un champ requis en produit des inventes, indiscernables d'une mesure une fois stockes. Ce qui est exige, c'est une hypothese ou un prochain test, sinon la boucle ne tourne pas.

Au passage, conceptReadiness a quitte concept-actions.ts pour un module pur : c'etait la seule des trois grilles du SOP a ne pas en etre un, et un export synchrone depuis un fichier "use server" n'est pas ce que cette directive veut dire. Elle vit aujourd'hui dans lib/studio/concept.ts, a cote de la taxonomie et pour la meme raison : deux mondes la lisent, le dashboard, qui demande ce qu'un humain doit encore fournir, et le runtime IA, qui ecrit un concept et rapporte ce qui manque.

Phase 2 — l'usine tourne seule: 🟡 3.8, 3.9 et 3.11 livres, 3.10 a 19/24

3.8 La cadence cree du travail, pas seulement un DRAFT : ✅ livre (backlog/_archive/platform-ops/0024) Le tick ouvre le drop et cree trois Task, une par etape du cycle (strategie → concepts, production → livrables, QA → publication), portant la meme echeance. StudioDrop.dueAt stocke enfin cette echeance, que le tick connaissait et jetait : d'ou l'impossibilite de calculer « livre a l'heure ».

L'assignation n'a lieu que s'il y a un proprietaire evident : un seul porteur du preset → la tache lui revient ; zero ou plusieurs → le tas non assigne, que /~/me affiche deja. Pas de round-robin invente. Le proprietaire de l'org ne compte pas comme porteur unique : il a toutes les permissions, donc le compter assignerait tout le cycle au fondateur. Task.createdBy vaut system:creative-drop-tick, jamais un id emprunte : attribuer a un humain un travail cree par un cron fait mentir l'audit.

3.9 Le cockpit agence, ✅ livre (backlog/_archive/app-shell/0025) /[orgSlug]/~/studio/cockpit, garde par agency.economics.read : les 11 KPIs du SOP, tous derives au moment de la lecture, aucune table de snapshot, aucun compteur incremente a l'ecriture. Un KPI stocke derive du travail qu'il pretend mesurer : meme argument que le score d'onboarding, applique a l'agence au lieu d'un client.

Deux regles portent la valeur du tableau :

  • null n'est pas 0. Un KPI sans donnee affiche « no data ». Zero est une mesure : l'imprimer sur une periode vide affirme que l'equipe n'a rien produit alors que personne ne lui avait rien demande.
  • Le denominateur d'un taux exclut ce qui n'est pas decide. Un lot en attente de revue n'a pas echoue, et le compter comme tel fait passer chaque bonne semaine pour un effondrement.

Chaque KPI se compare a la periode precedente, et la fleche lit higherIsBetter plutot que le signe : un cout qui baisse est bon, un throughput qui baisse ne l'est pas.

3.10 La Bibliotheque systeme devient executable, 🟡 19 composants sur 24 (backlog/ai-platform/0028, backlog/app-shell/0029, backlog/app-shell/0030, backlog/app-shell/0031, backlog/app-shell/0032, backlog/app-shell/0033) Un composant ne doit jamais exister aux deux endroits : deux copies d'une regle divergent, et plus personne ne sait laquelle fait foi. Chaque composant cable sort donc de Notion.

Lecture composant par composant de la data source 794b3bd2 au 23 aout 2026 :

Le detail composant par composant vit desormais dans studio-system-library.md — une ligne par entree Notion, avec le chemin exact dans le code. Ce paragraphe raconte, ce fichier-la fait foi, et src/test/studio-library-register.test.ts verifie que le chiffre ci-dessus en est derive.

Le tableau agrege qui vivait ici a ete supprime pour cette raison. Il comptait 2 + 12 + 5 + 2 + 5 = 26 destinations pour 24 composants, et rangeait Offer Architecture dans « reste a cabler » alors que Store.oneShotFoundation[] / contentFactory[] et la condition offerScope du Go/No-Go le cablent depuis l'item 0029. Deux erreurs dans un tableau tenu a la main, trouvees le jour ou on l'a derive.

DestinationNb
Regles pures (code)7
Instructions de skill (prompt)12
Orchestration, documentee et radiee de Notion5

Le decoupage suit le livrable, pas la ligne Notion. skills/router.ts selectionne un seul skill par message : douze petits skills se seraient disputes hook, angle, avatar, et le classifieur aurait tranche au hasard, en payant un aller-retour Haiku a chaque fois. D'ou deux destinations seulement :

  • creative-ads absorbe la chaine strategique du SOP a l'endroit ou elle travaille, pas en annexe : la veille concurrentielle dans son etape OBSERVE, les Avatar Cards / Matrice Avatar × Angle / Hooks dans IDEATE, le script video 10–15 s dans CREATE, la QA video dans QC.
  • pdp-conversion est nouveau : le module PDP (architecte de galerie, brief slide par slide, systeme d'icones, QA en 17 points) decrivait un livrable vendu et n'avait aucune surface d'execution.

Ce qui est migre l'est litteralement : 12 champs d'Avatar Card, 14 familles d'angles fermees, 10 hooks en 7 types, 7 sorties de script, 15 points de QA video, 6 a 10 slides, 17 points de QA PDP. src/test/studio-library-migration.test.ts les compte — une prose peut s'eroder en douceur (« quelques hooks » la ou le SOP dit dix) sans que personne le voie, un test qui compte, non.

Quatre trous nommes en migrant, hors scope de 0028 :

  1. Le gate Go/No-Go est plus faible que le SOP. Corrige par l'item 0029. productionReadiness verifie desormais 11 conditions pour les 12 lignes de la checklist. Les quatre ajoutees — Brand System exploitable, cadence tranchee, responsables nommes, DoD convenue : sont actionnables depuis /~/studio/clients et lisibles cote marchand.

    Deux decisions de modelisation portent ce correctif :

    • cadenceDecided est un drapeau sur la DECISION, pas sur la valeur. creativeCadence a un defaut non-nul (NONE), donc « pas de cadence, deliberement » et « personne n'a tranche » s'ecrivent pareil. Exiger != NONE bloquerait un client socle-seul legitime. oneShotFoundation / contentFactory n'ont pas ce probleme : un tableau vide n'est pas une decision, donc « au moins un des deux non vide » est deja la bonne regle.
    • Les points 6 et 7 du SOP restent fusionnes en un offerScope. Logique — Offer Architecture vend explicitement les deux formes separement ; exiger les deux refuserait une offre que l'agence vend. D'ou 11 conditions pour 12 lignes, et non un oubli.
  2. brandsystem.md est deja reclame par le Brand Kit wizard (create-shopify-brand). Il lui manque les champs de production creative du SOP : claims autorises, claims interdits, objections, declencheurs d'achat, winners existants.

  3. Deux collisions de declencheurs preexistantes, onboarding (ship-shopify-store vs store-setup) et tracking (seo-audit vs tracking-audit), epinglees par le test : la liste peut retrecir, jamais grandir.

  4. La taxonomie North Star a neuf termes, l'asset en portait sept. Corrige par l'item 0030 : voir ci-dessous.

La Creative Taxonomy : item 0030

Product × Marketing Avatar × Angle × Awareness × Funnel × Placement × Format × Hook × Result

Neuf termes. StudioAsset en portait sept : funnel vivait sur CreativeConcept et pas sur l'asset (l'etage de tunnel d'un CONCEPT etait connu, celui de la creative qui en sortait ne l'etait pas) et placement n'existait nulle part, Store.platforms melangeant les deux notions (meta a cote de reels). Les deux colonnes ont rejoint la table.

Trois decisions portent l'item, et elles se defendent chacune seule :

  • Ca ne bloque pas. Le Seuil / Gate du composant Notion est vide : la taxonomie demande a etre coherente et tracable, pas a etre bloquante. Refuser un saveStudioAsset inventerait une regle que le SOP ne pose pas, et perdrait un asset deja ecrit dans Blob. Meme forme que unmetered (item 0010) : mesurer et exposer. L'outil renvoie traceable et taxonomyMissing, le tableau QC compte, un humain tranche.
  • Result n'est pas un manque. Une creative generee il y a une heure n'a pas encore de performance ; la compter comme incomplete marquerait toute la file comme non tracable le jour ou le chiffre compte le plus. Huit termes sont exigibles a l'ecriture, le neuvieme arrive avec les resultats.
  • Une seule orthographe par terme. CreativeConcept.awareness stockait PROBLEM (enum Prisma) quand la creative qu'il produisait stockait problem (chaine libre) : rien ne les joignait, ce qui est precisement pourquoi il fallait le corriger avant que quelque chose essaie. L'outil accepte encore les deux (durcir l'enum ferait rejeter l'appel entier par zod, donc perdre l'asset) et normalise a l'ecriture.

Le module vit dans src/lib/studio/taxonomy.ts, pas dans features/studio/ avec les autres regles pures : deux mondes le lisent, le dashboard qui compte et le runtime IA qui ecrit. La raison donnee ici etait « rien hors de (dashboard) n'importe _studio » — c'etait deja faux quand la phrase a ete ecrite (features/ai/tools/studio-tools.ts importait ces loaders), et app-shell/0144 a retire le route group de la question. Le placement tient desormais sur une regle verifiee : pnpm fleet:boundaries fait de src/lib le plancher qu'aucune feature ne remonte, donc une regle pure lue par features/studio ET features/ai vit la, sinon la lecture de l'une devient une arete inter-features. Le filtre de comptage est derive de la liste des termes, jamais ecrit a la main : un audit qui cesse silencieusement d'auditer une colonne affiche zero et se lit comme propre.

La QC avant l'upload, et la matrice de variantes : item 0375

La taxonomie ci-dessus mesure et expose, et c'est la bonne regle. Elle laissait pourtant deux depenses sans contrepartie.

La liste des claims existait et rien ne la lisait. brand-system.ts normalise allowedClaims / forbiddenClaims depuis StoreContext.modules.brandKit depuis l'item 0034 ; le prompt creative-ads ordonne « aucun claim invente » ; la seule chose qui pouvait comparer les deux etait un modele a qui on ne donnait pas la liste. lib/studio/qa-gate.ts la lui donne, et tourne avant storage.upload : c'est le seul endroit ou un refus est encore possible, puisque une fois les octets dans Blob refuser perd un asset qui existe.

La regle du gate est plus etroite que celle de la taxonomie, deliberement : seul un claim interdit bloque. Tout le reste sort en qaWarnings et l'asset est sauve, exactement comme taxonomyMissing. Un claim non enregistre n'est pas un claim interdit — sinon une liste vide devient un arret de production, et la reponse humaine fiable a un arret de production est de cocher la case, pas de remplir la liste.

Une variante n'etait pas distinguable d'un re-rendu. « Fais-moi quelques variantes » est la demande la plus frequente apres une creative qui plait, et sans cadre elle produit quatre visuels ou le hook, l'angle et le plan d'ouverture ont bouge ensemble : l'un gagne, personne ne sait quoi, et le lot suivant repart de zero. lib/studio/variants.ts + planCreativeVariants posent la contrainte a un endroit ou elle est verifiable plutot que demandee : une seule variable bouge, et le test l'assert directement au lieu de faire confiance a l'appelant, qui est un modele de langage.

Les axes offerts sont les termes de la taxonomie ci-dessus, et c'est la meme raison qui fonde les deux : ce sont les seules dimensions sur lesquelles le registre sait regrouper un resultat. Faire varier ce que la table ne stocke pas produit un rendu, jamais une lecture. Le produit n'est pas un axe : deux produits sont deux tests, et c'est comme ca qu'un « hook gagnant » se revele etre un produit gagnant.

Le lien vit sur StudioAsset.variantOfId + variantAxis (auto-relation nullable, SetNull : perdre la base ne doit pas supprimer les variantes qui lui ont survecu). Sans cette paire la matrice redevient un lot : la planification peut etre parfaite, si le lien n'atteint pas la ligne rien n'a ete appris.

La Feedback Loop : item 0031

WINNER    = Avatar + Angle + Hook + Format + Offer + Product
LOSER     = meme taxonomie + hypothese sur la faiblesse
NEXT TEST = une variable principale a modifier

Cinq defauts, et le premier explique les autres.

Un WINNER n'avait pas d'identite, parce qu'on le notait au mauvais niveau. LearningForm ecrivait toujours conceptId, jamais assetId : alors que recordLearning acceptait les deux depuis le premier jour. Or un CreativeConcept porte hookIdeas[] et recommendedFormats[] : des pluriels, des candidats. Le hook et le format reellement diffuses ne vivent que sur le StudioAsset. L'app savait donc dire « ce concept a gagne » et jamais quelle creative avait gagne, et « decliner le winner », le rituel hebdomadaire du SOP, n'avait rien a decliner. Meme forme que le defaut corrige par l'item 0022 : un writer qui existe, aucun ecran pour l'atteindre.

Rien ne relisait la boucle. nextTest, hypothesis, whatWorked etaient ecrits, comptes par le KPI « learnings actionnables » du cockpit, et jamais affiches. Le KPI prouvait que la phrase EXISTAIT et ne disait rien de ce qu'elle disait. La section /~/studio/concepts les affiche enfin, en trois listes : winners a decliner, losers a remplacer, next tests ouverts. Elle vit la et pas derriere une septieme route Studio parce que decliner un winner, c'est ecrire le concept suivant : meme ecran, meme permission, meme personne.

whatFailed n'avait pas de champ : la colonne et l'action l'avaient, le formulaire non, donc un LOSER ne pouvait pas porter sa faiblesse.

Trois des dix metriques n'existaient pas : cpmUsd, taux d'ajout au panier, taux de conversion. La liste des metriques est desormais derivee d'un seul endroit (LEARNING_METRICS) : elle avait justement derive parce qu'elle etait ecrite a la main a trois endroits.

« Ne jamais conclure a partir d'une metrique indisponible », la seule regle dure du composant. Elle n'interdit pas d'enregistrer : le qualitatif est sur sa propre liste de donnees valides. Elle interdit qu'un avis se lise comme une mesure. D'ou measured / asserted, compte a part et affiche, jamais bloquant : le Seuil / Gate est vide, comme pour la taxonomie.

Deux decisions de modelisation portent l'item :

  • offer vit sur le learning, pas sur l'asset. La meme video 9:16 tourne plein tarif en semaine 1 et a -20 % en semaine 2 et produit deux resultats differents : l'offre est une propriete du test, pas du fichier. Elle ne rouvre donc pas les neuf termes que l'item 0030 a fixes. (Le SOP porte deux taxonomies divergentes : 9 termes sans Offer cote Creative Taxonomy, 10 avec Offer et Proof cote Creative Equation. Cet item ne tranche pas, il place chaque terme la ou il est vrai.)
  • nextTestDoneAt est un drapeau sur la DECISION. Rien ne relie une creative au learning qui l'a provoquee ; deduire « fait » de l'apparition d'un nouvel asset fermerait des tests que personne n'a menes, et la fermeture serait ensuite indistinguable d'une vraie. Un humain coche. Sans ce champ la liste des tests ouverts ne ferait que grandir : l'exact defaut d'affordance decrit plus bas.

3.11 Surface Studio cote marchand : ✅ livre (backlog/_archive/app-shell/0027) /[orgSlug]/[storeSlug]/studio, onglet studio de la barre store. Le marchand voit sa marque : le verdict de gate et ce qui manque, les drops publies avec leur lien de partage et leur retard eventuel, ce qui est en preparation, ses concepts avec leur taux d'approbation, et son economie sur 30 jours.

Trois decisions portent cette surface.

  • Une boutique, les memes chargeurs. loadQcBoard, loadConceptBoard et loadClientGates ont appris un scope storeId (dans la clause where, jamais en filtre memoire) au lieu de recevoir des copies. Une seconde implementation de la meme arithmetique est exactement ce que board.ts dit exister pour empecher, et c'est la que le filtre org disparait sans que personne le voie. Seul loadStoreDeliveries est nouveau : personne ne chargeait les drops publies, donc le livrable de toute l'usine etait le seul artefact sans page dans l'app.
  • Des resultats, pas des controles. Toutes les sections sont en lecture. Approuver une creative, valider un concept et lever la gate restent sur la surface agence, ou vit le contexte de la decision, et pour un marchand solo cette surface est sa propre org, donc rien n'est hors de portee. Afficher une file de verdicts sans les boutons de verdict serait le defaut de /~/me que l'audit d'aout 2026 vient de corriger.
  • Traduite. La console de l'agence peut vivre en anglais ; une surface vendue dans six locales, non. Le loader ne fait aucune fusion de fallback : une cle absente en fr jette pour les seuls utilisateurs francais.

Le meme mot dans deux contextes, deux perimetres : l'onglet org ouvre le tableau de l'agence sur toutes les marques, l'onglet store ouvre cette marque-la. studio.* reste une permission d'organisation dans les deux cas, donc serverOrg.studioAccess repond pour les deux, et src/test/store-studio-conventions.test.ts echoue si les deux destinations se confondent.

Phase 3 — l'amelioration continue

3.12 La boucle d'apprentissage alimente la memoire. Un CreativeLearning marque Gagnant doit ecrire un MemoryEvent / StoreFact pour que le prochain brief parte de ce qui a deja gagne. C'est la seule forme d'« auto-entrainement » qui soit honnete : pas de fine-tuning, une memoire structuree que le skill relit. Le socle existe (docs/architecture/memory-layer.md).

3.13 L'audit permanent. Le repo a deja codebase-auditor, pnpm docs:claims, le schema guard et pnpm fleet:scope. Etendre check-doc-claims.mjs aux affirmations du Studio (nombre de permissions studio.*, presets de role, motifs de rejet) pour que la doc ne puisse plus deriver silencieusement : comme elle l'a fait sur le modele image.


4. Les actions operateur, hors code

Trois choses bloquent sans qu'aucune ligne ne soit a ecrire.

ActionEffetOu
Poser HIGGSFIELDS_MCP_URL + HIGGSFIELDS_MCP_TOKENPlus une action bloquante depuis ai-platform/0345. La ligne disait « debloque video / voix / lipsync / vecteur » et que sans elles « l'offre 8 videos/mois du SOP n'est pas productible » : la video part desormais en direct sur Veo 3.1 (AI Gateway), la voix-off est generee DANS le clip, le vecteur est ecrit par le modele, et le lipsync n'a jamais eu de route — il n'en a toujours pas, avec ou sans ces cles. Le serveur Higgsfields reste un add-on optionnel.—
Creer l'org « BoostEcom Studio » et y inviter les 3 recruesLe tenant de l'agence existe. Les invitations + emails existent deja./[orgSlug]/~/members
Entrer les 3 marques du SOP comme StoreNacre Bijoux, Valise Demo, Boutique Demo deviennent des clients operables./[orgSlug]

5. Ce qui ne migre pas

Tout ne doit pas quitter Notion, et le dire evite un chantier inutile.

  • Le SOP complet reste la reference methodologique. Ce qui migre est l'etat operationnel (qui fait quoi, ou en est-on, ce qui bloque), pas la doctrine. Un playbook de role est un document, pas une table.
  • Le glossaire, les modeles de fiches, la certification pre-production sont de la documentation d'equipe. Ils ont leur place dans Notion ou dans docs/, pas dans Postgres.
  • La Bibliotheque systeme migre composant par composant, et seulement quand un composant devient executable (§3.10). Un prompt jamais execute par du code n'a rien a faire dans le repo.

La regle : ce qui a un statut migre, ce qui a une methode reste.


6. Par ou on pilote

Le §1 dit quoi correspond a quoi. Celui-ci dit par ou on agit dessus : la question que ce document ne repondait pas, et celle qu'on pose en premier quand on arrive.

SurfaceEtatCe qui existe
Panel agence / admin✅7 routes : ~/studio/{pipeline,clients,concepts,qc,cockpit}, la porte d'entree ~/studio, et [storeSlug]/studio cote marchand
Server actions✅4 fichiers d'ecritures dans features/studio/ — actions, concept-actions, gate-actions, prospect-actions
API✅GET /api/organizations/[orgId]/studio/[section], une seule table de correspondance dans lib/security/studio-sections.ts
Chat / Atlas✅Lecture dans studio-tools.ts, ecriture dans studio-write-tools.ts — meme porte, meme via, meme ligne d'audit que les server actions
MCP✅boostecom:studio.read → getStudioSection. Le Studio est le seul systeme natif du catalogue

Autrement dit : le Studio est pilotable par les quatre portes. Un @Atlas ne se contente plus de dire ou en est la production : il peut la faire avancer, a travers les memes gardes que la souris.

Ce tableau a annonce ❌ sur ses trois dernieres lignes bien apres leur livraison (app-shell/0082, ai-platform/0081, integrations/0083). Un tableau d'etat qu'on ne rejoue pas devient une liste de travaux deja faits. C'est exactement ce que le §3.13 de ce document existe pour empecher, et c'est l'argument le plus fort qu'il en reste a faire. L'etat courant se derive : node scripts/four-doors.mjs.

Le seul MCP dans le tableau va dans l'autre sens

HIGGSFIELDS_MCP_URL est un MCP que le Studio consommerait s'il etait configure. Ce n'est pas le Studio expose : la confusion est facile et elle inverse le sens de la fleche. Depuis ai-platform/0345 il ne produit plus rien d'indispensable — c'est un add-on, la production passe par l'AI Gateway.

Les trois portes d'autorisation

Un lecteur qui vient brancher une nouvelle surface doit savoir contre quoi il autorise. Il y en a trois, et une seule accepte un admin plateforme :

PorteRepli admin plateforme
lib/security/studio-guard.ts — lectures de pagenon
features/studio/guard.ts — creatives d'un storeoui, enregistre en via
features/studio/prospect-actions.ts — mutations prospectsnon

Le repli est l'exception, pour une raison ecrite dans guard.ts : un operateur a un motif legitime de debloquer une file de production. Le detail et la decision vivent dans backlog/_archive/security-identity/0051, et src/test/studio-authorization-doors.test.ts echoue si l'ensemble change.

Toute nouvelle surface doit passer par ces portes, pas en ouvrir une quatrieme. C'est la condition pour que « pilotable depuis le chat » ne veuille pas dire « pilotable sans droits ».

Les outils IA de lecture le font, et c'est la seule chose difficile de ce chantier. Les deux couches n'autorisent pas dans le meme langage : un outil @Atlas branche sur canRead(ctx.userRole) connait quatre roles, la ou le Studio en accorde onze avec un override par membre — le Studio serait tombe dans les mains de tout member. studio-tools.ts appelle donc studioAccessFor(), la moitie sans session de getStudioAccess(), avec l'appelant que AtlasToolContext porte deja. Le chat est un nouvel APPELANT de la porte, pas une nouvelle porte : c'est cette distinction que studio-authorization-doors.test.ts protege, et il n'a pas eu a changer.

L'ecart avec la cible

La cible : tout doit etre pilotable depuis le MCP, l'API, le Chat cote utilisateur et le panel cote agence, et rien ne doit rester dans un coin sans etre relie.

Le Studio en couvre quatre sur quatre. Les quatre items decoupes depuis inbox/0073 sont livres et archives : ai-platform/0080 (lecture chat), ai-platform/0081 (ecriture chat), app-shell/0082 (API) et integrations/0083 (scopes MCP).

Une nuance qui n'est pas une case a cocher : l'API et le MCP sont en lecture. L'ecriture passe par le panel et par le chat, qui partagent les memes portes ; l'ouvrir a un programme tiers est une decision produit, pas un reste de chantier.

L'ecart restant du Studio n'est plus une porte, ce sont les manques de §1 et §3 : le type de livrable non modelise, la 5e valeur de cadence, la bibliotheque a 19 composants sur 24, la boucle §3.12 et le garde de §3.13.


7. Dependances entre chantiers

3.1 permissions ──┬─→ 3.2 QC hors admin ──┐
                  │                        ├─→ 3.8 cadence cree du travail
3.3 Task humain ──┴─→ 3.4 Ma file  ────────┘
                                            └─→ 3.9 cockpit agence
3.5 Prospect ─────→ 3.7 Go/No-Go ──→ 3.6 Concept ──→ 3.12 memoire
                                          └─────────→ 3.11 surface marchand

3.1 et 3.3 sont independants et parallelisables. Rien d'autre ne demarre avant eux.