# Profil CSV Shopify V0 : création simple, réparation de format

Spécification approuvée par Pierre le 11 octobre 2026, conservée avant implémentation. Profil issu de la proposition examinée sur le dépôt PierreDaguier/service-factory, branche plan/pilot-selection-and-ape, commit 8d49932b75499c786a39e0f45809feab5ab2616e. Ce document complète la liste différée du [contrat](contract.md), avec le diagnostic de prix absent et la séparation codec/profil demandés lors de l'approbation. Aucun moteur, import réel ou résultat commercial n'est attesté par cette spécification.

## 1. Décision de périmètre

Identifiant de profil : shopify-simple-create-v0. Un enregistrement logique de données représente un produit simple. Le pilote répare une représentation CSV, jamais le contenu d'un catalogue. Il propose uniquement la conversion du séparateur, des fins d'enregistrement et le retrait du BOM, sur une nouvelle copie.

Choix V0 volontairement étroit : sept champs produit, sans prix, SKU, code-barres, options, images ni stock. Ces exclusions sont des limites du pilote, pas des impossibilités Shopify. Elles évitent de prétendre vérifier des dépendances de variantes que la documentation décrit surtout dans le contexte des mises à jour. Le codec est néanmoins testé avec des chaînes SKU, code-barres et prix, indépendamment du profil métier.

Avant export, l'utilisateur confirme l'intention « création de nouveaux produits simples ». Une intention de mise à jour bloque le parcours. Le fichier seul ne prouve pas cette intention et ne permet pas de connaître les handles déjà présents dans une boutique. Aucun accès boutique, import automatique ou contrôle de disponibilité d'un handle n'est effectué.

## 1.1. Séparation de la réparation de format et de la validation métier

Le codec CSV est indépendant de Shopify : décodage, parsing, matrice de chaînes, sérialisation et relecture ne dépendent ni d'une liste de sept colonnes, ni des noms d'en-tête, ni des règles métier. Il doit préserver aussi les colonnes supplémentaires et les chaînes SKU, codes-barres et prix dans ses tests propres. Il ne décide pas qu'un fichier est valide pour un import.

Le validateur shopify-simple-create-v0 applique ensuite la liste fermée, les règles de valeurs, l'intention de création et les avertissements sur la matrice correctement lue, sans la modifier. L'interface distingue les diagnostics de format, les réparations consenties et les diagnostics métier. Une réparation de format réussie n'efface jamais un blocage métier. Dans le parcours V0, un fichier hors profil reste non exportable ; cette séparation n'introduit aucun contournement d'export.

L'extension à davantage de colonnes se fait par une nouvelle version explicite du profil et ses tests, sans réécrire le codec ni élargir silencieusement V0. Les invariants de conservation restent communs ; les suites de tests codec et profil restent séparées.

## 2. Base documentaire et limites de preuve

La référence actuelle exige Title pour créer ; elle distingue les mises à jour, où les dépendances d'options importent. Status absent reçoit un défaut côté Shopify, tandis que Status présent doit être renseigné. L'ancienne nomenclature reste compatible. Barcode et Barcodes ne peuvent pas coexister. Le format documenté utilise virgule, UTF-8 et LF. Source S1.

La page d'erreurs atteste les anciens en-têtes Handle, Body (HTML) et Published. Sa longue ligne historique n'est pas une liste de colonnes obligatoires pour toute création ; S1 prévaut pour cette distinction. Source S2.

Le modèle CSV lié depuis S1 n'a pas pu être lu par l'outil web : type application/octet-stream non pris en charge. Aucune affirmation ne repose sur son contenu. Les alias ci-dessous reposent sur S1 et S2, sans prétendre qu'ils ont été testés par un import réel.

## 3. Liste fermée des colonnes

La table suivante définit notre profil de validation V0. Comparaison exacte, sensible à la casse, sans suppression d'espaces. Un alias accepté est conservé tel quel à l'export. Un seul nom par champ logique est autorisé.

