Une source de verite pour le store — cockpit, Hub OS, extension, MCP
Audit du 2026-09-17, sur une exigence : le dashboard de store, le detail du spy store, l'extension Chrome et le MCP sont entierement lies, synchronises, avec une seule source de verite. Chaque affirmation ci-dessous…
Audit du 2026-09-17, sur une exigence : le dashboard de store, le detail du spy store, l'extension Chrome et le MCP sont entierement lies, synchronises, avec une seule source de verite. Chaque affirmation ci-dessous nomme le fichier qui la porte ; rien n'est deduit d'un souvenir.
1. Il y a UNE source, et elle existe deja
StoreIntelligence.record (prisma/schema.prisma), un
StoreCanonicalRecord de quatorze sections — identity, catalog,
stack, ads, reviews, social, email, traffic, commerce,
seo, agentic, graph, prediction, inferred — plus meta
(src/services/algorithms/intelligence/canonical/sections.ts:985).
Cle : primaryDomain. Lien optionnel storeId vers le Store du
marchand, resynchronise quand une connexion Shopify atterrit
(canonical/storage.ts:216-259). L'extension y VERSE deja des signaux
(POST /api/intelligence/panel/ingest, lots HMAC → TrafficSection), et
une selection d'element (POST /api/extension/select).
La question n'est donc pas « ou est la verite » mais « qui la lit, et par quel chemin ».
2. Qui la lit, et par quel chemin
| Surface | Lecteur | Projection | Ce que l'utilisateur voit |
|---|---|---|---|
| Hub OS (home, « Store Spy ») | /api/intelligence/hub/*, lookup?rich=1, similar?enrich=1 | hub-projection.ts → HubStore (97 champs) | intel-detail-view.tsx : Shop Analytics · Similar Shops · Meta Ads · Emails |
| Extension Chrome | lookup?rich=1, similar?enrich=1 | HubStore — contrat externe epingle par src/test/hubstore-external-contract.test.ts | le meme detail, dans le navigateur |
Detail public /intelligence/[category]/[slug] | getItemByDomain (services/discovery/aggregator.ts:441) | une autre projection (le record brut, isPresent par feuille) | une fiche |
Org : /[orgSlug]/intelligence/[domain] | getItemByDomain | idem, grille de sections | une fiche |
Serveur MCP Intelligence (/api/mcp/intelligence) | getItemByDomain | idem | des outils |
| Porte de visibilite | prisma.storeIntelligence en direct | aucune | — |
| Cockpit du marchand — vue Action plan | features/action-plan/synthesizer.ts | le record, pour le plan | le plan |
| Cockpit du marchand — bandeau | store-intelligence-banner.tsx | un fetch | un bandeau |
| Cockpit du marchand — Store details (Profile · Algorithm · Metrics · Market · Memory · Index · Rules) | useShopifyMeta, useCatalogSummary, useAgentLedger, useStoreReports, /api/stores/[id]/memory, knowledge/search | aucune : jamais le record | ses propres donnees Shopify, le ledger |
Serveur MCP store (/api/mcp/[storeId], 17 outils) | relais Shopify + getStudioSection + docs | aucune | — |
Trois projections du meme record : backlog/intelligence/0599 l'a
etabli le 2026-09-12, a epingle le contrat externe de l'extension, et a
laisse la reconciliation comme « un chantier a part, a ouvrir apres
celui-ci ». Il n'a pas ete ouvert. C'est le premier item ci-dessous.
3. Les trois divergences qui comptent
- Le marchand ne voit pas son propre store comme le spy le voit.
Dans le Hub OS, n'importe quel visiteur lit pour un concurrent le
trafic estime, les creatives Meta, la velocite des avis, les
captures e-mail, les boutiques similaires. Dans le cockpit, pour SA
boutique, l'operateur lit ses metadonnees Shopify et le ledger des
agents. Le record de son domaine existe (le
storeIdest relie), personne ne le lui montre. C'est la divergence que le proprietaire nomme ; les deux autres en sont la cause. - Trois projections, dont deux sans unites normalisees.
hub-projectionnormalisevisits_change_pct(ratio) etmom_growth_pct(points), type les absences (ads_absence), masqueANONYMIZED.getItemByDomainrend le record brut : la fiche publique et le MCP peuvent rendre pour un domaine un chiffre que le hub rend autrement pour le meme domaine. - Le MCP du store ne porte pas d'intelligence. Les 17 outils du serveur par boutique sont le relais Shopify, le Studio et la doc. Un agent connecte a une boutique ne peut pas lui demander « que sait BoostEcom de mon domaine » sans changer de serveur MCP.
Ce qui n'est PAS une divergence, pour ne pas ouvrir un item de trop :
l'extension est alimentee par le systeme (HubStore via lookup et
similar), et son contrat est garde. Elle lira la meme chose que le
cockpit le jour ou le cockpit lit HubStore.
4. Le design : un lecteur, une projection, N surfaces
StoreIntelligence.record
│
readStoreIntelligence({ domain } | { storeId }) ← intelligence/2760
│ une lecture Prisma, la visibilite appliquee, la fraicheur
▼
projectHubStore(record) = HubStore ← deja la projection du hub
│
├── Hub OS + extension (inchange : lookup, similar, hub/*)
├── fiche publique + org (getItemByDomain rend HubStore, plus le record brut)
├── MCP Intelligence (idem)
├── cockpit : Store details ← app-shell/2761 : GET /api/stores/[id]/intelligence
│ + les quatre sections du hub, rendues par le
│ MEME composant, sorti de components/hub/
└── MCP store : getStoreIntelligence ← integrations/2762, scope
boostecom:intelligence.read
Trois regles, et elles sont des gardes, pas des intentions :
- une seule lecture de
storeIntelligence.recordhors deservices/algorithms/intelligence/: zero. Une garde source-level (src/test/store-intelligence-has-one-reader.test.ts) refuse toutstoreIntelligence+recordailleurs. C'est la forme declient-does-not-resolve-permissionsappliquee a la donnee ; - une seule projection :
HubStore.getItemByDomaincontinue de rendre le record brut a qui en a besoin (la fiche itere les sections), mais tout CHIFFRE qu'une surface affiche vient deHubStore, unites comprises. Le contrat externe de 0599 devient le contrat de tout le monde ; - un seul corps de rendu pour « ce que BoostEcom sait d'un store » :
IntelDetailBodysort decomponents/hub/(il est colle au shell marketing par.lp-mkt2__scroll) verscomponents/shared/intelligence/, et le cockpit le monte dans Store details. Deux copies divergent ; la copie qui derive est celle que personne ne relit (src/app/(dashboard)/CLAUDE.md, « Le Studio marchand »).
5. Ce que la stack offre deja, et qu'on utilise
Le besoin : exploiter tout ce que Vercel AI SDK et Shopify proposent. Ce que le depot porte deja, pour ne pas le reinventer dans ces items :
- Vercel AI SDK v6 + Gateway (
src/config/ai-models.ts) : les outils @Atlas rendent des resultats structures ;getStoreIntelligencecote MCP est le memeHubStoreque le chat peut citer, donc le chat, le MCP et l'extension repondent le meme chiffre ; - Shopify Admin GraphQL par le relais MCP (
shopifyAdminGraphQL,runShopifyQL) et le deep-scan (shopify-deep-scan.ts), qui calibre le record d'un store CONNECTE (« connected merchants only » dedocs/architecture/intelligence-pipeline.md§5.6). C'est ce qui rend le record du marchand plus precis que celui d'un concurrent — et c'est exactement ce que le cockpit n'affiche pas aujourd'hui ; - UCP (
/.well-known/ucp-agent,docs/architecture/ucp-client.md) pour la lecture de catalogue negociee.
6. Les items ouverts par cet audit
| Item | Pilier | Ce qu'il livre |
|---|---|---|
intelligence/2760 | intelligence | readStoreIntelligence + HubStore comme unique projection + la garde « un seul lecteur » ; getItemByDomain et le MCP Intelligence projettent par lui |
app-shell/2761 | app-shell | GET /api/stores/[storeId]/intelligence + les quatre sections du hub dans Store details, rendues par le corps partage |
integrations/2762 | integrations | getStoreIntelligence sur le serveur MCP du store, scope boostecom:intelligence.read, meme HubStore |
Ordre : 2760, puis 2761 et 2762 en parallele (les deux consomment le
lecteur, aucun ne le duplique). intelligence/0599 reste ouvert pour
la partie doc qu'il annonce (deplacer le contrat de §D hors d'un audit
date) ; sa garde existe deja.