ADRADR-0023 · Une capacite, un contexte, N grammaires

ADR-0023 — Une capacite, un contexte, N grammaires : toute surface de BoostEcom rend le meme moteur

La vision de la plateforme tient en une phrase, ecrite : BoostEcom doit devenir la couche d'intelligence qui connait simultanement le store de l'interieur, le store de l'exterieur, le marche autour de lui, et la…

Statut

Accepté · 2026-09-18

Piliers : ai-platform, intelligence, app-shell, integrations, commerce-systems, growth-web

Contexte

La vision de la plateforme tient en une phrase, ecrite :

BoostEcom doit devenir la couche d'intelligence qui connait simultanement le store de l'interieur, le store de l'exterieur, le marche autour de lui, et la connaissance mondiale necessaire pour comprendre et agir sur ces donnees.

Et sa consequence produit (2026-09-18) : « tout en un » veut dire que la preview est le meme systeme que le chat, le workflow, le MCP et l'API, et c'est ce qui ne tenait pas encore de bout en bout dans l'ecosysteme.

Le depot porte deja les quatre quadrants, sous forme d'outils :

QuadrantCe qui l'implemente, aujourd'hui
le store de l'interieurfeatures/ai/tools/commerce-tools.ts (8), shopify-admin.ts, theme-tools.ts (4), aov-tools.ts (5), store-tools.ts (5)
le store de l'exterieurtracking-scan.ts, store-audit-tool.ts, vitals-tools.ts (5, PSI / CrUX), aeo-tools.ts (3), cro-tools.ts (2)
le marche autourintelligence-tools.ts (14), radar-tools.ts (2), marketplace-tools.ts (2)
la connaissance mondialeknowledge-tools.ts (5), skills-tools.ts (2), le modele lui-meme

87 outils declares name: tool({...}) dans src/features/ai/tools/, chacun avec un inputSchema zod et un execute. Chacun fait une chose precise. Ils sont atteignables depuis une surface : le chat.