| Champ | Noms d'en-tête acceptés, exacts | Colonne absente | Cellule vide, chaîne de longueur zéro | Valeur non vide : choix V0 |
| --- | --- | --- | --- | --- |
| Titre | Title | Erreur TITLE_COLUMN_MISSING | Erreur TITLE_EMPTY | Toute chaîne contenant au moins un caractère autre qu'un espace Unicode ; espaces périphériques préservés |
| Identifiant URL | URL handle ; Handle | Acceptée, aucun ajout | Acceptée, avertissement HANDLE_UNSPECIFIED | Sous-ensemble conservateur ASCII : lettres, chiffres, tirets ; au moins une lettre ou un chiffre ; unicité dans le fichier |
| Description | Description ; Body (HTML) | Acceptée | Acceptée | Chaîne intacte, affichée comme texte, HTML jamais exécuté |
| Fournisseur | Vendor | Acceptée | Acceptée | Chaîne intacte |
| Type libre | Type | Acceptée | Acceptée | Chaîne intacte |
| Publication | Published on online store ; Published | Acceptée, avertissement PUBLICATION_DEFAULT | Acceptée, même avertissement | Seulement true ou false en minuscules ; autre représentation hors profil |
| État | Status | Acceptée, avertissement STATUS_DEFAULT_ACTIVE | Erreur STATUS_EMPTY | Seulement active, draft ou archived, exacts |

Une cellule composée exclusivement d'espaces n'est pas vide : Title provoque TITLE_WHITESPACE_ONLY ; un handle provoque HANDLE_OUTSIDE_PROFILE ; Status et publication provoquent leur diagnostic de valeur. Jamais de trim pour « réparer ».

Les avertissements de défaut disent que le fichier laisse Shopify décider et que le résultat peut être actif/publié. Ils ne remplissent aucune cellule, ne présument pas qu'un produit draft sera publié et sont regroupés pour éviter un avertissement par ligne.


Diagnostic stable PRICE_DEFAULT_ZERO : avertissement non bloquant de portée fichier lorsque le prix est absent, donc sur tout fichier accepté par V0. Message : « Aucun prix n'est fourni. Lors de la création, Shopify peut appliquer un prix de 0.00. Vérifiez les prix dans Shopify avant de mettre les produits en vente. » S1 documente le défaut 0.00 de Price en l'absence de valeur ; il s'agit d'un risque d'import futur, pas d'un prix calculé ni d'un import observé. L'avertissement ne remplit aucune cellule, n'ajoute aucune colonne, ne fixe aucun prix et ne rend pas Price ou Variant Price admissible en V0. Si une version future autorise un prix optionnel, elle doit expliciter et tester séparément le cas colonne absente et le cas cellule vide, sans défaut inséré par le codec.

PRICE_DEFAULT_ZERO, STATUS_DEFAULT_ACTIVE et PUBLICATION_DEFAULT doivent être visibles dans le diagnostic et le résumé avant le bouton d'export, même pour Title seul et même lorsqu'aucune réparation de format n'est nécessaire. Les avertissements restent non bloquants et regroupés ; ne pas les masquer derrière le message de réussite ni attendre le téléchargement pour les révéler.

Un handle absent/vide n'est pas une erreur de création. Pour les handles explicites, des doublons exacts bloquent ; une collision ne différant que par la casse bloque par prudence V0, sans prétendre documenter l'algorithme Shopify. Des titres identiques produisent un avertissement DUPLICATE_TITLE, sans génération de handle ni déduplication.

## 4. Structures et colonnes hors profil

La liste blanche est exhaustive. Toute autre colonne, même entièrement vide, bloque l'export et reste visible. Aucune suppression, fusion ou traduction d'en-tête n'est proposée.

