ArchitectureProvisioning turnkey — wizard brand (Figma) + wizard store (pool Shopify)

Provisioning turnkey — wizard brand (Figma) + wizard store (pool Shopify)

Audit + finalisation juin 2026. Ce document décrit le flux COMPLET "je veux lancer un store en quelques minutes" tel qu'il est réellement implémenté, et surtout pourquoi il est implémenté ainsi — chaque contrainte vient…

Audit + finalisation juin 2026. Ce document décrit le flux COMPLET "je veux lancer un store en quelques minutes" tel qu'il est réellement implémenté, et surtout pourquoi il est implémenté ainsi — chaque contrainte vient des plateformes elles-mêmes (Shopify, Figma), pas de nos choix.

Contraintes plateformes (vérifiées, juin 2026)

SouhaitRéalité plateformeRéponse produit
Créer un dev store Shopify par APIImpossible — le Partner API public n'expose aucune mutation devStoreCreate; la création vit dans le Dev Dashboard (UI).Pool pré-provisionné (DevStorePool) : l'ops pré-crée les stores, le wizard en réclame un en quelques millisecondes — plus rapide qu'un appel de création ne le serait.
Transférer la propriété par APIImpossible — transfert déclenché depuis le Dev Dashboard ; le destinataire reçoit un email, accepte sous 7 jours et choisit un plan payant (help.shopify.com).Machine à états (CLAIMED → TRANSFER_REQUESTED → TRANSFERRED) + alerte ops + détection automatique de l'acceptation (poll shop.plan_name : un dev store répond partner_test/affiliate, un vrai plan = transfert accepté).
Connexion auto post-transfert (custom app)La création de custom app n'a pas d'API……mais la custom app installée par l'ops survit au transfert. L'IntegrationConnection créée au claim continue de fonctionner pour le nouveau propriétaire — zéro ré-autorisation. Le problème est résolu en amont, pas après coup.
Cloner notre template Figma par APIImpossible — l'API REST Figma ne duplique pas de fichiers.Template publié sur Figma Community ; duplication en un clic par l'utilisateur ("Open in Figma"), puis il colle l'URL de SA copie.
Écrire dans le fichier Figma par APIImpossible — l'API REST est en lecture seule sur le contenu (l'écriture passe par un plugin dans l'éditeur ; l'API Variables en écriture est Enterprise-only).Le wizard vérifie la copie via l'OAuth de l'utilisateur (preuve qu'elle est dans son compte), lit ses tokens, et exporte le brand kit en design-tokens JSON (W3C) que l'utilisateur importe dans sa copie via un plugin tokens. La mémoire système, elle, est nourrie directement par le wizard.
Installer des apps tierces automatiquementImpossible — toute installation d'app passe par l'OAuth du marchand (anti-abus Shopify).Les milestones "apps" restent des checkpoints guidés (1 clic chacun) ; tout le reste (theme, produits, collections, pages, menus, blog, légal, SEO) est 100 % automatisé.

Wizard brand (Figma)

1. L'utilisateur choisit la source : Prompt | Upload | Figma.
2. Figma → OAuth (scope file_read) via popup
   (/api/connectors/figma/authorize → callback, token chiffré AES-256-GCM).
3. Étape template : GET /api/wizard/brand/template renvoie l'URL
   Community du template BoostEcom (env FIGMA_BRAND_TEMPLATE_URL).
   L'utilisateur l'ouvre, clique "Open in Figma" (duplication 1 clic),
   colle l'URL de sa copie.
4. POST /api/wizard/brand/template :
   - rejette l'URL du template maître (preuve de non-duplication),
   - vérifie la copie via le token OAuth de l'utilisateur,
   - extrait palette + typographies (styles paint/text),
   - persiste la référence fichier dans
     StoreContext.modules.brandKit.figma { fileKey, fileName, verifiedAt }.
5. Les réponses du wizard adaptent le kit (7 étapes, draft IA streamé).
6. Étape résumé : bouton "Figma tokens" → export design-tokens JSON
   (W3C) construit côté client — c'est le pont retour vers la copie
   Figma (import via plugin tokens, ou notre futur plugin BoostEcom).
7. Mémoire : au launch, les faits brand.* / store.* (nom, tagline,
   mission, voix, palette, typo, catégorie, audience, marchés) sont
   persistés en StoreFacts (tags ["brand"], demi-vie 180 j) — chaque
   spécialiste les résout sans re-demander.

Wizard store (création, pas connexion)

1. 6 étapes (identité → positionnement → audience → catalogue →
   marchés → résumé + thème), draft IA streamé, seed depuis le brand
   kit si l'utilisateur vient du wizard brand.
2. Submit :
   a. POST /api/stores                 → Store row.
   b. POST /api/wizard/store/provision → claim atomique d'un dev store
      du pool ; Store.domain + IntegrationConnection active (token pool
      chiffré). Pool vide → "waitlisted" (ops alerté, le flux continue).
   c. POST /api/wizard/store/launch    → spec + brandKit persistés,
      29 milestones (13 auto + 16 checkpoints humains) matérialisés en
      PlatformActivity, mémoire nourrie.
3. Le cron launch-tick (toutes les 15 min, `*/15 * * * *`) exécute les 13 milestones auto en
   respectant le DAG dependsOn : theme (couleurs brand via
   themeFilesUpsert), produits (productSet, DRAFT), collections,
   pages, menus, blog, légal, SEO meta. Tout est idempotent
   (tag boostecom-seed / lookup par handle / dedupeKey).
4. Écran de succès du wizard :
   - lien PREVIEW (storefront derrière la password page Shopify),
   - CTA "Transfer ownership" → POST /api/wizard/store/transfer
     (email résolu côté serveur depuis la session — jamais du client),
   - puis "Open the cockpit" → dashboard.
5. Transfert : l'ops reçoit l'alerte (email + /api/admin/devstore-pool),
   déclenche l'invitation dans le Dev Dashboard ; l'utilisateur accepte
   l'email Shopify (≤ 7 jours) et choisit son plan. Le cron détecte
   l'acceptation (plan_name), marque TRANSFERRED, journalise
   launch.transfer.accepted et envoie l'email "your store is yours"
   avec l'URL du dashboard. Invitation expirée → retour CLAIMED +
   re-demande possible.
6. Post-transfert : RIEN à faire — la custom app du pool survit au
   transfert, la connexion, les agents, les données et le dashboard
   restent actifs pour le nouveau propriétaire.

Surfaces

SurfaceRôle
POST /api/wizard/store/provisionClaim pool → binding Store + connexion
POST/GET /api/wizard/store/transferDemande de transfert / état (polling UI)
POST /api/wizard/store/launchSpec + brand kit + milestones + mémoire
POST /api/wizard/skill/runExécution manuelle d'un milestone (cockpit)
GET /api/cron/launch-tickWorker : milestones (DAG) + détection transferts
GET/POST /api/admin/devstore-pool, DELETE /api/admin/devstore-pool/[id]Inventaire ops du pool (token vérifié via shop.json, plan dev exigé, chiffré at-rest, jamais renvoyé)
GET/POST /api/wizard/brand/templateTemplate Figma : URL publique / vérification de la copie + extraction + persistance

Sécurité

  • Tous les tokens (pool, custom app, Figma access + refresh) sont chiffrés AES-256-GCM at-rest ; les lectures passent par safeDecryptToken / getValidShopifyToken (refresh client_credentials automatique). L'audit de juin 2026 a corrigé trois écritures/lectures plaintext (connect wizard, callback Figma, runner de skills).
  • L'email de transfert est résolu serveur-side depuis la session (un email fourni par le client serait falsifiable).
  • themeUrl (zip de thème) est restreint à une allowlist d'hôtes (github.com / codeload.github.com / cdn.shopify.com).
  • Le pool row est lié au Store par String libre (pas de FK) : la suppression d'un Store ne peut jamais remettre en circulation un dev store déjà transféré.
  • Une ligne CLAIMED ou TRANSFER_REQUESTED passe en RETIRED quand son Store est supprimé (releaseDevStorePoolForStores, appelé par DELETE /api/stores/[storeId] et par la cascade DELETE /api/organizations/[orgId]). L'absence de FK justifiée ci-dessus ne couvrait que le cas TRANSFERRED : pour les deux autres, la ligne restait CLAIMED sur un storeId inexistant — hors du backfill waitlist de launch-tick, et refusée par retireDevStore qui rejette précisément ces deux statuts. Le shop n'était plus joignable depuis aucune surface, et le pool se vidait sans cause affichée.
  • RETIRED, jamais AVAILABLE. Un shop CLAIMED porte déjà le catalogue, le thème et le contenu du marchand qui l'avait réclamé : le remettre en rotation le sert au marchand suivant. Un shop TRANSFER_REQUESTED porte en plus une invitation Shopify que le marchand peut encore accepter pendant sept jours, et qui lui donnerait un store désormais rattaché à quelqu'un d'autre. Le storeId et l'orgId sont conservés (c'est la seule trace de qui a consommé le shop) et la raison est écrite dans notes avec sa date. Retour en rotation = action opérateur : vérifier via shop.json, vider, puis ré-enregistrer. Un statut ORPHANED dédié demanderait une migration d'enum et n'est pas posé ici.

Runbook ops — garder le pool chaud

  1. dev.shopify.com → Stores → Create a dev store (préfixe boostecom-pool-…).
  2. Installer la custom app BoostEcom (scopes Admin complets : products, content, themes, menus, pages, blogs…).
  3. POST /api/admin/devstore-pool { shopDomain, accessToken, notes } — le token est vérifié (GET /shop.json, plan dev exigé) puis chiffré.
  4. Surveiller les alertes "pool EMPTY" (email ADMIN_EMAIL + activité launch.provision.waitlisted) et viser ≥ 3 stores AVAILABLE.
  5. Sur alerte "transfer requested" : Dev Dashboard → Client transfer → Transfer store → email indiqué. Le reste (détection, email de félicitations, dashboard) est automatique.