Dossier d'exemple, non retouché
Kodrudder, par lui-même
Ce dossier a été écrit par Kodrudder. Nous l'affichons tel quel, avec ses hypothèses et les écarts que la relecture a trouvés.
Relevé de cette génération : 177 s, coût estimé 0,54 $.
À lire d'abord
1 hypothèse marquée
Kodrudder transforme une idée en une phrase en un dossier « ready-to-dev » (cadrage, règles, parcours, architecture, SQL, API, tâches) destiné à Cursor, Claude Code, Lovable ou Bolt. Chaque information y est marquée Fait (avec URL) ou H.
Ce qui est décidé
- Lot 1 : générer (Rapide ou Complète), discuter par section, exporter (ZIP + AGENTS.md), payer.
- Plans : gratuit (1 projet, Rapide, BYOK) et Solo à 15 €/mois (quota mensuel, BYOK au-delà).
- Export : AGENTS.md plus un CLAUDE.md d'une ligne qui l'importe.
- Stripe avec Stripe Tax ; base et stockage hébergés dans l'UE ; interface FR/EN.
- Hors lot 1 : GitHub, MCP, mises à jour par différences, agent de codage, équipes.
Ce qui reste une hypothèse à vérifier avant de coder
- Estimation coût/durée (D-02) : tester le ratio estimé/réel sur quelques idées types.
- Marge de Solo (15 €) : chiffrer le coût moyen d'une Complète avant de fixer les quotas.
- TVA/TTC et sorties hors UE vers les fournisseurs IA : validation juridique (DPA, base légale).
Corrections de la relecture
Ces corrections prévalent sur le texte des sections.
- EC-01 : RM-15 : plan
free= 3 Rapides au total ;free_quota_used+1 par génération (transaction, verrou sursubscriptions), restitué si annulée sans section ou échec. - EC-02 : le quota Solo se décompte par utilisateur sur
quota_units(hors générations remboursées ou échouées) depuiscurrent_period_start; plafond 30. - EC-03 : une génération BYOK a
quota_units = 0. - EC-04 : un job (ou évaluation à la lecture) bascule en
freeaprèsgrace_ends_atoucurrent_period_end. - EC-05 : après
DELETE /me, connexion refusée ; purge des données à J+30. - EC-06 : ajouter
GET /modelsetusers.default_model_id. - EC-07 : valider les questions en transaction (3 à 5, 2 options) avant insertion ; sinon relance puis
failed. - EC-08 : ajouter
users.doc_locale_default. - EC-09 : le SQL exporté est extrait des blocs de code de la section « Architecture et données ».
Comment utiliser ce dossier
- Donner à l'outil de codage AGENTS.md (ou CLAUDE.md) et cette page, puis lui faire lire le Cadrage et les règles métier.
- Lui fournir ensuite l'architecture, le SQL et l'API, en lui imposant les corrections ci-dessus.
- Exécuter les tâches du plan dans l'ordre (T-01, T-02…), une par une, en validant chaque « preuve de fin » avant la suivante.
Cadrage
14 hypothèses marquées
Résumé produit
- Kodrudder est un SaaS qui transforme une idée en une phrase en un dossier « ready-to-dev » unique. Il est destiné à Cursor, Claude Code, Lovable et Bolt.
- Le flux : 3 à 5 questions à deux options, puis génération (cadrage, règles, parcours, architecture, SQL, API, tâches). Chaque information est marquée « fait sourcé » ou « hypothèse ».
- Lot 1 : générer, discuter section par section, exporter (ZIP + AGENTS.md), payer. Données hébergées dans l'UE, interface FR/EN.
Utilisateurs et résultat attendu
| ID | Utilisateur | Résultat attendu |
|---|---|---|
| U-01 | Vibecoder non technique | Obtenir un dossier qu'un outil de codage IA peut exécuter sans qu'il écrive de spécification. |
| U-02 | Développeur | Gagner du temps de cadrage et partir de tâches ordonnées, avec faits et hypothèses distingués. |
Décisions tranchées
| ID | Question | Décision retenue | Fait / H |
|---|---|---|---|
| D-01 | Quelle différence entre Rapide et Complète ? | Rapide = 1 appel IA, sections condensées, 1 à 2 min. Complète = génération section par section avec vérification web. | H |
| D-02 | Comment afficher coût et durée avant lancement ? | Estimation en fourchette, calculée à partir de la longueur de l'idée et du modèle choisi. Elle est affichée avant confirmation. | H |
| D-03 | Que se passe-t-il avec une clé d'API personnelle (BYOK) ? | La clé est chiffrée côté serveur, jamais renvoyée au client. Le coût affiché est alors indicatif et facturé par le fournisseur. | H |
| D-04 | Données dans l'UE et modèles IA ? | Base de données et stockage hébergés dans l'UE. Les appels aux modèles sont signalés à l'utilisateur comme pouvant sortir de l'UE. Le catalogue de modèles est à valider avec le responsable produit. | H |
| D-05 | Que contient l'export ? | Le ZIP contient les sections en Markdown, le SQL et AGENTS.md. Il contient aussi un CLAUDE.md d'une ligne qui importe AGENTS.md. Raison : Cursor lit AGENTS.md, alors que Claude Code ne le lit que si aucun CLAUDE.md n'existe (https://codersera.com/blog/agents-md-complete-guide-2026/). | Fait + H |
| D-06 | Paiement et TVA ? | Abonnement Stripe, avec Stripe Tax activé. Raison : la TVA UE s'applique selon la localisation du client (https://docs.stripe.com/tax/digital-products). Le prix de 15 €/mois est TTC pour les particuliers. | Fait + H |
| D-07 | Que couvre le plan gratuit ? | 1 projet, profondeur Rapide uniquement, BYOK autorisé. | H |
| D-08 | Que couvre le plan Solo à 15 €/mois ? | Projets illimités avec quota mensuel de générations. Au-delà, l'utilisateur passe en BYOK. | H |
| D-09 | Langue du dossier généré ? | Même langue que l'interface choisie par l'utilisateur, modifiable par projet. | H |
| D-10 | Authentification ? | E-mail + mot de passe et lien magique. Pas de connexion sociale dans le lot 1. | H |
Périmètre MVP (lot 1)
| ID | Fonction |
|---|---|
| MVP-01 | Saisie de l'idée, puis 3 à 5 questions à deux options |
| MVP-02 | Génération Rapide et Complète, avec coût et durée affichés avant lancement |
| MVP-03 | Dossier en 7 sections, chaque information marquée Fait (avec URL) ou H |
| MVP-04 | Chat IA par section |
| MVP-05 | Choix du modèle et BYOK |
| MVP-06 | Export ZIP + AGENTS.md |
| MVP-07 | Comptes, abonnement gratuit/Solo, interface FR/EN, hébergement UE |
Hors périmètre
| ID | Exclu | Preuve qui justifierait l'ajout H |
|---|---|---|
| HP-01 | Connexion GitHub | Plus de 30 % des exports sont recopiés à la main dans un dépôt |
| HP-02 | Serveur MCP | Demandes répétées d'utilisateurs de Claude Code ou Cursor |
| HP-03 | Mise à jour de la spec par différences | Utilisateurs qui régénèrent un même projet plus de 2 fois |
| HP-04 | Agent de codage | Hors positionnement tant que le dossier n'est pas validé |
| HP-05 | Équipe / multi-utilisateurs | Demandes d'accès partagé de la part d'abonnés payants |
Mesures de succès (H : cibles à calibrer après 30 jours)
| ID | Mesure | Cible |
|---|---|---|
| MS-01 | Idée → dossier exporté | ≥ 50 % des projets créés |
| MS-02 | Durée de génération Rapide | ≤ 2 min dans 90 % des cas |
| MS-03 | Conversion gratuit → Solo | ≥ 5 % à 60 jours |
| MS-04 | Dossier jugé « utilisable tel quel » (question post-export) | ≥ 70 % |
Règles métier et critères d'acceptation
10 hypothèses marquées
Statuts (liste unique)
| Objet | Statuts autorisés |
|---|---|
| Projet | draft → questioning → ready → generating → generated · failed |
| Génération | queued → running → succeeded · failed · canceled |
| Abonnement | free · solo_active · solo_past_due · solo_canceled |
| Clé BYOK | valid · invalid · revoked |
Besoins fonctionnels
| ID | Besoin | MVP |
|---|---|---|
| BE-01 | Créer un compte et se connecter (e-mail + mot de passe, lien magique) | MVP-07 |
| BE-02 | Saisir une idée en une phrase et créer un projet | MVP-01 |
| BE-03 | Répondre à 3 à 5 questions à deux options | MVP-01 |
| BE-04 | Choisir la profondeur (Rapide/Complète) et voir coût + durée avant lancement | MVP-02 |
| BE-05 | Lancer et suivre la génération du dossier en 7 sections | MVP-02, 03 |
| BE-06 | Voir chaque information marquée Fait (avec URL) ou H | MVP-03 |
| BE-07 | Discuter avec l'IA sur une section et appliquer ou rejeter sa proposition | MVP-04 |
| BE-08 | Choisir le modèle IA et enregistrer sa propre clé d'API | MVP-05 |
| BE-09 | Exporter un ZIP contenant AGENTS.md | MVP-06 |
| BE-10 | S'abonner à Solo, gérer ou résilier l'abonnement | MVP-07 |
| BE-11 | Basculer l'interface FR/EN et choisir la langue du dossier | MVP-07 |
| BE-12 | Supprimer un projet et son compte (RGPD) | MVP-07 |
Règles métier
| ID | Règle | Valeur par défaut H | Cas limites |
|---|---|---|---|
| RM-01 | Le questionnaire compte 3 à 5 questions, chacune à exactement 2 options | 4 questions | Génération IA invalide (≠ 2 options, < 3 ou > 5 questions) : 1 relance auto, puis échec failed avec message ; idée vide ou < 10 caractères : refusée |
| RM-02 | Un plan free a 1 projet actif maximum, profondeur Rapide uniquement | 1 projet | Création d'un 2ᵉ projet refusée avec invitation à passer Solo ; suppression du projet libère le quota ; retour à free avec plusieurs projets : lecture et export gardés, génération bloquée |
| RM-03 | Le plan Solo (15 €/mois) autorise projets illimités et un quota mensuel de générations | 30 Rapide ou 10 Complète par mois, remis à zéro à la date anniversaire | Quota épuisé : proposition de BYOK ; génération échouée non décomptée ; une Complète compte pour 3 Rapides |
| RM-04 | Coût et durée estimés sont affichés avant lancement, la confirmation explicite est obligatoire | Fourchette min–max | Changement de modèle ou de profondeur : recalcul avant confirmation ; en BYOK, coût marqué « indicatif, facturé par le fournisseur » ; estimation indisponible : lancement bloqué |
| RM-05 | Une seule génération queued/running par projet | — | Double clic ou 2 onglets : la 2ᵉ requête est refusée (409), pas de double décompte ; clé d'idempotence par requête |
| RM-06 | Génération échouée ou annulée : reprise possible sans perte des sections déjà produites | Reprise à la 1ʳᵉ section manquante | Timeout fournisseur : 2 tentatives puis failed ; annulation utilisateur : état canceled, crédit restitué si aucune section produite |
| RM-07 | Toute information du dossier porte un marqueur Fait (avec URL) ou H ; un Fait sans URL est requalifié en H | — | URL inaccessible à la vérification (Complète) : requalifiée en H ; Rapide : sans vérification web, les faits sans URL deviennent H |
| RM-08 | Le chat est rattaché à une section ; une proposition de l'IA n'est appliquée qu'après acceptation | Historique conservé par section | Modification concurrente de la section : la version la plus récente gagne, l'ancienne est conservée 1 version ; quota de messages atteint : message d'erreur explicite |
| RM-09 | La clé BYOK est chiffrée côté serveur, jamais renvoyée au client ni journalisée | Test de validité à l'enregistrement | Clé invalide : invalid, génération bloquée ; clé révoquée en cours de génération : arrêt failed, aucune bascule vers la clé de la plateforme sans accord |
| RM-10 | L'export ZIP contient les 7 sections en Markdown, le SQL, AGENTS.md et un CLAUDE.md d'une ligne important AGENTS.md | — | Projet non generated : export refusé ; section en cours de régénération : export refusé ; noms de fichiers sûrs (pas de chemin absolu ni de ..) |
| RM-11 | L'accès à un projet est limité à son propriétaire | — | Accès à l'ID d'un autre utilisateur : 404 ; session expirée : redirection vers la connexion en conservant la saisie |
| RM-12 | Abonnement Stripe avec Stripe Tax ; l'état local suit les webhooks Stripe | — | Webhook dupliqué ou hors ordre : traitement idempotent ; paiement refusé : solo_past_due avec 7 jours de grâce puis retour à free ; résiliation : accès Solo jusqu'à la fin de période |
| RM-13 | Les données sont stockées dans l'UE ; l'utilisateur est averti que les appels aux modèles peuvent en sortir | Bandeau à la première génération | Sans acceptation de l'avertissement : génération bloquée |
| RM-14 | La suppression de compte efface projets, clés et historiques sous 30 jours ; l'abonnement actif est résilié d'abord | 30 jours | Abonnement solo_active : résiliation demandée avant suppression |
Table de décision : autorisation de lancer une génération (RM-02, 03, 04, 09, 13)
| Plan | Profondeur | Clé | Quota restant | Estimation confirmée | Avertissement UE accepté | Résultat |
|---|---|---|---|---|---|---|
| free | Complète | — | — | — | — | Refusé : Solo requis |
| free | Rapide | plateforme | — | oui | oui | Autorisé (hors 1ʳᵉ génération gratuite : quota gratuit de 3 Rapides, H) |
| free | Rapide | BYOK valid | — | oui | oui | Autorisé |
| solo | Rapide/Complète | plateforme | oui | oui | oui | Autorisé |
| solo | Rapide/Complète | plateforme | non | — | — | Refusé : proposer BYOK |
| solo | Rapide/Complète | BYOK valid | non requis | oui | oui | Autorisé |
| tout | tout | BYOK invalid/revoked | — | — | — | Refusé : corriger la clé |
| tout | tout | tout | tout | non | tout | Refusé : confirmation requise |
| tout | tout | tout | tout | oui | non | Refusé : accepter l'avertissement |
solo_past_due après grâce | tout | plateforme | — | — | — | Traité comme free |
Critères d'acceptation
| ID | Étant donné | Quand | Alors | BE | RM |
|---|---|---|---|---|---|
| CA-01 | Une idée vide ou de 5 caractères | L'utilisateur valide | Erreur « idée trop courte », aucun projet créé | BE-02 | RM-01 |
| CA-02 | Une idée valide | Le projet passe en questioning | 3 à 5 questions sont affichées, chacune avec exactement 2 options | BE-03 | RM-01 |
| CA-03 | L'IA renvoie 2 fois un questionnaire invalide | La relance échoue | Projet failed, message d'erreur, possibilité de réessayer | BE-03 | RM-01 |
| CA-04 | Un compte free avec 1 projet | Il crée un 2ᵉ projet | Refus (403) avec lien vers l'abonnement Solo | BE-02 | RM-02 |
| CA-05 | Un compte free | Il sélectionne la profondeur Complète | L'option est désactivée et renvoie à Solo | BE-04 | RM-02 |
| CA-06 | Un compte Solo sans quota restant, sans BYOK | Il lance une génération | Refus avec proposition d'ajouter une clé BYOK | BE-05 | RM-03 |
| CA-07 | Un projet ready | L'utilisateur change de modèle | Coût et durée sont recalculés avant confirmation | BE-04, 08 | RM-04 |
| CA-08 | Une clé BYOK valid sélectionnée | L'estimation s'affiche | Elle porte la mention « indicatif, facturé par le fournisseur » | BE-04 | RM-04 |
| CA-09 | Une estimation non confirmée | Le lancement est tenté via l'API | Refus 422, aucune génération créée | BE-04 | RM-04 |
| CA-10 | Une génération running | Une 2ᵉ requête de lancement arrive | Réponse 409, un seul décompte de quota | BE-05 | RM-05 |
| CA-11 | Une génération échouée après 3 sections | L'utilisateur relance | Reprise à la 4ᵉ section, sections 1 à 3 conservées | BE-05 | RM-06 |
| CA-12 | Une génération annulée avant toute section | Annulation confirmée | Statut canceled, crédit restitué | BE-05 | RM-06 |
| CA-13 | Un dossier généré | L'utilisateur le consulte | Aucune information sans marqueur Fait/H ; chaque Fait a une URL | BE-06 | RM-07 |
| CA-14 | Un Fait sans URL dans la sortie IA | Le dossier est enregistré | Il est stocké comme H | BE-06 | RM-07 |
| CA-15 | Une section ouverte dans le chat | L'IA propose une modification | La section ne change qu'après clic sur « Appliquer » ; « Rejeter » la laisse intacte | BE-07 | RM-08 |
| CA-16 | Deux modifications concurrentes sur une section | Elles sont enregistrées | La plus récente est conservée, la version précédente est restaurable | BE-07 | RM-08 |
| CA-17 | Une clé API invalide saisie | L'utilisateur l'enregistre | Statut invalid, génération bloquée avec message | BE-08 | RM-09 |
| CA-18 | Une clé BYOK enregistrée | Toute réponse API et tout journal sont inspectés | La clé en clair n'y apparaît jamais | BE-08 | RM-09 |
| CA-19 | Une clé révoquée en cours de génération | Le fournisseur renvoie une erreur d'authentification | Génération failed, aucune bascule vers la clé de la plateforme | BE-05, 08 | RM-09 |
| CA-20 | Un projet generated | L'utilisateur exporte | Le ZIP contient 7 sections .md, le SQL, AGENTS.md et un CLAUDE.md important AGENTS.md | BE-09 | RM-10 |
| CA-21 | Un projet generating | L'export est demandé | Refus avec message explicite | BE-09 | RM-10 |
| CA-22 | Un utilisateur A | Il appelle l'ID d'un projet de B | Réponse 404 | BE-02 | RM-11 |
| CA-23 | Un abonnement solo_active | Le webhook de paiement refusé arrive | Statut solo_past_due, accès Solo conservé 7 jours puis retour à free | BE-10 | RM-12 |
| CA-24 | Un même webhook Stripe reçu 2 fois | Le second est traité | Aucun effet supplémentaire sur l'état | BE-10 | RM-12 |
| CA-25 | Un abonné qui résilie | La résiliation est confirmée | Statut solo_canceled, accès Solo jusqu'à la fin de période puis free | BE-10 | RM-12 |
| CA-26 | Un utilisateur n'ayant jamais lancé de génération | Il en lance une | L'avertissement UE est affiché ; sans acceptation, lancement refusé | BE-05 | RM-13 |
| CA-27 | Un utilisateur FR change la langue en EN | Il actualise l'interface | Tous les libellés passent en anglais ; la langue du dossier reste celle du projet | BE-11 | — |
| CA-28 | Un compte solo_active | Il demande la suppression du compte | La suppression est refusée tant que l'abonnement n'est pas résilié | BE-12 | RM-14 |
| CA-29 | Un compte sans abonnement actif | Il confirme la suppression | Les projets, clés et historiques sont purgés sous 30 jours | BE-12 | RM-14 |
| CA-30 | Un compte free ayant épuisé son quota gratuit de 3 Rapides | Il lance une 4ᵉ génération sans BYOK | Refus avec invitation à passer Solo ou à ajouter une clé BYOK | BE-05 | RM-02, RM-03 |
Questions ouvertes (H à valider)
| ID | Question | Hypothèse retenue |
|---|---|---|
| Q-01 | Quota mensuel Solo exact et quota gratuit de 3 Rapides | 30 Rapide ou 10 Complète ; 3 Rapides gratuites (RM-03, RM-02) |
| Q-02 | Délai de grâce après impayé | 7 jours (RM-12) |
| Q-03 | Durée de rétention après suppression | 30 jours (RM-14) |
Parcours et écrans
7 hypothèses marquées
Parcours principaux
P-01 — Vibecoder / développeur : de l'idée au dossier exporté (U-01, U-02)
- S'inscrire ou se connecter (e-mail + mot de passe, ou lien magique).
- Saisir l'idée en une phrase et valider (≥ 10 caractères, RM-01).
- Répondre aux 3 à 5 questions à deux options, une par une.
- Choisir la profondeur (Rapide / Complète) et le modèle ; lire coût et durée estimés.
- Accepter l'avertissement UE (1ʳᵉ génération), puis confirmer le lancement.
- Suivre la progression section par section ; annuler possible.
- Lire le dossier : sections 1 à 7, marqueurs Fait (URL) / H.
- Discuter d'une section avec l'IA, puis « Appliquer » ou « Rejeter ».
- Exporter le ZIP (AGENTS.md inclus).
P-02 — Clé BYOK et choix du modèle (U-02)
- Ouvrir Réglages > Clés d'API.
- Choisir le fournisseur, coller la clé, enregistrer.
- Voir le résultat du test (
valid/invalid) ; révoquer possible. - Sélectionner le modèle au moment du lancement ; voir la mention « indicatif ».
P-03 — Abonnement Solo (U-01, U-02)
- Atteindre une limite (2ᵉ projet, Complète, quota) ou ouvrir Abonnement.
- Consulter la comparaison Gratuit / Solo (15 €/mois).
- Payer via Stripe Checkout, puis retour sur l'app avec statut mis à jour.
- Gérer ou résilier via le portail Stripe.
P-04 — Suppression de compte (RGPD)
- Réglages > Compte > Supprimer.
- Si
solo_active: résilier d'abord (RM-14). - Confirmer en saisissant « SUPPRIMER » ; message de purge sous 30 jours.
Écrans
| ID | But | Éléments clés | États à gérer | CA |
|---|---|---|---|---|
| EC-01 | Connexion / inscription | Champs e-mail et mot de passe, lien magique, sélecteur FR/EN | Erreur d'identifiants, lien envoyé, lien expiré, chargement | CA-27 |
| EC-02 | Liste des projets | Cartes (nom, statut, date), bouton « Nouveau projet », jauge de quota | Vide (onboarding), chargement, erreur réseau, limite free atteinte (CTA Solo) | CA-04 |
| EC-03 | Saisie de l'idée | Champ texte avec compteur, bouton « Continuer » | Idée trop courte, envoi en cours, échec IA avec réessai | CA-01 |
| EC-04 | Questionnaire | Une question par vue, 2 options (boutons radio), progression « 2/4 », retour | Chargement des questions, questionnaire invalide → failed + réessayer, reprise après rechargement | CA-02, CA-03 |
| EC-05 | Lancement | Choix Rapide/Complète, sélecteur de modèle, estimation coût/durée, case d'avertissement UE, bouton « Lancer » | Complète désactivée en free (renvoi Solo), estimation en recalcul/indisponible (lancement bloqué), clé invalid, quota épuisé (proposer BYOK), mention « indicatif » en BYOK | CA-05, 06, 07, 08, 09, 26, 30 |
| EC-06 | Suivi de génération | Liste des 7 sections avec statut, durée écoulée, bouton « Annuler » | queued, running, failed avec « Reprendre », canceled, 409 si double lancement, clé révoquée | CA-10, 11, 12, 19 |
| EC-07 | Dossier (lecture) | Sommaire des 7 sections, rendu Markdown/tableaux, badges Fait/H avec URL cliquable, bouton « Exporter » | Section en régénération, export refusé hors generated | CA-13, 14, 20, 21 |
| EC-08 | Chat de section | Panneau latéral, historique, proposition IA en aperçu, boutons « Appliquer » / « Rejeter » | Envoi en cours, quota de messages atteint, erreur IA, conflit de version (restaurer la précédente) | CA-15, 16 |
| EC-09 | Réglages : clés et modèle | Liste des clés avec statut, ajout, révocation, modèle par défaut | Clé masquée après saisie, test en cours, invalid/revoked | CA-17, 18 |
| EC-10 | Abonnement | Plan actuel, comparatif, bouton Stripe, date de fin de période | free, solo_active, solo_past_due (bandeau 7 jours), solo_canceled, retour de paiement en attente | CA-23, 24, 25 |
| EC-11 | Réglages : compte | Langue de l'interface, langue du dossier par défaut, suppression | Suppression bloquée si solo_active, confirmation par saisie | CA-27, 28, 29 |
| EC-12 | Erreurs transverses | Pages 404 (projet d'autrui), session expirée | Redirection vers EC-01 avec saisie conservée | CA-22 |
Règles d'accessibilité et mobile
- Clavier : tout parcours P-01 réalisable sans souris ; ordre de focus logique, focus visible, aucun piège (WCAG 2.2, https://www.w3.org/WAI/WCAG22/quickref/).
- Erreurs et états : message textuel associé au champ, annoncé aux lecteurs d'écran ; la couleur ne porte jamais seule l'information, y compris pour Fait / H (texte ou icône + libellé).
- Progression asynchrone : le suivi de génération (EC-06) annonce les changements de statut via une région live (H :
aria-live="polite"). - Cibles tactiles : ≥ 44 × 44 px H pour options du questionnaire et boutons de confirmation.
- Mobile first : EC-03 à EC-06 utilisables à 360 px de large ; le chat (EC-08) s'ouvre en plein écran sur mobile, en panneau latéral sur desktop H.
- Actions destructrices et coûteuses : confirmation explicite (lancement, annulation, suppression) ; libellés FR/EN sans texte codé en dur dans les images.
Limite : ces règles sont des exigences de conception ; la conformité WCAG reste à tester sur le rendu final (H : niveau AA visé).
Architecture et données
2 hypothèses marquées
Stack retenue
| Couche | Choix | Raison | Alternative écartée |
|---|---|---|---|
| Front + API | Next.js (TypeScript), i18n FR/EN | Un seul dépôt, SSR, adapté aux outils de codage IA H | SPA React + API séparée : deux déploiements pour un MVP |
| Base de données | PostgreSQL managé, région UE | RM-13, transactions et contraintes pour RM-02/05 | NoSQL : invariants multi-tables difficiles à garantir |
| Authentification | Auth intégrée (e-mail + lien magique), sessions serveur | D-10 | Connexion sociale : hors lot 1 |
| Génération | Worker asynchrone + file en base (generations) | Génération de 1 à plusieurs minutes, reprise RM-06 | Appel synchrone : timeouts HTTP |
| Appels IA | Couche d'abstraction multi-fournisseurs | BE-08 : choix du modèle et BYOK | Fournisseur unique : pas de choix de modèle |
| Secrets BYOK | Chiffrement applicatif AES-256-GCM, clé maître en KMS UE | RM-09 | Stockage en clair : interdit |
| Paiement | Stripe Billing + Stripe Tax, webhooks | D-06, RM-12 | Paiement maison : conformité TVA |
| Export | ZIP généré à la volée côté serveur | RM-10, pas de stockage de fichiers | Stockage objet : inutile au lot 1 |
Composants
- Web : interface FR/EN, parcours idée → questions → estimation → dossier → export.
- API : authentification, projets, génération, chat, clés, export, webhooks Stripe.
- Worker : exécute les générations (
queued→running), enregistre les sections, applique RM-06/07. - Connecteur IA : appelle les modèles choisis, plateforme ou BYOK.
- Facturation : synchronise l'abonnement local depuis les webhooks (idempotents).
- Moteur de quotas : calcule le décompte (Complète = 3 Rapides, RM-03).
Modèle de données (PostgreSQL)
CREATE EXTENSION IF NOT EXISTS pgcrypto;
CREATE TABLE users (
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
email text NOT NULL,
password_hash text, -- NULL si lien magique uniquement
ui_locale text NOT NULL DEFAULT 'fr' CHECK (ui_locale IN ('fr','en')), -- BE-11
eu_notice_accepted_at timestamptz, -- RM-13 : NULL = génération bloquée
deletion_requested_at timestamptz, -- RM-14
created_at timestamptz NOT NULL DEFAULT now()
);
CREATE UNIQUE INDEX users_email_uq ON users (lower(email));
CREATE TABLE subscriptions (
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
user_id uuid NOT NULL UNIQUE REFERENCES users(id) ON DELETE CASCADE,
status text NOT NULL DEFAULT 'free'
CHECK (status IN ('free','solo_active','solo_past_due','solo_canceled')),
stripe_customer_id text UNIQUE,
stripe_subscription_id text UNIQUE,
current_period_start timestamptz, -- RM-03 : date anniversaire
current_period_end timestamptz, -- RM-12 : accès jusqu'à la fin de période
grace_ends_at timestamptz, -- RM-12 : 7 jours après impayé
free_quota_used integer NOT NULL DEFAULT 0
CHECK (free_quota_used BETWEEN 0 AND 3), -- RM-02 : 3 Rapides gratuites (H)
updated_at timestamptz NOT NULL DEFAULT now()
);
-- RM-12 : idempotence des webhooks Stripe (CA-24)
CREATE TABLE stripe_events (
event_id text PRIMARY KEY,
type text NOT NULL,
processed_at timestamptz NOT NULL DEFAULT now()
);
CREATE TABLE ai_models (
id text PRIMARY KEY, -- ex. identifiant catalogue
provider text NOT NULL,
label text NOT NULL,
input_price_per_mtok numeric(10,4) NOT NULL CHECK (input_price_per_mtok >= 0),
output_price_per_mtok numeric(10,4) NOT NULL CHECK (output_price_per_mtok >= 0),
active boolean NOT NULL DEFAULT true
);
CREATE TABLE byok_keys (
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
user_id uuid NOT NULL REFERENCES users(id) ON DELETE CASCADE,
provider text NOT NULL,
key_cipher bytea NOT NULL, -- RM-09 : chiffré, jamais renvoyé ni journalisé
key_last4 text NOT NULL, -- seul élément affichable
status text NOT NULL DEFAULT 'valid'
CHECK (status IN ('valid','invalid','revoked')),
created_at timestamptz NOT NULL DEFAULT now(),
UNIQUE (user_id, provider)
);
CREATE TABLE projects (
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
owner_id uuid NOT NULL REFERENCES users(id) ON DELETE CASCADE, -- RM-11
name text NOT NULL,
idea text NOT NULL CHECK (char_length(btrim(idea)) >= 10), -- RM-01 : idée >= 10 car.
doc_locale text NOT NULL DEFAULT 'fr' CHECK (doc_locale IN ('fr','en')), -- D-09
status text NOT NULL DEFAULT 'draft'
CHECK (status IN ('draft','questioning','ready','generating','generated','failed')),
created_at timestamptz NOT NULL DEFAULT now(),
updated_at timestamptz NOT NULL DEFAULT now()
);
CREATE INDEX projects_owner_idx ON projects (owner_id, created_at DESC);
CREATE TABLE questions (
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
project_id uuid NOT NULL REFERENCES projects(id) ON DELETE CASCADE,
position smallint NOT NULL CHECK (position BETWEEN 1 AND 5), -- RM-01 : 3 à 5 questions
prompt text NOT NULL,
option_a text NOT NULL, -- RM-01 : exactement 2 options (deux colonnes NOT NULL)
option_b text NOT NULL,
answer text CHECK (answer IN ('a','b')),
UNIQUE (project_id, position),
CHECK (option_a <> option_b)
);
-- Le minimum de 3 questions est vérifié par l'application avant le passage en `questioning`.
CREATE TABLE generations (
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
project_id uuid NOT NULL REFERENCES projects(id) ON DELETE CASCADE,
depth text NOT NULL CHECK (depth IN ('rapide','complete')),
model_id text NOT NULL REFERENCES ai_models(id),
key_source text NOT NULL CHECK (key_source IN ('platform','byok')),
byok_key_id uuid REFERENCES byok_keys(id) ON DELETE SET NULL,
status text NOT NULL DEFAULT 'queued'
CHECK (status IN ('queued','running','succeeded','failed','canceled')),
idempotency_key text NOT NULL, -- RM-05
estimate_min_eur numeric(8,2) NOT NULL, -- RM-04 : estimation obligatoire
estimate_max_eur numeric(8,2) NOT NULL,
estimate_min_sec integer NOT NULL,
estimate_max_sec integer NOT NULL,
estimate_confirmed_at timestamptz NOT NULL, -- RM-04 : pas de génération sans confirmation (CA-09)
quota_units smallint NOT NULL CHECK (quota_units IN (0,1,3)), -- RM-03 : Rapide=1, Complète=3
quota_refunded boolean NOT NULL DEFAULT false, -- RM-06 : crédit restitué
attempts smallint NOT NULL DEFAULT 0 CHECK (attempts <= 2), -- RM-06 : 2 tentatives
error_message text,
created_at timestamptz NOT NULL DEFAULT now(),
finished_at timestamptz,
CHECK (key_source = 'platform' OR byok_key_id IS NOT NULL),
CHECK (estimate_min_eur <= estimate_max_eur AND estimate_min_sec <= estimate_max_sec),
UNIQUE (project_id, idempotency_key)
);
-- RM-05 : une seule génération queued/running par projet (CA-10 -> 409)
CREATE UNIQUE INDEX generations_one_active_uq
ON generations (project_id) WHERE status IN ('queued','running');
-- RM-03 : décompte mensuel par utilisateur via project -> owner
CREATE INDEX generations_project_created_idx ON generations (project_id, created_at DESC);
CREATE TABLE sections (
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
project_id uuid NOT NULL REFERENCES projects(id) ON DELETE CASCADE,
position smallint NOT NULL CHECK (position BETWEEN 1 AND 7), -- 7 sections du dossier
slug text NOT NULL,
title text NOT NULL,
content_md text NOT NULL,
version integer NOT NULL DEFAULT 1, -- RM-08 : version courante
generation_id uuid REFERENCES generations(id) ON DELETE SET NULL, -- RM-06 : reprise à la 1re manquante
updated_at timestamptz NOT NULL DEFAULT now(),
UNIQUE (project_id, position),
UNIQUE (project_id, slug)
);
-- RM-08 : version précédente restaurable (CA-16)
CREATE TABLE section_versions (
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
section_id uuid NOT NULL REFERENCES sections(id) ON DELETE CASCADE,
version integer NOT NULL,
content_md text NOT NULL,
created_at timestamptz NOT NULL DEFAULT now(),
UNIQUE (section_id, version)
);
-- RM-07 : toute information porte un marqueur ; un Fait exige une URL (CA-13, CA-14)
CREATE TABLE claims (
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
section_id uuid NOT NULL REFERENCES sections(id) ON DELETE CASCADE,
text text NOT NULL,
kind text NOT NULL CHECK (kind IN ('fact','hypothesis')),
source_url text,
CHECK (kind = 'hypothesis' OR (source_url IS NOT NULL AND source_url ~ '^https?://'))
);
CREATE INDEX claims_section_idx ON claims (section_id);
CREATE TABLE chat_messages (
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
section_id uuid NOT NULL REFERENCES sections(id) ON DELETE CASCADE, -- RM-08 : chat par section
role text NOT NULL CHECK (role IN ('user','assistant')),
content text NOT NULL,
proposal_md text, -- proposition de l'IA
proposal_status text CHECK (proposal_status IN ('pending','applied','rejected')), -- appliquée seulement après acceptation
created_at timestamptz NOT NULL DEFAULT now(),
CHECK (proposal_md IS NOT NULL OR proposal_status IS NULL)
);
CREATE INDEX chat_messages_section_idx ON chat_messages (section_id, created_at);
-- RM-02 : 1 projet actif maximum en free : contrôle applicatif en transaction
-- (verrou sur subscriptions) car la règle dépend du plan, non exprimable en contrainte simple.
Notes de cohérence
- Statuts alignés sur la section 2 : projet, génération, abonnement, clé BYOK.
- Hypothèses H : quota gratuit de 3 Rapides et grâce de 7 jours (Q-01, Q-02) ; dates anniversaire dérivées de
current_period_start. - Les règles RM-02 (limite de projets), RM-13 (avertissement UE) et RM-14 (purge sous 30 jours via
deletion_requested_atet un job planifié) sont appliquées côté application ; les contraintes SQL couvrent les autres invariants.
API, intégrations et sécurité
9 hypothèses marquées
API (REST JSON, préfixe /api/v1, session cookie, CSRF actif)
Rôles : anon (non connecté), owner (propriétaire du projet), user (connecté). Tout accès à un projet d'autrui renvoie 404 (RM-11, CA-22). Erreurs communes : 401 (session expirée), 404, 429 (limite de débit, H).
| Méthode | Chemin | Rôle | Entrée | Sortie | Erreurs | RM / CA |
|---|---|---|---|---|---|---|
| POST | /auth/signup | anon | email, password, ui_locale | 201 user | 409 e-mail pris, 422 | RM-11 · CA-27 |
| POST | /auth/login · /auth/magic-link | anon | email, password ou email | 200 session / 202 lien envoyé | 401, 410 lien expiré | RM-11 |
| GET | /projects | user | — | liste + quota restant | 401 | RM-02, 03 |
| POST | /projects | user | idea, doc_locale | 201 project draft | 422 idée < 10 car., 403 free avec 1 projet | RM-01, 02 · CA-01, 04 |
| POST | /projects/{id}/questions | owner | — | 201 3 à 5 questions, projet questioning | 502 (2 échecs IA → failed), 409 déjà générées | RM-01 · CA-02, 03 |
| PUT | /projects/{id}/answers | owner | [{question_id, answer a/b}] | 200 projet ready | 422 réponses incomplètes | RM-01 · CA-02 |
| POST | /projects/{id}/estimate | owner | depth, model_id, key_source | 200 min/max € et s, drapeau indicative (BYOK) | 403 Complète en free, 422 estimation indisponible | RM-02, 04 · CA-05, 07, 08 |
| PUT | /me/eu-notice | user | accepted=true | 204 (pose eu_notice_accepted_at) | — | RM-13 · CA-26 |
| POST | /projects/{id}/generations | owner | depth, model_id, key_source, byok_key_id?, header Idempotency-Key, confirmed_estimate | 202 generation queued | 409 génération active, 422 non confirmée, 403 refus table de décision (plan, quota, clé invalid/revoked, avertissement UE non accepté) | RM-02 à 05, 09, 13 · CA-06, 09, 10, 26, 30 |
| GET | /projects/{id}/generations/{gid} | owner | — | statut, sections produites, erreur | — | RM-06 · CA-11 |
| POST | /projects/{id}/generations/{gid}/cancel | owner | — | 200 canceled, crédit restitué si 0 section | 409 déjà terminée | RM-06 · CA-12 |
| POST | /projects/{id}/generations/{gid}/resume | owner | Idempotency-Key | 202 reprise à la 1ʳᵉ section manquante | 409, 403 quota/clé | RM-05, 06 · CA-11 |
| GET | /projects/{id} · /sections/{sid} | owner | — | projet, sections, claims (fact+URL / hypothesis) | 404 | RM-07, 11 · CA-13, 22 |
| GET | /sections/{sid}/chat | owner | — | historique | 404 | RM-08 |
| POST | /sections/{sid}/chat | owner | message | 201 réponse + proposal_md (pending) | 429 quota de messages, 502 erreur IA | RM-08 · CA-15 |
| POST | /chat-messages/{mid}/apply · /reject | owner | base_version | 200 section v+1 (ancienne dans section_versions) / 200 inchangée | 409 version périmée (restauration proposée) | RM-08 · CA-15, 16 |
| POST | /sections/{sid}/restore | owner | version | 200 | 404 | RM-08 · CA-16 |
| GET | /me/keys | user | — | liste (provider, key_last4, status) | — | RM-09 · CA-18 |
| POST | /me/keys | user | provider, key | 201 valid ou invalid (test à l'enregistrement) | 422 | RM-09 · CA-17, 18 |
| DELETE | /me/keys/{kid} | user | — | 204 (revoked) | 404 | RM-09 |
| GET | /projects/{id}/export | owner | — | ZIP : 7 sections .md, SQL, AGENTS.md, CLAUDE.md | 409 projet non generated ou section en régénération | RM-10 · CA-20, 21 |
| POST | /billing/checkout · /billing/portal | user | — | URL Stripe | 409 déjà Solo | RM-12 · CA-25 |
| POST | /webhooks/stripe | Stripe (signature) | événement | 200 | 400 signature invalide ; doublon : 200 sans effet | RM-12 · CA-23, 24 |
| PATCH | /me | user | ui_locale | 200 | 422 | BE-11 · CA-27 |
| DELETE | /me | user | confirmation « SUPPRIMER » | 202 purge sous 30 jours | 409 solo_active (résilier d'abord) | RM-14 · CA-28, 29 |
| DELETE | /projects/{id} | owner | — | 204 | 404 | RM-02 |
Intégrations externes
| Service | Usage | Point de défaillance | Comportement de repli |
|---|---|---|---|
| Fournisseurs IA (plateforme ou BYOK) | Questions, sections, chat | Timeout, 429, erreur d'authentification | 2 tentatives puis failed (RM-06) ; clé révoquée : failed sans bascule vers la clé plateforme (RM-09, CA-19) ; reprise possible |
| Stripe Billing + Tax | Abonnement Solo, TVA | Indisponibilité, webhooks dupliqués ou hors ordre | Idempotence via stripe_events ; état local conservé ; solo_past_due : grâce 7 j H puis free ; Checkout indisponible : message et réessai |
| Vérification d'URL des Faits (Complète) | Contrôle d'accessibilité | Site lent ou inaccessible | Fait requalifié en H (RM-07) ; jamais bloquant |
| E-mail transactionnel (UE, H) | Lien magique | Échec d'envoi | Bouton « Renvoyer » ; connexion par mot de passe toujours possible |
| KMS UE | Clé maître de chiffrement BYOK | Indisponible | Enregistrement et usage des clés BYOK refusés ; génération plateforme non affectée |
Sécurité et données personnelles
- Authentification : mots de passe hachés (argon2id, H), cookie
HttpOnly/Secure/SameSite=Lax, lien magique à usage unique expirant sous 15 min H, limitation de débit sur la connexion. - Autorisation : chaque requête filtre par
owner_id(RM-11) ; refus renvoyé en 404 pour ne pas révéler l'existence du projet. - Clés BYOK : AES-256-GCM, clé maître en KMS UE ; la clé en clair n'apparaît ni dans les réponses (seul
key_last4) ni dans les journaux (masquage systématique, test CA-18) ; l'appel au fournisseur se fait côté serveur uniquement (RM-09). - Entrées et sorties : validation de schéma sur chaque endpoint ; contenu IA rendu en Markdown assaini (pas de HTML brut) ; noms de fichiers du ZIP normalisés, sans
..ni chemin absolu (RM-10) ; secrets Stripe vérifiés par signature de webhook. - Protection contre les abus :
Idempotency-Keyobligatoire pour lancer ou reprendre ; index unique des générations actives (RM-05) ; plafond de messages de chat et de débit par utilisateur H. - RGPD : données stockées dans l'UE ; avertissement avant la 1ʳᵉ génération sur la sortie possible des données vers les fournisseurs IA (RM-13) ; suppression du compte et purge de projets, clés et historiques sous 30 jours (RM-14) ; journaux sans idée de projet ni contenu de dossier H.
Limite : les valeurs marquées H sont à confirmer ; aucune mesure de charge n'a été réalisée.
Plan de développement
4 hypothèses marquées
Conventions : TS strict, Next.js, PostgreSQL. Chaque tâche livre du code + tests. Ordre = dépendances. Les corrections « corrige EC-xx » sont intégrées dans les tâches (dont ajout de RM-15 : plan free = 3 Rapides au total, free_quota_used +1 par génération, en transaction avec verrou sur subscriptions, restitué si annulée sans section ou échec ; BYOK = quota_units = 0).
Jalon J1 — Socle, comptes, projets, questionnaire (MVP-01, 07 partiel)
| ID | Objectif | Modules / fichiers | Dépend de | CA | Preuve de fin | Piège à éviter |
|---|---|---|---|---|---|---|
| T-01 | Initialiser Next.js TS, lint, i18n FR/EN, CI, config env | app/, i18n/, .github/ | — | CA-27 | pnpm build && pnpm test verts ; bascule FR/EN testée | Libellés en dur : tout passe par les clés i18n |
| T-02 | Migrations SQL complètes de §4 + seed ai_models ; corrige EC-06/08 : ajouter users.default_model_id (FK ai_models) et users.doc_locale_default (fr/en) | db/migrations/001_init.sql, db/seed.sql | T-01 | CA-13, 14, 16 | pnpm db:migrate sur base vide ; insertion Fait sans URL rejetée par CHECK | Oublier l'index unique partiel generations_one_active_uq |
| T-03 | Auth e-mail + mot de passe (argon2id), lien magique (15 min, usage unique), sessions HttpOnly/Secure/SameSite, CSRF, rate limit ; connexion refusée si deletion_requested_at non nul (corrige EC-05) | api/auth/*, lib/session.ts | T-02 | CA-22, 27 | Tests : login OK/KO, lien expiré 410, compte en suppression refusé | Fuite d'existence d'un e-mail dans les messages d'erreur |
| T-04 | Garde d'autorisation requireOwner (filtre owner_id, 404 si autrui) | lib/authz.ts | T-03 | CA-22 | Test : user A sur projet de B → 404 | Renvoyer 403 au lieu de 404 |
| T-05 | POST/GET/DELETE /projects : idée ≥ 10 car. ; limite 1 projet free en transaction avec verrou subscriptions | api/projects/* | T-04 | CA-01, 04 | Tests : idée de 5 car. → 422 ; 2ᵉ projet free → 403 ; suppression libère le quota | Course : deux créations simultanées dépassant le quota |
| T-06 | Abstraction fournisseurs IA (interface complete(), timeouts, 2 tentatives, erreurs typées auth/429/timeout) + mock déterministe | lib/ai/* | T-01 | CA-03, 19 | Tests avec mock : timeout → 2 essais puis erreur typée | Journaliser le prompt ou la clé |
| T-07 | POST /projects/{id}/questions ; corrige EC-07 : validation en transaction (3 à 5 questions, 2 options distinctes) avant insertion ; relance auto 1 fois, puis projet failed ; PUT /answers → ready | api/projects/[id]/questions, lib/questionnaire.ts | T-05, T-06 | CA-02, 03 | Test : mock invalide ×2 → failed, rien inséré ; mock valide → questioning | Insertion partielle de questions après échec de validation |
| T-08 | UI EC-01 à EC-04 (connexion, liste, idée, questionnaire), états vide/erreur/chargement, accessibilité clavier, cibles ≥ 44 px | app/(auth), app/projects/* | T-03, T-05, T-07 | CA-01, 02, 03, 27 | Test Playwright : P-01 étapes 1 à 3 au clavier, à 360 px | Perdre la saisie à l'expiration de session |
Jalon J2 — Clés BYOK, modèles, estimation (MVP-05, 02 partiel)
| ID | Objectif | Modules / fichiers | Dépend de | CA | Preuve de fin | Piège à éviter |
|---|---|---|---|---|---|---|
| T-09 | Chiffrement AES-256-GCM, clé maître via KMS UE (abstraction + stub local), masquage des logs | lib/crypto.ts, lib/logger.ts | T-01 | CA-18 | Test unitaire aller-retour ; test log sans clé en clair | IV réutilisé |
| T-10 | GET/POST/DELETE /me/keys : test de validité à l'enregistrement (valid/invalid), revoked à la suppression ; réponses sans clé (seul key_last4) | api/me/keys | T-04, T-06, T-09 | CA-17, 18 | Tests : clé invalide → invalid ; réponse/log inspectés sans clé | Renvoyer la clé dans l'écho du POST |
| T-11 | GET /models (catalogue actif) et PATCH /me (ui_locale, default_model_id, doc_locale_default) — corrige EC-06/08 | api/models, api/me | T-02, T-04 | CA-07, 27 | Tests : modèle inactif refusé ; défaut persisté | Accepter un model_id inexistant |
| T-12 | POST /projects/{id}/estimate : fourchette € et s selon profondeur × modèle ; drapeau indicative en BYOK ; 403 Complète en free ; 422 si indisponible | lib/estimate.ts, api/.../estimate | T-02, T-04 | CA-05, 07, 08 | Tests : changement de modèle → recalcul ; BYOK → indicative=true | Estimation min > max |
| T-13 | UI EC-09 (clés/modèle par défaut) | app/settings/keys | T-10, T-11 | CA-17, 18 | Test E2E : ajout clé invalide → statut visible, clé masquée | Afficher la clé après saisie |
Jalon J3 — Facturation, quotas, autorisation de lancement (MVP-07, 02)
| ID | Objectif | Modules / fichiers | Dépend de | CA | Preuve de fin | Piège à éviter |
|---|---|---|---|---|---|---|
| T-14 | Stripe Checkout/Portal + POST /webhooks/stripe : signature vérifiée, idempotence stripe_events, transitions solo_active/solo_past_due (grâce 7 j)/solo_canceled | api/billing/*, api/webhooks/stripe | T-03 | CA-23, 24, 25 | Tests : webhook rejoué ×2 sans effet ; hors ordre toléré ; signature KO → 400 | Traiter l'événement avant d'écrire stripe_events |
| T-15 | Job d'expiration (corrige EC-04) : status = 'free' si grace_ends_at ou current_period_end dépassé ; évaluation aussi à la lecture du plan | jobs/expire-subscriptions.ts, lib/plan.ts | T-14 | CA-23, 25 | Test avec horloge simulée : J+8 → free ; résilié → Solo jusqu'à fin de période | Fuseaux : comparer en UTC |
| T-16 | Moteur de quotas (corrige EC-01/02/03) : requête de décompte Solo = somme quota_units des générations de l'utilisateur (jointure projects.owner_id) avec created_at >= current_period_start, quota_refunded = false, status <> 'failed', plafond 30 ; index adapté ; RM-15 pour free (3 Rapides, free_quota_used en transaction + verrou) ; BYOK → quota_units = 0 ; restitution sur annulation sans section ou échec | lib/quota.ts, migration 002_quota_index.sql | T-02, T-14 | CA-06, 12, 30 | Tests : Complète = 3 ; 4ᵉ free refusée ; échec non décompté ; BYOK = 0 | Course sur deux lancements : verrou obligatoire |
| T-17 | PUT /me/eu-notice + fonction pure canLaunch() = table de décision (plan, profondeur, clé, quota restant, estimation confirmée, avertissement UE) ; « quota gratuit restant > 0 » (corrige EC-01) | lib/launch-policy.ts, api/me/eu-notice | T-10, T-12, T-15, T-16 | CA-05, 06, 08, 09, 17, 26, 30 | Test paramétré : chaque ligne de la table de décision | Ordre des refus incohérent avec la table |
| T-18 | UI EC-10 (abonnement) et bandeaux solo_past_due | app/billing | T-14, T-15 | CA-23, 25 | Test E2E avec Stripe en mode test | Bandeau absent hors grâce |
Jalon J4 — Génération asynchrone et dossier (MVP-02, 03)
| ID | Objectif | Modules / fichiers | Dépend de | CA | Preuve de fin | Piège à éviter |
|---|---|---|---|---|---|---|
| T-19 | POST /generations : Idempotency-Key obligatoire, confirmed_estimate, canLaunch, insertion queued ; 409 sur index unique ; 422 non confirmée ; projet → generating | api/.../generations | T-12, T-16, T-17 | CA-09, 10, 26, 30 | Tests : double POST concurrent → un seul 202 + un 409, un seul décompte | Décompter avant l'insertion réussie |
| T-20 | Worker : prend les queued, produit les 7 sections dans l'ordre, enregistre section + claims ; Fait sans URL → H ; Rapide : sans vérification web ; Complète : vérification d'URL, inaccessible → H | worker/*, lib/claims.ts | T-06, T-19 | CA-13, 14 | Tests mock : sortie avec Fait sans URL stocké hypothesis ; aucun claim sans marqueur | Vérification d'URL bloquante (ne doit pas l'être) |
| T-21 | Reprise, échec, annulation : 2 tentatives puis failed ; resume à la 1ʳᵉ section manquante ; cancel avec crédit restitué si 0 section ; clé révoquée → failed sans bascule plateforme | worker/resume.ts, api/.../cancel, .../resume | T-20 | CA-11, 12, 19 | Tests : échec après 3 sections → reprise section 4 ; annulation à 0 section → quota_refunded | Régénérer les sections 1 à 3 |
| T-22 | GET projet/génération/sections + UI EC-05 (lancement), EC-06 (suivi, région aria-live), EC-07 (lecture, badges Fait/H en texte + icône) | app/projects/[id]/* | T-19, T-20, T-21 | CA-05 à 13, 26 | E2E : P-01 étapes 4 à 7 en Rapide avec mock ; bannière UE bloquante | Couleur seule pour Fait/H |
Jalon J5 — Chat, export, RGPD (MVP-04, 06, 07)
| ID | Objectif | Modules / fichiers | Dépend de | CA | Preuve de fin | Piège à éviter |
|---|---|---|---|---|---|---|
| T-23 | Chat par section : GET/POST /sections/{sid}/chat, proposition pending, apply (section v+1, ancienne dans section_versions, base_version → 409 si périmée), reject, restore ; plafond de messages → 429 | api/sections/*, api/chat-messages/* | T-20 | CA-15, 16 | Tests : section inchangée avant « Appliquer » ; deux apply concurrents → plus récente gagne, ancienne restaurable | Appliquer sans transaction |
| T-24 | UI EC-08 (panneau latéral desktop, plein écran mobile) | app/projects/[id]/chat | T-23 | CA-15, 16 | E2E : appliquer/rejeter ; conflit → restauration proposée | Perte de l'historique au rechargement |
| T-25 | GET /export ZIP : 7 sections .md, schema.sql extrait des blocs de code de la section « Architecture et données » (corrige EC-09), AGENTS.md, CLAUDE.md d'une ligne ; noms normalisés ; 409 si non generated ou section en régénération | api/.../export, lib/zip.ts | T-20 | CA-20, 21 | Test : contenu du ZIP vérifié ; aucun .. ni chemin absolu | Extraire plusieurs blocs SQL dans le mauvais ordre |
| T-26 | Suppression de compte (corrige EC-05) : DELETE /me avec « SUPPRIMER » ; 409 si solo_active ; pose deletion_requested_at, invalide les sessions ; job de purge à J+30 supprimant users (cascade) et données liées hors cascade (stripe_events rattachés, journaux) | api/me, jobs/purge-accounts.ts | T-03, T-14 | CA-28, 29 | Tests : refus solo_active ; connexion refusée après demande ; purge à J+30 avec horloge simulée | Oublier les tables hors cascade |
| T-27 | UI EC-11, EC-12 (compte, 404, session expirée avec saisie conservée) ; durcissement : Markdown assaini, rate limits, en-têtes de sécurité | app/settings/account, middleware.ts | T-26, T-23 | CA-22, 27, 28, 29 | E2E : suppression bloquée puis confirmée ; test XSS dans le Markdown IA | HTML brut rendu depuis la sortie IA |
| T-28 | Parcours E2E complets P-01 à P-04 + passe accessibilité clavier | e2e/* | T-08 à T-27 | CA-01 à CA-30 | pnpm e2e vert en CI | Dépendre de vrais fournisseurs IA en CI (utiliser le mock) |
Couverture des critères d'acceptation
| CA | Tâche(s) | CA | Tâche(s) |
|---|---|---|---|
| CA-01 | T-05, T-08, T-28 | CA-16 | T-02, T-23, T-24 |
| CA-02 | T-07, T-08 | CA-17 | T-10, T-13, T-17 |
| CA-03 | T-06, T-07, T-08 | CA-18 | T-09, T-10, T-13 |
| CA-04 | T-05 | CA-19 | T-06, T-21 |
| CA-05 | T-12, T-17, T-22 | CA-20 | T-25 |
| CA-06 | T-16, T-17 | CA-21 | T-25 |
| CA-07 | T-11, T-12, T-22 | CA-22 | T-03, T-04, T-27 |
| CA-08 | T-12, T-17 | CA-23 | T-14, T-15, T-18 |
| CA-09 | T-17, T-19 | CA-24 | T-14 |
| CA-10 | T-19 | CA-25 | T-14, T-15, T-18 |
| CA-11 | T-21 | CA-26 | T-17, T-19, T-22 |
| CA-12 | T-16, T-21 | CA-27 | T-01, T-03, T-08, T-11, T-27 |
| CA-13 | T-02, T-20, T-22 | CA-28 | T-26, T-27 |
| CA-14 | T-02, T-20 | CA-29 | T-26, T-27 |
| CA-15 | T-23, T-24 | CA-30 | T-16, T-17, T-19 |
Écarts corrigés : EC-01 (T-16, T-17), EC-02 (T-16), EC-03 (T-16), EC-04 (T-15), EC-05 (T-03, T-26), EC-06 (T-02, T-11), EC-07 (T-07), EC-08 (T-02, T-11), EC-09 (T-25).
Relecture indépendante
Écarts
| ID | Gravité | Où | Problème | Correction |
|---|---|---|---|---|
| EC-01 | Haute | §2 RM-02/CA-30 vs §4 SQL, §5 API | Le quota gratuit de 3 Rapides est une règle (table de décision, CA-30) mais n'a aucune règle propre : free_quota_used n'est jamais incrémenté ni décrit côté API. Les Rapides « gratuites » contredisent aussi RM-03 (quota mensuel Solo) et la mention « 1ʳᵉ génération gratuite » ambiguë. | Ajouter RM-15 : « plan free = 3 Rapides au total ; free_quota_used +1 à chaque génération (en transaction, avec verrou sur subscriptions), restitué si annulée sans section ou échec » ; remplacer la mention ambiguë de la table de décision par « quota gratuit restant > 0 ». |
| EC-02 | Haute | §2 RM-03 vs §4 SQL | Le quota mensuel Solo (30 Rapide / 10 Complète) n'a ni compteur ni requête : seul un index generations(project_id, created_at) existe, alors que le décompte est par utilisateur. | Ajouter l'index/vue de décompte : somme de quota_units des générations de l'utilisateur (jointure projects.owner_id) où created_at >= current_period_start, quota_refunded = false, status <> 'failed'. Plafond : 30 unités (RM-03 : 10 Complètes × 3). |
| EC-03 | Moyenne | §2 RM-03 vs §4 quota_units | quota_units IN (0,1,3) : 0 n'est pas défini (BYOK ?). Or RM-03 dit « quota épuisé : proposer BYOK », donc BYOK ne décompte pas, mais la règle n'est écrite nulle part. | Ajouter à RM-03 : « une génération BYOK a quota_units = 0 ». |
| EC-04 | Moyenne | §2 RM-12 vs §4 subscriptions / §5 | solo_past_due « traité comme free après grâce » : aucun job ni règle ne bascule status vers free à grace_ends_at (idem solo_canceled à current_period_end). | Ajouter un job planifié (ou évaluation à la lecture) : si grace_ends_at ou current_period_end dépassé → status = 'free'. Le noter en §4 composants. |
| EC-05 | Moyenne | §2 RM-14 / CA-29 vs §4 | La purge sous 30 jours est citée (job planifié) mais deletion_requested_at n'est pas lié à une règle de blocage de connexion ni à une résiliation Stripe ; ON DELETE CASCADE ne couvre pas stripe_events ni les journaux. | Préciser : après DELETE /me, session invalidée et connexion refusée ; le job de purge supprime users (cascade) à J+30. |
| EC-06 | Moyenne | §3 EC-05 / §5 | Aucun endpoint pour afficher le catalogue de modèles (ai_models) ni pour le modèle par défaut (EC-09 « modèle par défaut »), et pas de colonne default_model_id. | Ajouter GET /models et users.default_model_id (ou retirer « modèle par défaut » d'EC-09). |
| EC-07 | Moyenne | §5 POST /questions vs RM-01 | Le minimum de 3 questions et l'exactement 2 options ne sont vérifiés qu'« par l'application » : pas de test de passage en questioning dans l'API ni de rollback. | Préciser : validation en transaction avant insertion ; si invalide → relance, puis failed. |
| EC-08 | Faible | §1 D-09 vs EC-11 | La langue du dossier « par défaut » (EC-11) n'existe pas en base (users n'a que ui_locale). | Ajouter users.doc_locale_default ou supprimer ce réglage d'EC-11. |
| EC-09 | Faible | §5 export vs RM-10 | GET /export doit exporter « le SQL » : ce SQL est la section 4 du dossier, pas un fichier distinct stocké. | Préciser que le SQL est extrait des blocs de code de la section « Architecture et données ». |
Hypothèses à vérifier en premier
- Estimation fiable du coût/durée avant lancement (D-02) — risque : fourchette fausse, lancement bloqué (RM-04) ou coût sous-estimé ; vérifier sur quelques idées types le ratio estimé/réel.
- Marge économique de Solo à 15 €/mois (30 Rapides ou 10 Complètes, modèles plateforme) — risque : coût IA supérieur au prix ; chiffrer le coût moyen d'une Complète avec vérification web avant de fixer les quotas.
- TVA/Stripe Tax et prix « TTC » pour particuliers (D-06) et sortie de données hors UE vers les fournisseurs IA (D-04/RM-13) — risque juridique RGPD/TVA ; faire valider par un juriste (DPA des fournisseurs, base légale, mention TTC vs HT).
Sources web consultées
20 liens vers des sources
- Claude Code now supports AGENTS.md Natively - Here's how it actually works - DEV Community
- AGENTS.md Spec (2026): Recommended Sections + AGENTS.md vs CLAUDE.md vs .cursorrules
- CLAUDE.md, AGENTS.md & Copilot Instructions: Configure Every AI Coding Assistant
- AGENTS.md Guide 2026: Tool Support, Claude Code, Caveats
- How to Write AGENTS.md: Templates, Best Practices, and Multi-Tool Support (2026) — The Prompt Shelf
- What Is AGENTS.md? A Guide for AI Coding Agents
- AGENTS.md vs CLAUDE.md: Does Claude Code or Codex Read Both?
- What is AGENTS.md, and why Claude Code now reads it
- AGENTS.md: Format, Example, vs CLAUDE.md, and Generator
- AGENTS.md Guide (2026): Copilot, Cursor & More
- Stripe VAT in Germany: Registration & Filing
- VAT (Value-Added Tax) and SaaS (Software as a Service)
- Tax for digital products
- How the VAT OSS scheme works for Dutch companies
- VAT registration in Europe in 2027: A practical guide
- What is EU value-added tax (VAT) & VAT OSS?
- EU VAT for a SaaS with Stripe Tax: OSS, reverse charge and VIES - DEV Community
- 16 Tax Compliance Tools for SaaS Using Stripe: What Stripe Tax Actually Covers (And What It Doesn't)
- Navigating the VAT registration process in Europe
- Introduction to EU VAT and European VAT OSS