| Famille détectée | Diagnostic | Exemples, liste indicative |
| --- | --- | --- |
| Options et variantes | SCOPE_VARIANTS | Option1/2/3 name/value, anciens Name/Value, LinkedTo, Variant image URL |
| Attributs de variante non vérifiés en V0 | SCOPE_VARIANT_FIELDS | SKU, Variant SKU, Price, Variant Price, Compare-at price, Cost per item, poids, taxe, expédition, dimensions |
| Codes-barres | SCOPE_BARCODES | Barcode, Barcodes, Variant Barcode |
| Inventaire/exécution | SCOPE_INVENTORY | Inventory tracker/quantity, anciens Variant Inventory…, Fulfillment service, Continue selling when out of stock |
| Images | SCOPE_IMAGES | Product image URL, Image Src, Image position, Image alt text, variantes historiques |
| Marchés | SCOPE_MARKETS | Included / …, Price / …, Compare-at price / …, Compare At Price / … |
| Métachamps | SCOPE_METAFIELDS | product.metafields.…, noms contenant cette notation, Option LinkedTo, Google Shopping / … |
| Autres colonnes reconnues ou inconnues | HEADER_OUTSIDE_PROFILE | Tags, Product category, SEO…, Collection, Gift card, champs fournisseur arbitraires |

La reconnaissance d'une famille sert à expliquer, jamais à autoriser. Une faute de casse ou un nom non répertorié reste HEADER_OUTSIDE_PROFILE, sans renommage suggéré comme automatique.

Le couple exact Barcode + Barcodes reçoit en priorité HEADER_BARCODE_CONFLICT, règle Shopify attestée. Toute combinaison de plusieurs noms d'un même champ logique accepté reçoit HEADER_ALIAS_CONFLICT. Un en-tête exact répété reçoit HEADER_DUPLICATE. Les trois colonnes Barcode/Barcodes/Variant Barcode ne sont pas présentées comme trois alias sémantiquement interchangeables : le plural introduit une syntaxe qui n'est pas implémentée.

Toutes les colonnes d'options sont exclues, même un couple supposé « par défaut ». Aucune valeur Option1 n'est exigée ou inventée pour Title seul. Une ligne sans titre ou un handle répété n'est jamais reconstruite en variante ou ligne d'image. Le diagnostic peut signaler cette possibilité sans l'affirmer.

XLSX, archives, mappings fournisseur, mise à jour de boutique, import et cartes-cadeaux restent hors parcours. Le refus d'un XLSX ne repose pas uniquement sur son extension : un fichier ZIP/XLSX identifié n'est pas lu comme du CSV.

## 5. Lecture, conservation et réparations

