Audits · septembre 2026Les trois plafonds de lecture Shopify, et aucun n'est Shopify

Les trois plafonds de lecture Shopify, et aucun n'est Shopify

Date : 2026-09-20 · Demandé par : le propriétaire · Méthode : mesure

Date : 2026-09-20 · Demandé par : le propriétaire · Méthode : mesure

« On doit absolument tout pouvoir lire d'un admin Shopify basé sur les permissions que l'user nous donne, mais nous devons avoir aucune limite à part celle de l'user ou Shopify. »

La guideline nomme deux limites légitimes : l'utilisateur et Shopify. Cet audit mesurait trois plafonds internes.

Correction du 2026-09-22. Le premier plafond ci-dessous mesurait le mauvais chemin : /api/auth/shopify/start est la surface public-app OAuth, pas la source d'autorite de la connexion merchant-owned utilisee par /api/integrations/shopify/custom-app. La liste SHOPIFY_OAUTH_SCOPES peut plafonner le chemin public ; elle ne plafonne pas une Custom App que le marchand configure dans son Dev Dashboard. Les plafonds 2 (ingestion) et 3 (surface MCP) restent valides.

Plafond 1 — nous mesurions le mauvais grant Shopify

src/app/api/auth/shopify/start/route.ts demande 20 scopes pour le chemin public OAuth. Le tableau ci-dessous reste donc un audit utile de CE chemin secondaire. Il ne dit pas ce qu'une Custom App merchant-owned peut accorder : ce grant vient de la version d'app configuree par le marchand et doit etre lu apres le client_credentials exchange.

Scope jamais demandéCe qu'on ne peut pas lire
read_all_orderschemin Partner/public : historique au-delà de 60 jours ; ne pas appliquer cette regle telle quelle a une merchant-owned Custom App (voir plus bas)
read_inventorystocks par variante, alors que l'ingester a un handler inventory.ts
read_locationsles points de stock, donc la géographie de l'exécution
read_metaobjectsle contenu structuré — la famille qu'on vient de brancher
read_translationsles marchés non anglophones du marchand
read_marketsla segmentation géographique et ses prix
read_customer_eventsle parcours côté Shopify
read_gift_cards, read_shipping, read_marketing_events, read_online_store_navigationcartes cadeaux, profils de livraison, campagnes, menus

Une neuvième ligne que j'ai cru trouver, et qui était fausse

Ce paragraphe affirmait d'abord que runShopifyQL était gaté sur read_analytics et que personne ne le demandait. C'est faux : le gate accepte read_analytics OU read_reports, et read_reports était demandé depuis toujours. L'outil a toujours été offrable.

C'est le garde écrit dans la foulée (sdk/oauth-scopes.test.ts) qui a refusé la fausse affirmation : il compare la demande OAuth aux gates de TOOL_SHOPIFY_SCOPES et est resté vert quand read_analytics a été retiré. Il a donc été vérifié dans l'autre sens — en retirant read_reports, qui le fait bien échouer. La ligne est conservée ici plutôt que supprimée : un audit qui efface ses erreurs apprend à se tromper deux fois.

Les 60 jours : vraie limite du chemin Partner, fausse regle universelle

La documentation generique des access scopes dit bien que read_orders / write_orders couvrent 60 jours et que read_all_orders est un scope Shopify a permission pour aller au-dela. C'est le contrat a appliquer aux apps Partner/public qui passent par ce modele.

Mais Shopify Staff a precise en janvier puis fevrier 2026 que, pour une merchant-owned Custom App creee par le marchand dans son Dev Dashboard, read_orders suffit a recuperer l'historique complet et que client_credentials est le grant attendu. Le depot ne peut donc plus encoder « pas de read_all_orders = 60 jours » comme une propriete globale de Shopify.

Sources :

La consequence correcte est plus exigeante : la couverture temporelle des commandes doit etre mesuree par type de connexion et verifiee sur une vraie boutique. RevenueDaily, LTV, cohortes et prediction ne doivent jamais croire qu'une fenetre partielle est l'histoire complete.

Protected Customer Data est une contrainte distincte de read_all_orders. La doc Shopify impose une review aux public apps pour ces donnees, alors que les custom apps suivent un regime different ; certaines donnees peuvent encore dependre du type d'app, du plan ou d'une restriction Shopify. On expose donc le refus observe, on ne le predit pas avec une regle « public app » appliquee a tous.

Plafond 2 — l'ingester couvre 4 domaines sur ~20

features/shopify/ingester/handlers/ : order, product, customer, inventory. Les topics souscrits confirment le même périmètre.

Tout le reste de l'admin — collections, menus, metafields, metaobjects, remises, marchés, locales, traductions, fulfillments, cartes cadeaux, publications, thèmes, fichiers — n'entre JAMAIS dans notre base. Il est au mieux proxyfié vers un agent, jamais ingéré.

C'est la distinction qui décide de tout : proxyfier n'est pas connaître. Un graphe, une sonde, un algo de prédiction ne lisent pas un passthrough. Ils lisent nos tables. Un domaine non ingéré est invisible pour StoreSignalIndex, StoreMetricDaily, le Prediction Engine et tout ce que la guideline appelle « la couche d'intelligence ».

Plafond 3 — la surface MCP est écrite à la main

15 outils relais, énumérés un par un. shopifyAdminGraphQL rend déjà techniquement TOUT le lisible atteignable, donc le manque n'est pas la capacité : c'est la lisibilité. Un outil nommé, typé, documenté est découvrable par un agent ; un passthrough générique demande à l'agent de deviner le schéma d'abord.

Mais énumérer à la main est une course perdue : 70 méthodes attendent une surface, et chaque tranche coûte six locales, deux corpus de doc et quatre gardes dérivées. La bonne forme est la dérivation, comme tout le reste de ce dépôt : la surface de lecture doit se déduire du schéma Admin et du grant réel du marchand, pas d'une liste tapée.

Ce que cet audit ne mesure pas

L'extérieur du store. La guideline en nomme quatre couches (intérieur, extérieur, marché, connaissance mondiale) ; cette page ne traite que la première. Le scanner, storefront-mcp, le client UCP et le Store Graph couvrent les autres et n'ont pas été remesurés ici.

Ce qui en découle

  1. Lire le grant reel du bon chemin. La Custom App merchant-owned est le chemin principal (ADR 0033) : ses scopes viennent du Dev Dashboard du marchand. Le catalogue OAuth public ne doit jamais devenir une limite implicite de ce chemin. Un grant partiel est accepte et ses gaps sont derives.
  2. Ingérer tout domaine lisible, pas seulement les quatre qui ont un webhook.
  3. Dériver la surface de lecture du schéma et du grant reel plutôt que l'énumérer.