Le reste de la plateforme ne les voit pas, et chaque surface a re-derive la sienne. Mesure le 2026-09-18, dans le meme depot, le meme jour :

  • le canvas du Studio (workflow/v2/components/add-node-dialog.tsx) listait 57 entrees ecrites a la main plus 18 derivees de commerce-os-registry, resolues en 5 types executables. Un clic sur « SEO Audit » et un clic sur « Forecast » produisaient le meme noeud aiText, prompt: "". L'executeur ne lit ni label, ni description, ni capabilityKey — capabilityKey n'est lu nulle part cote serveur. Soixante-quinze promesses, zero comportement (ai-platform/2764) ;
  • le Hub OS (la home publique) lisait ses propres routes /api/intelligence/hub/{stores,ads,counts,scan,tracker} ; aucune n'appelait readStoreIntelligence, le lecteur canonique que intelligence/2760 venait de faire adopter par sept appelants ;
  • l'inspecteur de la preview (preview/store-browser/components/store-inspector/) avait huit onglets — algorithm, index, intelligence, market, memory, metrics, profile, rules — dont un seul lisait le descripteur partage storeIntelSections ;
  • la regle de visibilite d'un record avait ete ecrite sept fois (six dans le serveur MCP Intelligence, une quatrieme version dans les outils du chat) avant que 2760 ne la ramene a quatre portes nommees ;
  • CLAUDE.md decrivait le serveur MCP du store comme « 18 outils enregistres via register-tools.ts + shopify-bridge ». Le nombre est juste, et il est derive : la claim mcp.tools de scripts/check-doc-claims.mjs compte les server.registerTool( et les confronte a MCP_SCOPES (18, dont 2 platformAdminOnly). La premiere mesure de cette session avait dit 14 en ne comptant que l'assistant maybe( — et c'est la claim derivee qui l'a corrigee, ce qui est la lecon meme de ce document. Ce qui etait faux : « via shopify-bridge » — le bridge n'enregistre rien, il est importe en type seul, c'est le client que les outils appellent — et le docblock du fichier, « all 15 », perime et garde par rien. La phrase fusionnait la porte entrante (le MCP) avec la credential sortante (la Custom App).

Aucun de ces cinq defauts n'est une negligence. Ils ont la meme cause : rien, nulle part, ne disait ce qu'une surface a le droit de contenir. Chaque surface a donc contenu les trois choses a la fois — ce qu'on peut faire, sur quoi, et comment on le presente — et trois copies de « ce qu'on peut faire » ne restent pas d'accord six semaines.

Deux faits mesures fixent les bornes de la solution :

  1. Un moteur unique fonctionne. readStoreIntelligence sert sept appelants (deux serveurs MCP, une route REST, les outils du chat, le catalogue de scopes, l'agregateur discovery, le cockpit) et storeIntelSections en sert quatre. Trois surfaces tres differentes affichent le meme chiffre parce qu'elles lisent la meme liste.
  2. Une interface unique ne fonctionne pas. La PR #1300 l'a mesure avant d'y renoncer : le Hub est une ile CSS auto-contenue, chaque selecteur sous .boostecom-hub, dans un espace de tokens clair bati pour le shell marketing ; le cockpit est dark-only et lit les tokens du design system. Monter l'un dans l'autre importe un second espace de tokens dans la surface que l'operateur regarde le plus.

Décision

Toute surface de BoostEcom est un triplet (capacite, contexte, grammaire). Les deux premiers sont uniques et partages ; seule la troisieme appartient a la surface.

Ce que c'estRegle
capacitece qui peut etre fait — un outil du registre src/features/ai/tools/une seule liste, importee, jamais recopiee
contextesur quoi et pour qui — storeId / orgId / userId, plus la porte d'audience (public, agent externe, membre, admin)un seul resolveur par fait ; une porte est une fonction nommee, jamais une condition inline
grammairecomment l'intention est exprimee et le resultat ludifferente par surface, par design

Les grammaires en vigueur :

SurfaceSa grammaireCe que le moteur ajoute a la version generique
chatla conversationun ChatGPT ne connait pas la boutique ; ici chaque tour lit les 87 outils sur le contexte du store
canvas (Studio)le grapheun editeur de noeuds generique n'a que des primitives ; ici chaque noeud est une capacite du registre
previewle store vu a travers les quatre quadrantsun onglet Chrome montre des pixels ; ici le storefront est rendu avec ce que la plateforme en sait — interieur, exterieur, marche, monde — et ce qu'elle peut y faire
cockpitle tableau de bordun dashboard montre des metriques ; ici il montre le record canonique, non masque, pour le membre
Hub OSle parcours et le classement, publicun annuaire liste ; ici il projette le meme record, par la porte publique
Bridge AI (le serveur MCP)le protocole, pour un agent tiersune API generique ; ici les memes capacites, plafonnees par la Custom App et le consentement. « Bridge AI » est le nom produit, « MCP » le nom technique : les deux designent une surface, jamais deux
API RESTHTTP, pour un developpeuridem, en HTTP
extension Chromel'overlay in-situ, sur le store d'un autreun onglet ; ici le meme descripteur, par la porte publique

Trois corollaires, chacun testable :

  1. Une surface est finie quand il ne reste dans son code que de la grammaire. Tout ce qui n'en est pas — une liste de capacites, une regle de visibilite, une unite de mesure, une decision de « ce que veut dire une absence » — appartient au moteur et s'importe.
  2. Une capacite porte le meme nom partout. getRevenueTrend dans le chat est getRevenueTrend dans le canvas, dans le MCP et dans l'API. Un libelle traduit est de la grammaire ; un identifiant est du contexte. Si trois surfaces nomment differemment la meme chose, il y a trois produits.
  3. Une grammaire n'est jamais le generique. Une surface gagne son existence par ce que le moteur ajoute a sa version banale. Une preview qui n'ajoute rien a Chrome n'a pas de raison d'exister ; un chat qui n'ajoute rien a ChatGPT non plus. C'est le test de chaque nouvelle surface, avant meme le triplet.
  4. Deux surfaces peuvent montrer un sous-ensemble different, ou un rendu different — jamais une valeur differente. Le paywall est une porte ; le composant est de la grammaire ; la valeur est le moteur. Meme store, meme champ, meme instant : le Hub OS, le cockpit, Bridge AI, l'extension et la preview affichent le meme chiffre ou n'en affichent pas. Un ecart entre deux surfaces n'est jamais un defaut d'affichage : c'est une seconde lecture, et elle s'efface.
  5. Le moteur ne s'expose jamais ; seule une grammaire s'expose. Une surface publie le nom d'une capacite, son contrat (entrees, sorties, etat) et son resultat — jamais son implementation : prompts, algorithmes, sondes, ponderations, sources, code. Le catalogue d'une surface est servi par store et par la porte de son audience, jamais comme une liste globale ; un enregistrement retire et un domaine inconnu repondent pareil. C'est la ligne que le depot tient deja : /intelligence/transparency publie la methode, pas le moteur ; docs-separation refuse dans la doc publique tout lien vers le depot prive ; un scope platformAdminOnly disparait de l'ecran de consentement de qui n'est pas admin. Donner acces a l'ecosysteme, c'est ouvrir des grammaires coherentes sur un moteur qui reste ferme — pas distribuer le moteur.

Le contexte a trois niveaux, et chacun a un proprietaire

Le modele (2026-09-18) : d'abord l'intelligence des providers et de la gateway (les modeles) ; en dessous, celle de l'ecosysteme (process, strategie, skills, prompt systeme, regles, garde-fous, connecteurs) ; en dessous encore, l'utilisateur ajoute la sienne par tous les moyens qu'on lui donne. Le systeme monte en puissance a mesure qu'on le nourrit.

C'est deja la mecanique du chat — composeAtlasPrompt empile le kernel (prompts/kernel.ts, ADR 0022), les regles du store (storeRules de loadStoreContext), la memoire (memory-tools), les skills (skills-tools, applySkillFilter) et les connecteurs a chaque tour. Ce que la formulation ajoute est la propriete de chaque niveau, et elle range le cinquieme corollaire :

NiveauQuoiA quiOu il est lu
1les modeles — providers, gatewayloue : interchangeable par config/ai-models.tsresolveModel, resolveWorkflowModel
2l'ecosysteme — kernel, process, skills de plateforme, regles par defaut, garde-fous, connecteursa nous : le moteur, jamais exposekernel.ts, config/default-rules.ts, tool-permission-matrix.ts, le registre
3ce que l'utilisateur ajoute — sources de connaissance, skills du store, regles, memoire, connecteursa lui : portable, exportable, effacable (RGPD)addStoreKnowledgeSource, createStoreSkill, OrgFact / StoreFact / UserFact, IntegrationConnection

La propriete de croissance tient a une seule chose : chaque capacite lit le niveau 3 par le contexte, donc nourrir le store ameliore toutes les surfaces a la fois. Une capacite qui appelle un modele le fait a travers les trois niveaux, jamais nue. C'est mesurable, et la mesure trouve une exception : le noeud aiText du canvas appelle generateText({ model, prompt, system: data.systemPrompt }) — niveau 1 seul, ni kernel, ni regles, ni memoire. Le chat est un systeme a trois niveaux ; le noeud IA du canvas en a un. ai-platform/2777 le corrige.

Le vocabulaire de l'ecosysteme, range par etage

L'ecosysteme s'appelle « BoostEcom » et ses pieces BoostEcom OS, Intelligence, Spy, Graph, MCP, Algo, API, Extension, Theme, App, « et bien d'autres certainement oublies », sur le modele de ce que font les produits du monde reel. La liste est utile parce qu'elle melange les deux etages — et c'est exactement le test du triplet. Occurrences mesurees dans src/, content/, messages/ et docs/ le 2026-09-18 :

EtageNomCe que c'estDans le code
moteurBoostEcom Intelligencele record canonique, ses lecteurs, son descripteur93
moteurBoostEcom Spyle store vu de l'exterieur : scanner, sondes, le « Spy Store » du Hub158
moteurBoostEcom Graphle Store Graph (StoreSignalIndex, boutiques liees)0 comme nom
moteurBoostEcom Algosrc/services/algorithms/0 comme nom
grammaireBoostEcom OSle Hub OS de la home et le cockpit41
grammaireBoostEcom MCPle serveur MCP (« Bridge AI » dans les contenus)14
grammaireBoostEcom APIles routes REST3
grammaireBoostEcom Extensionl'extension Chrome7
grammaireBoostEcom Appl'app Shopify (listee « Theme Copilot AI », et c'est voulu)2
grammaireBoostEcom Themele theme2

La regle que ce rangement impose : un nom du moteur ne designe jamais une surface, et un nom de surface ne possede jamais une valeur. « Spy » est le seul endroit ou les deux se touchent — la section « Spy Store » du Hub est de la grammaire, la donnee qu'elle montre est le moteur — et c'est precisement la ou 2760 a trouve la regle de visibilite ecrite sept fois.

src/config/ecosystem.ts (ECOSYSTEM_PRODUCTS) est un troisieme axe : ce qui est vendu (Agency, Academy, Theme FullStack, Store…), ni moteur ni grammaire. Cet ADR ne le touche pas. Que Graph et Algo soient a zero occurrence comme noms de produit est un fait note ici, pas une decision prise ici : nommer un produit est une decision de gouvernance.

Et deux frontieres qui decoulent de la deuxieme mesure du contexte :

  • le serveur MCP du store n'est pas la connexion Shopify. Le MCP est une porte entrante pour un agent tiers, gatee par un consentement OAuth ; la Custom App est la credential sortante, la seule chose qui touche la boutique. Ils se rencontrent en un point exactement — la porte 2 de toolIsRegistrable, ou les scopes de la Custom App sont le plafond de ce qu'un client MCP peut se voir offrir. Une surface interne (canvas, cockpit, preview) s'assoit la ou le chat s'assoit : sur le registre, avec AtlasToolContext, jamais derriere le MCP ;
  • la connexion est un etat de la capacite, pas de la surface. Une capacite qui lit Shopify a trois etats : available, planned (pas d'executeur), et needs-connection (executeur present, mais ce store n'a pas la Custom App ou ses scopes). Le troisieme se derive des scopes de l'IntegrationConnection — la meme source que la porte 2 du MCP — et se rend desactive avant le run, jamais en erreur apres.

Alternatives écartées

OptionPourquoi non
Une interface unique — monter le corps du Hub dans le cockpit, ou l'inverseMesure dans #1300 : deux espaces de tokens (ile claire .boostecom-hub / cockpit dark-only). Ce qui se partage est la decision (le descripteur), jamais le composant.
Chaque surface tient sa liste de capacites (l'etat de depart)75 libelles pour 5 executions, capabilityKey lu nulle part. Trois copies d'une liste ne restent pas d'accord ; la garde ne peut compter que ce qui est derive.
Reduire le catalogue du canvas aux 7 types (la recommandation initiale de 2764)Prend le symptome pour le probleme. L'intention — un noeud = une chose precise — est deja implementee 87 fois dans le registre ; le catalogue etait trop petit, pas trop grand.
Ecrire un preset (prompt, modele, schema) par entreeUn preset est un libelle sur le meme noeud aiText. Il rend le catalogue « honnete » sans lui donner acces aux 87 capacites reelles, et il faudrait en ecrire 75. Reste utile par-dessus le registre (un preset = une capacite + une config), jamais a la place.
Brancher les surfaces internes sur le serveur MCPModele de confiance inverse (consentement OAuth pour sa propre boutique), plafond a 18 outils au lieu de 87, un saut reseau pour rentrer chez soi. Le MCP est un consommateur du registre.
Une seule route REST « capabilities » lue par toutes les surfaces sans porte d'audienceUn endpoint qui liste tout pour tout le monde est un oracle. Le contexte porte la porte ; la liste se derive apres la porte, par surface.

Conséquences

Ce que ca coute, et on l'accepte :

  • Quatre rebranchements, un par surface qui ecrit encore sa liste : le canvas (ce lot, ai-platform/2764), le Hub OS vers le lecteur canonique, l'inspecteur de la preview (sept onglets), l'extension. Chacun est un item nomme, pas une intention.
  • Le nom d'un outil devient un identifiant public. Le renommer le renomme dans le chat, le canvas, le MCP et l'API en meme temps — c'est le but — donc c'est une decision de contrat, plus un refactor local. Un workflow sauvegarde porte le nom de ses capacites ; un outil retire du registre rend ses noeuds planned, jamais silencieux.
  • Une capacite d'ecriture demande une approbation par surface. Le chat l'a (permissions.ts, fence.ts) ; le canvas ne l'a pas encore. Tant qu'il ne l'a pas, les outils d'ecriture y sont planned, visibles et desactives — jamais caches, jamais executes sans porte. Ecrire cette porte est un lot a part.
  • Le registre devient un hot file au sens de docs/team/protocol.md : y ajouter un outil ajoute un noeud au canvas et un item au catalogue de chaque surface branchee. C'est la propriete recherchee, et c'est aussi ce qui impose qu'un outil declare honnetement ce qu'il ecrit et ce qu'il exige.
  • Une surface ne peut plus « aller vite » avec une liste locale. Le cout d'une nouvelle surface est de brancher le registre, pas de le recopier. C'est plus lent le premier jour et c'est le seul chemin qui reste vrai le trentieme.

Ce que ca achete, et c'est la raison (2026-09-18) : chaque pilier versionne, pour qu'une amelioration ne casse jamais rien ailleurs, et pour presenter, utiliser, revendre et pricer chacun des piliers :

  • ce qui se versionne est le moteur, jamais une surface. Le nom d'une capacite, son inputSchema, la forme de sa sortie, les ids et unites du descripteur : ce sont les contrats. Une surface n'a rien a versionner, elle importe. Le serveur MCP porte deja /api/mcp/v1/ ; c'est ce motif, etendu au registre. Ameliorer une grammaire ne peut donc casser aucune autre surface ; ameliorer le moteur les touche toutes en meme temps, ce qui est le seul endroit ou il vaut la peine de tester ;
  • le registre est le point de comptage. Toutes les surfaces executent une capacite par la meme porte, donc l'usage se mesure une fois, au meme endroit, pour toutes — par capacite, par org, par surface. Il suffit que chaque outil declare l'etage du moteur auquel il appartient (Intelligence, Spy, Graph, Algo, Commerce…) pour que l'usage par etage se derive, sans un compteur par surface. Le prix d'un etage reste une decision de gouvernance (billing) ; ce que cette decision garantit est qu'il se calcule au lieu de s'estimer ;
  • revendre une donnee, c'est ouvrir une grammaire sur le meme moteur. Par l'API et par les portes d'audience existantes (readPublicIntelligence, la cle IntelligenceApiKey en lecture seule) — jamais par une seconde copie exportee. Une donnee vendue et une donnee affichee sont la meme valeur, au meme instant, par construction.

Un recalibrage de vocabulaire, assume ici : dans ce depot, un pilier (ownership.json) est une frontiere de propriete entre agents, pas une frontiere produit. Ce qui se versionne et se vend, ce sont les etages du moteur et les capacites qu'ils exposent.

Le signal qui dirait de revisiter ce choix : une capacite que le registre d'outils ne sait pas exprimer — un flux long avec humain dans la boucle, une operation transactionnelle multi-etapes — et qu'une surface voudrait quand meme offrir. Ce jour-la, on etend le registre, on ne recree pas une liste a cote.

Comment c'est appliqué

  • La garde src/test/surfaces-import-their-capabilities.test.ts : derive le registre d'outils du disque et refuse, dans toute surface (workflow/v2/components, preview/store-browser, components/hub, les routes api/intelligence/hub), un identifiant de capacite ecrit en dur qui ne resout aucun outil, en nommant le fichier. Sa liste d'exceptions ne peut que retrecir.
  • Le canvas : le type de noeud capability (workflow/v2/lib/workflow-types.ts) dont l'executeur (workflow/v2/server/execute.ts) construit AtlasToolContext depuis ExecuteScope par le meme builder que le chat et appelle l'outil par sa cle ; le catalogue du dialogue est servi par GET /api/workflow/v2/capabilities, derive du registre et du store, et n'a plus de liste ecrite a la main ; les trois etats vivent dans workflow-capabilities.ts et src/test/the-studio-runs-what-it-declares.test.ts les derive.
  • Le contexte : readStoreIntelligence et ses quatre portes (readPublicIntelligence, intelligenceIsPublic, orgMayReadIntelligence) restent LE lecteur ; src/test/store-intelligence-has-one-reader.test.ts tient la liste des lectures directes restantes comme un ensemble qui ne peut que retrecir.
  • La doc : le compte d'outils du serveur MCP du store etait deja une claim derivee (mcp.tools) ; la formulation « via shopify-bridge » et le docblock « all 15 » de register-tools.ts sont corriges, et le docblock recoit sa propre claim pour ne plus deriver seul.
  • Ce qui reste tenu a la main, et est donc dit ici : le troisieme corollaire (« jamais le generique ») n'a pas de garde. C'est un test de revue, pose a chaque nouvelle surface, et il sera viole le jour ou personne ne le pose.