- Limites V0, pas limites Shopify : 5 Mio = 5 242 880 octets, BOM compris ; 10 000 enregistrements logiques de données, hors en-tête. Une cellule multiligne ne crée pas plusieurs produits. Taille contrôlée avant décodage ; arrêter dès le 10 001e enregistrement. Aucun résultat tronqué exportable.
- UTF-8 strict. Aucune substitution de caractères. Un seul BOM initial est une signature d'encodage : signaler sa présence, demander son retrait pour la copie. Un U+FEFF dans une cellule est une donnée à préserver, pas un BOM à retirer. BOM refusé : original disponible, export V0 normalisé bloqué jusqu'au choix.
- Virgule par défaut. Point-virgule et tabulation possibles uniquement après aperçu et confirmation explicite. Une suggestion n'est pas une décision. En ambiguïté, exiger le choix ; ne pas choisir le séparateur qui fait artificiellement passer le schéma. Title seul reste un CSV virgule valide à une colonne.
- Grammaire stricte : cellules citées, guillemets doublés, séparateurs et LF/CRLF internes acceptés. Hors cellule citée, LF/CRLF acceptés ; CR isolé bloqué par choix V0. Guillemets non fermés, guillemet au milieu d'un champ non cité, caractères après la fermeture avant séparateur/fin : erreur, aucune devinette.
- Largeur identique à l'en-tête. Une cellule finale vide explicitement délimitée est une cellule ; une cellule absente est une erreur de largeur. Aucune ligne blanche sautée. Un terminator final clôt le dernier enregistrement et ne crée pas de ligne supplémentaire ; deux terminators peuvent créer une ligne vide qui doit être diagnostiquée.
- Chaque cellule demeure une chaîne, sans conversion numérique, normalisation Unicode, changement de casse, remplissage, suppression, tri ou fusion. Les limites de validation n'autorisent jamais à transformer les valeurs.
- Sortie UTF-8 sans BOM, virgule, LF entre enregistrements et un LF final. Les LF/CRLF internes aux cellules demeurent inchangés. Les guillemets de représentation peuvent changer ; pas les chaînes relues. Relecture indépendante de la sortie : matrice complète, ordre, dimensions et toutes les chaînes identiques à l'entrée après interprétation du BOM initial. Toute différence interdit le téléchargement.
- Le résumé sépare format source, réparations proposées/acceptées, format cible et zéro cellule modifiée. Le bouton explicite exporte une copie avec nom distinct. Aucun succès silencieux : si aucune réparation n'est nécessaire, le libellé dit « télécharger une copie », pas « réparer ».
- Diagnostic et aperçu restent disponibles en cas de blocage. Aucun contenu de cellule n'est exécuté comme HTML ou formule. Signaler les cellules à préfixe de formule pour l'ouverture future en tableur ; ne pas ajouter d'apostrophe, ce qui changerait la donnée.
- Tout est local, en mémoire. Aucun catalogue, fichier, nom, extrait ou rapport envoyé, télémétré ou persisté automatiquement. Le rapport n'est enregistré que sur action explicite. Annulation/réinitialisation libère l'état du fichier ; une nouvelle sélection invalide diagnostics, confirmations et copie de l'ancien fichier.

## 6. Diagnostics stables et ordre de traitement

Chaque diagnostic contient code, catégorie, gravité, message, portée, enregistrement logique et ligne physique de début/fin si connus, indice de colonne et nom exact si connus, règle du profil ou source officielle, ainsi qu'une action possible. Pas de fausse localisation pour une erreur d'encodage : utiliser l'offset d'octet quand disponible.

1. Admission/encodage : FILE_EMPTY, FILE_TYPE_UNSUPPORTED, FILE_TOO_LARGE, UTF8_INVALID, ROW_LIMIT_EXCEEDED.
2. Choix et structure : SEPARATOR_CONFIRMATION_REQUIRED, SEPARATOR_AMBIGUOUS, CSV_UNCLOSED_QUOTE, CSV_ILLEGAL_QUOTE, CSV_BARE_CR, CSV_ROW_WIDTH, HEADER_EMPTY, HEADER_DUPLICATE, HEADER_ALIAS_CONFLICT, HEADER_BARCODE_CONFLICT, NO_PRODUCT_ROWS.
3. Profil : codes SCOPE_* et HEADER_OUTSIDE_PROFILE ci-dessus ; SCOPE_UPDATE, TITLE_COLUMN_MISSING, TITLE_EMPTY, TITLE_WHITESPACE_ONLY, STATUS_EMPTY, STATUS_INVALID, HANDLE_OUTSIDE_PROFILE, HANDLE_DUPLICATE, HANDLE_CASE_COLLISION, PUBLICATION_VALUE_OUTSIDE_PROFILE.
4. Avertissements non bloquants : HANDLE_UNSPECIFIED, DUPLICATE_TITLE, STATUS_DEFAULT_ACTIVE, PUBLICATION_DEFAULT, PRICE_DEFAULT_ZERO, CELL_FORMULA_RISK. Diagnostic de format : BOM_REMOVAL_REQUIRED et RECORD_ENDINGS_NORMALIZATION, explicitement soumis au choix d'export.
5. Garde finale : ROUNDTRIP_MISMATCH, EXPORT_FAILED. Aucun fichier partiel livré.

