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 :
| Quadrant | Ce qui l'implemente, aujourd'hui |
|---|---|
| le store de l'interieur | features/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'exterieur | tracking-scan.ts, store-audit-tool.ts, vitals-tools.ts (5, PSI / CrUX), aeo-tools.ts (3), cro-tools.ts (2) |
| le marche autour | intelligence-tools.ts (14), radar-tools.ts (2), marketplace-tools.ts (2) |
| la connaissance mondiale | knowledge-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 decommerce-os-registry, resolues en 5 types executables. Un clic sur « SEO Audit » et un clic sur « Forecast » produisaient le meme noeudaiText,prompt: "". L'executeur ne lit nilabel, nidescription, nicapabilityKey—capabilityKeyn'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'appelaitreadStoreIntelligence, le lecteur canonique queintelligence/2760venait 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 partagestoreIntelSections; - 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
2760ne la ramene a quatre portes nommees ; CLAUDE.mddecrivait le serveur MCP du store comme « 18 outils enregistres viaregister-tools.ts+shopify-bridge». Le nombre est juste, et il est derive : la claimmcp.toolsdescripts/check-doc-claims.mjscompte lesserver.registerTool(et les confronte aMCP_SCOPES(18, dont 2platformAdminOnly). La premiere mesure de cette session avait dit 14 en ne comptant que l'assistantmaybe(— et c'est la claim derivee qui l'a corrigee, ce qui est la lecon meme de ce document. Ce qui etait faux : « viashopify-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 :
- Un moteur unique fonctionne.
readStoreIntelligencesert sept appelants (deux serveurs MCP, une route REST, les outils du chat, le catalogue de scopes, l'agregateur discovery, le cockpit) etstoreIntelSectionsen sert quatre. Trois surfaces tres differentes affichent le meme chiffre parce qu'elles lisent la meme liste. - 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'est | Regle | |
|---|---|---|
| capacite | ce qui peut etre fait — un outil du registre src/features/ai/tools/ | une seule liste, importee, jamais recopiee |
| contexte | sur 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 |
| grammaire | comment l'intention est exprimee et le resultat lu | differente par surface, par design |
Les grammaires en vigueur :
| Surface | Sa grammaire | Ce que le moteur ajoute a la version generique |
|---|---|---|
| chat | la conversation | un ChatGPT ne connait pas la boutique ; ici chaque tour lit les 87 outils sur le contexte du store |
| canvas (Studio) | le graphe | un editeur de noeuds generique n'a que des primitives ; ici chaque noeud est une capacite du registre |
| preview | le store vu a travers les quatre quadrants | un 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 |
| cockpit | le tableau de bord | un dashboard montre des metriques ; ici il montre le record canonique, non masque, pour le membre |
| Hub OS | le parcours et le classement, public | un annuaire liste ; ici il projette le meme record, par la porte publique |
| Bridge AI (le serveur MCP) | le protocole, pour un agent tiers | une 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 REST | HTTP, pour un developpeur | idem, en HTTP |
| extension Chrome | l'overlay in-situ, sur le store d'un autre | un onglet ; ici le meme descripteur, par la porte publique |
Trois corollaires, chacun testable :
- 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.
- Une capacite porte le meme nom partout.
getRevenueTrenddans le chat estgetRevenueTrenddans 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. - 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.
- 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.
- 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/transparencypublie la methode, pas le moteur ;docs-separationrefuse dans la doc publique tout lien vers le depot prive ; un scopeplatformAdminOnlydisparait 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 :
| Niveau | Quoi | A qui | Ou il est lu |
|---|---|---|---|
| 1 | les modeles — providers, gateway | loue : interchangeable par config/ai-models.ts | resolveModel, resolveWorkflowModel |
| 2 | l'ecosysteme — kernel, process, skills de plateforme, regles par defaut, garde-fous, connecteurs | a nous : le moteur, jamais expose | kernel.ts, config/default-rules.ts, tool-permission-matrix.ts, le registre |
| 3 | ce que l'utilisateur ajoute — sources de connaissance, skills du store, regles, memoire, connecteurs | a 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 :
| Etage | Nom | Ce que c'est | Dans le code |
|---|---|---|---|
| moteur | BoostEcom Intelligence | le record canonique, ses lecteurs, son descripteur | 93 |
| moteur | BoostEcom Spy | le store vu de l'exterieur : scanner, sondes, le « Spy Store » du Hub | 158 |
| moteur | BoostEcom Graph | le Store Graph (StoreSignalIndex, boutiques liees) | 0 comme nom |
| moteur | BoostEcom Algo | src/services/algorithms/ | 0 comme nom |
| grammaire | BoostEcom OS | le Hub OS de la home et le cockpit | 41 |
| grammaire | BoostEcom MCP | le serveur MCP (« Bridge AI » dans les contenus) | 14 |
| grammaire | BoostEcom API | les routes REST | 3 |
| grammaire | BoostEcom Extension | l'extension Chrome | 7 |
| grammaire | BoostEcom App | l'app Shopify (listee « Theme Copilot AI », et c'est voulu) | 2 |
| grammaire | BoostEcom Theme | le theme | 2 |
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, avecAtlasToolContext, 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), etneeds-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
| Option | Pourquoi non |
|---|---|
| Une interface unique — monter le corps du Hub dans le cockpit, ou l'inverse | Mesure 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 entree | Un 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 MCP | Modele 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'audience | Un 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 sontplanned, 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 cleIntelligenceApiKeyen 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 routesapi/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) construitAtlasToolContextdepuisExecuteScopepar le meme builder que le chat et appelle l'outil par sa cle ; le catalogue du dialogue est servi parGET /api/workflow/v2/capabilities, derive du registre et du store, et n'a plus de liste ecrite a la main ; les trois etats vivent dansworkflow-capabilities.tsetsrc/test/the-studio-runs-what-it-declares.test.tsles derive. - Le contexte :
readStoreIntelligenceet ses quatre portes (readPublicIntelligence,intelligenceIsPublic,orgMayReadIntelligence) restent LE lecteur ;src/test/store-intelligence-has-one-reader.test.tstient 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 « viashopify-bridge» et le docblock « all 15 » deregister-tools.tssont 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.