Audits · septembre 2026Une source de verite pour le store — cockpit, Hub OS, extension, MCP

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

SurfaceLecteurProjectionCe que l'utilisateur voit
Hub OS (home, « Store Spy »)/api/intelligence/hub/*, lookup?rich=1, similar?enrich=1hub-projection.ts → HubStore (97 champs)intel-detail-view.tsx : Shop Analytics · Similar Shops · Meta Ads · Emails
Extension Chromelookup?rich=1, similar?enrich=1HubStore — contrat externe epingle par src/test/hubstore-external-contract.test.tsle 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]getItemByDomainidem, grille de sectionsune fiche
Serveur MCP Intelligence (/api/mcp/intelligence)getItemByDomainidemdes outils
Porte de visibiliteprisma.storeIntelligence en directaucune—
Cockpit du marchand — vue Action planfeatures/action-plan/synthesizer.tsle record, pour le planle plan
Cockpit du marchand — bandeaustore-intelligence-banner.tsxun fetchun bandeau
Cockpit du marchand — Store details (Profile · Algorithm · Metrics · Market · Memory · Index · Rules)useShopifyMeta, useCatalogSummary, useAgentLedger, useStoreReports, /api/stores/[id]/memory, knowledge/searchaucune : jamais le recordses propres donnees Shopify, le ledger
Serveur MCP store (/api/mcp/[storeId], 17 outils)relais Shopify + getStudioSection + docsaucune—

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

  1. 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 storeId est relie), personne ne le lui montre. C'est la divergence que le proprietaire nomme ; les deux autres en sont la cause.
  2. Trois projections, dont deux sans unites normalisees. hub-projection normalise visits_change_pct (ratio) et mom_growth_pct (points), type les absences (ads_absence), masque ANONYMIZED. getItemByDomain rend 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.
  3. 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.record hors de services/algorithms/intelligence/ : zero. Une garde source-level (src/test/store-intelligence-has-one-reader.test.ts) refuse tout storeIntelligence + record ailleurs. C'est la forme de client-does-not-resolve-permissions appliquee a la donnee ;
  • une seule projection : HubStore. getItemByDomain continue de rendre le record brut a qui en a besoin (la fiche itere les sections), mais tout CHIFFRE qu'une surface affiche vient de HubStore, 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 » : IntelDetailBody sort de components/hub/ (il est colle au shell marketing par .lp-mkt2__scroll) vers components/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 ; getStoreIntelligence cote MCP est le meme HubStore que 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 » de docs/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

ItemPilierCe qu'il livre
intelligence/2760intelligencereadStoreIntelligence + HubStore comme unique projection + la garde « un seul lecteur » ; getItemByDomain et le MCP Intelligence projettent par lui
app-shell/2761app-shellGET /api/stores/[storeId]/intelligence + les quatre sections du hub dans Store details, rendues par le corps partage
integrations/2762integrationsgetStoreIntelligence 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.