Catégories : erreur de structure, erreur de valeur, hors profil, choix requis, avertissement. Les quatre premières bloquent tant qu'elles s'appliquent. Après erreur de parsing, ne pas produire de diagnostics métier sur des cellules devinées. Le rapport peut être plafonné à 100 exemples par code avec nombre total exact ; cette limite d'affichage n'est ni un arrêt de validation ni un succès partiel.

Message de réussite autorisé : « Aucune erreur détectée dans le profil pris en charge. » Ajouter que l'import réel et les collisions avec la boutique ne sont pas vérifiés. Jamais « fichier Shopify garanti ».

## 7. Matrice d'acceptation comportementale

| Cas | Résultat requis |
| --- | --- |
| Fixture title-only.csv actuelle : deux titres accentués | Profil accepté ; deux produits conservés ; aucun handle/option/statut ajouté ; avertissements de défaut |
| Fixture semicolon.csv actuelle | Aperçu : 2 colonnes, 2 produits ; confirmation du point-virgule ; export virgule ; virgule et guillemets internes intacts |
| Fixture unclosed-quote.csv actuelle | CSV_UNCLOSED_QUOTE à partir de la ligne 2 ; export bloqué |
| Fixture ragged-row.csv actuelle | CSV_ROW_WIDTH, ligne 2, attendu 2/reçu 1 ; aucune cellule ajoutée |
| Title seul, aucun handle/options | Accepté ; ne pas inventer une dépendance Shopify ; PRICE_DEFAULT_ZERO visible avant export, sans ajout de prix |
| Prix absent avec Status=draft et Published=false | PRICE_DEFAULT_ZERO reste visible ; aucune affirmation de publication et aucune cellule modifiée |
| Format déjà normalisé, prix absent | PRICE_DEFAULT_ZERO visible avant « télécharger une copie » ; avertissement non bloquant |
| Codec avec colonnes supplémentaires | Lecture/sérialisation/relecture sans perte indépendantes de la liste V0 ; validation métier et export V0 demeurent bloqués si hors profil |
| En-tête seul ou fichier vide | NO_PRODUCT_ROWS ou FILE_EMPTY ; aucun succès trompeur |
| Title absent / vide / espaces seuls | Trois diagnostics distincts, aucun trim |
| Vendor absent vs Vendor vide | Deux matrices différentes, toutes deux conservées ; aucune colonne ajoutée ou supprimée |
| Status absent / vide / draft / DRAFT | Avertissement / blocage / accepté / STATUS_INVALID |
| Publication absente / vide / false / FALSE | Avertissement / avertissement / accepté / hors profil |
| Handle ancien ou URL handle actuel seul | Accepté avec la même validation ; en-tête original conservé |
| Description et Body (HTML) ensemble | HEADER_ALIAS_CONFLICT ; pas de fusion |
| En-tête vide, Vendor répété, espace après Title | Blocage distinct ; espace non corrigé |
| Colonne inconnue vide / Tags / Collection | HEADER_OUTSIDE_PROFILE ; visible, intacte ; pas de bouton supprimer |
| SKU/Price ou anciens équivalents | SCOPE_VARIANT_FIELDS, même sans options ; restriction V0 affichée |
| Price contenant 12,50 en cellule citée | String intacte ; hors profil, aucune conversion en 12.50 |
| Barcode + Barcodes | HEADER_BARCODE_CONFLICT ; aucune fusion ou interprétation des codes |
| Option1 vide, couple par défaut, Option2 | Tous hors profil ; aucune exception implicite |
| Handle répété / collision de casse | Blocage ; lignes non fusionnées |
| Ligne image sans titre, colonne de marché/stock/métachamp | Hors profil et/ou titre manquant ; pas de reconstruction |
| UTF-8 invalide / BOM accepté / BOM refusé | Refus sans remplacement / retrait visible / export normalisé bloqué |
| LF et CRLF hors cellules, CRLF à l'intérieur | LF externe à la sortie ; CRLF interne strictement identique |
| Séparateur ambigu / confirmation tabulation / changement ultérieur | Export bloqué / transformation approuvée / nouveau parsing et confirmations invalidées |
| Cellule finale vide / ligne courte / ligne blanche finale supplémentaire | Largeur correcte conservée / erreur / non ignorée |
| Codec seul : SKU 000123, code long, prix 00012.50, 12,50, accents composés/décomposés, emoji, guillemets, espace, multiligne | Égalité exacte des chaînes à la relecture, sans passage numérique ; ne constitue pas un succès du profil Shopify |
| Taille exactement 5 242 880 / +1 octet | Acceptation technique si CSV valide / FILE_TOO_LARGE |
| 10 000 / 10 001 produits avec cellules multilignes | Acceptation si valide / ROW_LIMIT_EXCEEDED ; nombre logique |
| Description contenant script HTML, préfixe de formule | Texte inerte ; avertissement formule ; valeurs inchangées |
| Erreur injectée dans la sérialisation | ROUNDTRIP_MISMATCH ; aucun téléchargement |
| Double clic export, annuler, nouveau fichier, reset | Pas d'export ancien ou partiel ; copie distincte ; états et consentements liés au fichier courant |
| Fichier bloqué puis téléchargement du rapport | Rapport explicite disponible localement ; aucune fuite réseau/persistance automatique |

Les deux fixtures positives existantes restent positives. Aucune fixture actuelle annoncée valide ne devient bloquée. Les nouveaux cas métier exclus sont volontairement négatifs ; les cas de chaînes SKU/prix appartiennent à la suite codec.

## 8. Critères de livraison et décisions restantes

Avant de dire moteur livré : tests réels du codec, du profil, de la relecture et du parcours local ; vérification navigateur des étapes et interruptions ; vérification des requêtes réseau/persistances ; commande de contrôle du dépôt exécutée en plus. Le contrôle documentaire racine seul ne prouve rien sur le moteur. Aucun test n'a été exécuté dans cette recherche.

Les choix techniques ci-dessus sont bornés, versionnés et réversibles ; aucune décision supplémentaire n'est indispensable pour préparer l'implémentation de ce profil. Pierre a approuvé ce profil et le choix du pilote ; leur conservation dans le dépôt précède l'implémentation. Le statut de fusion de la PR de sélection reste un contrôle distinct. Pour inclure prix/SKU/codes-barres/options/images ou davantage d'alias, il faut une nouvelle version de profil, des règles vérifiées et des tests dédiés ; ne pas improviser ces extensions pendant le codage.

Avant toute affirmation de compatibilité réelle, une validation contrôlée en boutique demanderait un protocole et une autorisation séparés. Avant publication commerciale, acquisition, prix, mesure et critères de poursuite restent non validés. Ni un export synthétique ni l'existence d'offres concurrentes ne prouvent des ventes.

## Sources exactes, consultées le 2026-10-11

S1. Shopify, format produit CSV, sections Required columns, Data dependencies, description des colonnes et création : https://help.shopify.com/en/manual/products/import-export/using-csv

S2. Shopify, erreurs d'import CSV, notamment anciens en-têtes et guillemets : https://help.shopify.com/en/manual/products/import-export/common-import-issues

S3. Contrat lu au commit exact : https://github.com/PierreDaguier/service-factory/blob/8d49932b75499c786a39e0f45809feab5ab2616e/services/pilot-01/contract.md

S4. Fixtures lues à ce commit : https://github.com/PierreDaguier/service-factory/tree/8d49932b75499c786a39e0f45809feab5ab2616e/services/pilot-01/fixtures

S5. Recherche du dépôt, utilisée comme contexte sans reprendre ses sources commerciales comme règles techniques : https://github.com/PierreDaguier/service-factory/blob/8d49932b75499c786a39e0f45809feab5ab2616e/growth/pilot-01-research.md
