Technique · Manager technique
Vue du manager technique
L’API est rapide ; c’est sa forme qui coûte. Aucune rupture de contrat sur ce que l’application consomme, mais 16 problèmes bloquants sur 59 relevés, et une documentation qui ne décrit pas le comportement réel.
Chemins de la spécification consommés
61 à 62
sur 94
Opérations sans champ requis déclaré
41 sur 107 (38 %)
alors que l’API valide strictement
Problèmes bloquants relevés
16
sur 59 problèmes, 11 groupes de workflows
Rupture de contrat sur un endpoint consommé
0 rupture
contrôle automatisé à chaque mise à jour
État de l’API
Les faits mesurés, puis les problèmes par workflow : rouge bloquant, orange majeur, jaune moyen.
Mesures
- Spécification en ligne
- OpenAPI 3.0.1, v4 : 94 chemins, 107 opérations, 165 schémas
- Chemins réellement consommés par l'application
- 61 à 62 (67 fonctions HTTP : 33
GET, 27POST, 3PUT, 2PATCH, 2DELETE) - Chemins consommés absents de la spécification
- 0 (la surface est déclarée)
- Opérations dont le corps de requête déclare
required: [] - 41 sur 107 (38 %) alors que l'API valide strictement
- Opérations déclarant un
400 - 13 seulement ; les refus de saisie sortent en
500 - Réponses déclarées
*/* - 9 (dont un devis qui renvoie du JSON et quatre endpoints photo qui renvoient du binaire)
- Enveloppes d'erreur distinctes observées
- 5, avec trois formats d'horodatage
- Formats de dates coexistants
- 5, dont un compact
AAAAMMJJpropre à un seul endpoint - Enveloppes de pagination
- 2 (page Spring complète / page réduite), base d'index non uniforme (0 ou 1 selon l'endpoint)
- Latence mesurée
- Devis 31 à 94 ms, connexion 277 ms : l'API est rapide, c'est sa forme qui coûte
- Contrôle de dérive du contrat sur les endpoints consommés
- 0 rupture
- Coût côté application de l'écart API ↔ besoins
- Client HTTP écrit à la main (1 968 lignes, le générateur de code n'a jamais pu être exécuté) + couche d'anti-corruption de 4 051 lignes d'adaptateurs et 2 762 lignes de tests, soit environ 66 lignes de traduction par endpoint
Problèmes par workflow
Légende : 🔴 bloquant · 🟠 majeur · 🟡 moyen · ⚪ mineur. Les références renvoient aux tickets de la Partie 3.
| Groupe | Bloquant | Majeur | Moyen | Mineur | Total |
|---|---|---|---|---|---|
| Authentification | |||||
| Accueil | |||||
| Dashboard | |||||
| Compte | |||||
| Menu | |||||
| Souscription | |||||
| Paiement | |||||
| Contrats | |||||
| Sinistres | |||||
| Remboursements | |||||
| Santé Individuelle · Santé Famille · Santé Groupe | |||||
| Total (+ 6 transverses) | 16 | 17 | 25 | 1 | 59 |
| Sévérité | Endpoint | Problème observé | Réf. |
|---|---|---|---|
| Authentification | |||
| Bloquant | POST /auth/refresh | Répond 500 avec un jeton de rafraîchissement valide ; le nom de champ est confirmé correct par les 400 obtenus avec d'autres noms. Le jeton d'accès vit 30 minutes : toute session au-delà est détruite et la connexion par empreinte échoue de la même manière. Seul cas de l'audit où la spécification est juste et l'implémentation cassée | À créerA9#1(ticket dans l’outil de ticketing, nouvelle fenêtre) |
| Bloquant | POST /auth/otp/validate | Seul l'OTP e-mail est contrôlé ; l'OTP téléphone est envoyé, collecté et jamais vérifié | P0-D#2(ticket dans l’outil de ticketing, nouvelle fenêtre) |
| Bloquant | POST /auth/mfa/send | Séquence du diagramme backend rejouée sur le compte de recette demo@shiftin-demo.com (boîte relevée) : aucun code n'arrive après le 420, et /auth/mfa/send répond 200 « RESENDING WITH SUCCESS » pour EMAIL, email, MAIL, avec username et même à corps vide, sans qu'aucun e-mail ne soit délivré (INBOX, Junk et spam vérifiés, sur une adresse qui reçoit bien l'OTP de /auth/otp). Aucun compte ne peut franchir le défi MFA par e-mail | A22#16(ticket dans l’outil de ticketing, nouvelle fenêtre) |
| Bloquant | POST /auth/otp/validate | L'OTP n'est pas à usage unique : le même code validé 3 fois en 8 minutes, un nouveau jeton UPDATE_PASSWORD émis à chaque fois ; un code vide expose une SQLGrammarException Hibernate dans le message | A19#66(ticket dans l’outil de ticketing, nouvelle fenêtre)A21#68(ticket dans l’outil de ticketing, nouvelle fenêtre) |
| Majeur | POST /auth/otp | Répond 404 « User Not found » pour toute adresse non encore inscrite et pour tout numéro de téléphone : aucun OTP possible avant POST /users ; répond 409 « Code generated déja » pendant les 15 minutes de validité du code précédent (aucun renvoi possible) | A17#64(ticket dans l’outil de ticketing, nouvelle fenêtre)A20#67(ticket dans l’outil de ticketing, nouvelle fenêtre)A23#69(ticket dans l’outil de ticketing, nouvelle fenêtre) |
| Majeur | POST /users puis POST /auth/login | La politique de mot de passe n'est pas appliquée à la création : un mot de passe accepté par POST /users est refusé en 419 CHANGE_PASSWORD à la première connexion (branche « mot de passe invalide » du diagramme backend, vérifiée : un second compte au mot de passe conforme reçoit directement le défi MFA) | A18#65(ticket dans l’outil de ticketing, nouvelle fenêtre) |
| Mineur | POST /auth/otp/validate | Le jeton est renvoyé avec le préfixe Bearer dans la valeur ; e-mail émis par notification_sinistre@amana.dz avec la coquille « Changemenet » | A24#70(ticket dans l’outil de ticketing, nouvelle fenêtre) |
| Majeur | POST /auth/login | Codes 419 (changement de mot de passe imposé) et 420 (MFA) hors RFC ; le jeton porteur du défi arrive sans champ status ; la connexion est rejetée si un JWT périmé est attaché | P2.2#30(ticket dans l’outil de ticketing, nouvelle fenêtre) |
| Moyen | PATCH /auth/password · GET /auth/password-policy | L'endpoint de politique répond désormais (anonyme, quatre règles en français : 8 caractères, complexité avec @ # ! ? $ £, pas d'informations personnelles, différent des 5 derniers). Il reste absent de la spécification et non structuré (phrases libres, classées par mots-clés côté application) ; le refus 419 ne nomme toujours pas la règle violée | résolu, documentation à compléterP3.7 |
| Majeur | JWT client | Le jeton d'un simple assuré porte 19 rôles, dont des périmètres de back-office ; au moins un est inerte sur cette passerelle (affichage-swagger → 403). Pas d'élévation prouvée, mais un jeton qui n'est pas au moindre privilège | À créer |
| Majeur | POST /users | Le contrat documenté répondait 500 ; l'opération réelle est newUser. Le CIN collecté à l'inscription n'a aucune destination dans le contrat et est perdu | Question Q9 bis |
| Moyen | POST /auth/attach | Contrat non confirmé (corps LoginRequest sous session porteuse ?), déclencheur inconnu (checkuser, altern_users ?), cas d'échec et idempotence non documentés : 9 questions transmises, sans réponse | QuestionsQ1-Q9#71(ticket dans l’outil de ticketing, nouvelle fenêtre) |
| Accueil | |||
| Majeur | GET /notifications/unread-count | 404 : une page de 50 notifications est chargée dans 4 écrans uniquement pour compter les non-lus | P3.2#43(ticket dans l’outil de ticketing, nouvelle fenêtre) |
| Moyen | GET /produits · GET /tarifs | Aucun prix minimum sur la liste des produits : un appel /tarifs par produit, soit un N+1 imposé à l'accueil ; /tarifs sans codeProduit répond 200 [] au lieu de 400 | P3.9#44(ticket dans l’outil de ticketing, nouvelle fenêtre) |
| Moyen | GET /produits/{id}/detail | Charge la plus riche de l'API, sans aucun schéma dans la spécification ; prix pré-formaté en chaîne, décisions de présentation (isBest, recommandee) prises côté serveur | P2.7#40(ticket dans l’outil de ticketing, nouvelle fenêtre) |
| Moyen | Incident de pré-production | Une panne de GET /produits a bloqué l'accueil entier pendant une trentaine de minutes (22 endpoints en régression, résolution spontanée sans cause identifiée) | — |
| Dashboard | |||
| Moyen | GET /contrats · /refunds · /claims | Deux enveloppes de pagination ; base d'index 0 pour /contrats, 1 pour /refunds et /claims, qui répondent 500 sans page ; la spécification documente encore l'enveloppe retirée de /refunds et /claims | P2.4#33(ticket dans l’outil de ticketing, nouvelle fenêtre) |
| Moyen | GET /contrats | Aucun numéro de police ; ordre de tri et politique d'exclusion non confirmés ; limitation à 1 000 éléments | P3.15#46(ticket dans l’outil de ticketing, nouvelle fenêtre) |
| Moyen | GET /refunds/{id} | Renvoie le même objet plat que la liste, sans enrichissement | — |
| Compte | |||
| Bloquant | PUT /members | Remplacement d'enregistrement complet et non mise à jour partielle ; bloc questionnaire nommé questionnaire et non bonneSanteQuestion ; réponses en chaînes « Oui »/« Non » et non en booléens ; validation (téléphone 10 chiffres, code postal 5 chiffres, ville, date d'entrée en service) documentée nulle part | P0-F#18(ticket dans l’outil de ticketing, nouvelle fenêtre) |
| Majeur | PUT /beneficiaries/updateTaux | Total des parts imposé à 100 %, non documenté ; le compte de test porte 10 % + 19 %, donc aucune répartition ne peut y être enregistrée | P1-F#26(ticket dans l’outil de ticketing, nouvelle fenêtre) |
| Majeur | POST /beneficiaries | Seule la date compacte AAAAMMJJ est acceptée (JJ/MM/AAAA → 400, formes ISO → 422) alors que la lecture renvoie JJ/MM/AAAA ; énumérations LienParenteEnum (Conjoint, Enfant, Ascendant) et SexeEnum fermées et non publiées | P2.13#32(ticket dans l’outil de ticketing, nouvelle fenêtre)P1-G#27(ticket dans l’outil de ticketing, nouvelle fenêtre) |
| Majeur | DELETE /ayantDroits/{id}/demandeSuppression | Corps multipart déclaré sans aucun champ ; 500 sur identifiant inconnu ; aucun état de demande exposé ensuite | P3.19#47(ticket dans l’outil de ticketing, nouvelle fenêtre) |
| Moyen | GET /auth/me | DTO plat de 5 champs déclaré, réponse de 18 champs et 4 objets imbriqués ; nom/prenom vides à la racine ; un compte de test n'expose ni personneId ni bloc souscripteur alors que /auth/login les renvoie | P2.1#31(ticket dans l’outil de ticketing, nouvelle fenêtre)P1-J#25(ticket dans l’outil de ticketing, nouvelle fenêtre) |
| Moyen | GET /users/photo | Binaire image/png exigeant l'en-tête Authorization : impossible à donner à un composant image, converti en data: URI côté application | — |
| Menu | |||
| Bloquant | GET /documents | 500 pour tout souscripteur non adhérent santé (route interne bulletinV2DocumentUtil/{adherentId}) sur un catalogue de textes réglementaires publics ; le champ fichier est un chemin de fichier serveur ; GET /assets?path= répond 404 | P0-L#3(ticket dans l’outil de ticketing, nouvelle fenêtre)P0-H#4(ticket dans l’outil de ticketing, nouvelle fenêtre) |
| Moyen | GET /a-propos-amana/sos · /documents · /actes/groups | accept-language ignoré : contenu en français dans l'application arabe ; /sos mêle une ligne de prose à une liste de numéros | P3.16#49(ticket dans l’outil de ticketing, nouvelle fenêtre) |
| Moyen | POST /nousContacter | 500 « Required part 'nom' is not present » sur champ manquant (attendu 400) ; champs multipart non déclarés ; enveloppe de réponse propre à cet endpoint | P2.6#37(ticket dans l’outil de ticketing, nouvelle fenêtre)P2.10#42(ticket dans l’outil de ticketing, nouvelle fenêtre) |
| Moyen | GET /garanties/{codeProduit} | Valeurs par formule là où le modèle attend une valeur unique ; la fiche produit Santé renvoie guarantees: [] et force le repli | P3.6#48(ticket dans l’outil de ticketing, nouvelle fenêtre) |
| Moyen | GET /a-propos-amana/actualites | Pas d'endpoint de détail : le détail réutilise la charge de la liste | — |
| Souscription | |||
| Bloquant | POST /souscriptions/voyage | A répondu 201 success: true avec un contratId qui répond ensuite 404 (V8b) ; création en trois appels non transactionnels (/personnes/assure → /souscriptions/voyage → /personnes/membre-assure), rattacher un membre à un contrat inexistant → 500 (V8c) ; souscripteurId déclaré requis mais ignoré par le serveur | P1-A#20(ticket dans l’outil de ticketing, nouvelle fenêtre)P1-B#21(ticket dans l’outil de ticketing, nouvelle fenêtre) |
| Majeur | POST /simulation-voyage | Champs déclarés obligatoires ignorés : age omis → 200 avec un prix plausible mais faux, dateEffet/dateFin non déclarés → 500 (V5) ; dureeJours obligatoire mais sans effet sur le tarif (V9) ; réponse déclarée type: string ; primes négatives renvoyées en succès sur le type Groupe (V12) ; prime Famille plafonnée au 7ᵉ voyageur (V13) ; champ recommandee ajouté sans annonce | P1-C#22(ticket dans l’outil de ticketing, nouvelle fenêtre)P1-D#23(ticket dans l’outil de ticketing, nouvelle fenêtre)P1-E#24(ticket dans l’outil de ticketing, nouvelle fenêtre)P3.14#51(ticket dans l’outil de ticketing, nouvelle fenêtre) |
| Majeur | POST /souscriptions (Scolaire, Accident, Santé) | Durée de couverture imposée (Scolaire 364–366 j, Accident 30 j, Santé 365–366 j) découverte par un 409 « Tarifs invalide » ; création Accident en groupe encore en 409 ; le backend recalcule la prime Santé et ignore le montant transmis ; nombreMembres désigne les ayants droit et non les personnes ; réponse de 6 blocs dont 5 sans lecteur ; coupon inconnu → 409 en Santé mais ignoré en silence hors santé | à ticketer (Accident groupe)P3.3#50(ticket dans l’outil de ticketing, nouvelle fenêtre) |
| Majeur | Aucune idempotence | Un double appui ou un rejeu réseau crée un second contrat ; aucune compensation (DELETE) sur un contrat non payé | À créer |
| Moyen | POST /personnes/assure · GET /professions | profession en texte libre ; 354 professions non triées | P2.11#34(ticket dans l’outil de ticketing, nouvelle fenêtre) |
| Moyen | POST /promoCode/validate | Aucun code valide en base | P3.3#50(ticket dans l’outil de ticketing, nouvelle fenêtre) |
| Moyen | Montants | Primes en flottants à 13 décimales (16164.146341463415), reduction: false alors que les variantes avec et sans réduction diffèrent | À créer |
| Paiement | |||
| Bloquant | POST /modePaiement/CIB (returnUrl) | Après le 3-D Secure, la passerelle redirige vers le site vitrine et non vers le lien profond de l'application ; la page de test est au nom d'un marchand tiers ; formUrl: null avec errorCode: 0 a été observé | P0-M#5(ticket dans l’outil de ticketing, nouvelle fenêtre) |
| Bloquant | GET /modePaiement/confirmPayment/{orderId} | valid: false, tous champs nuls, sur un contrat passé à ENCAISSE avec police émise | P0-N#6(ticket dans l’outil de ticketing, nouvelle fenêtre) |
| Bloquant | confirmPayment/livraison/{quittanceId} | Endpoint décrit par l'équipe backend, absent de la spécification | P0-C#8(ticket dans l’outil de ticketing, nouvelle fenêtre) |
| Majeur | POST /modePaiement/DAHABIA | Fonctionne jusqu'à la page de paiement ; jamais réglé faute de cartes de test | CL-DAHABIA |
| Majeur | /modePaiement/* | Aucune idempotence sur des écritures d'argent ; nom de champ dépendant du résultat (code_reference / codeReference) | À créer |
| Contrats | |||
| Majeur | POST /avenant/resiliation | justificatif en base64 là où tout le reste est multipart ; motifId sans énumération ; 500 sur contrat inconnu divulguant un chemin de service interne | P3.17#54(ticket dans l’outil de ticketing, nouvelle fenêtre)P3.18#55(ticket dans l’outil de ticketing, nouvelle fenêtre) |
| Moyen | GET /contrats/{id} | Ni personnes assurées, ni libellé du moyen de paiement, ni valeur par garantie : second appel GET /coverages/{produitId} à chaque ouverture | P3.5#53(ticket dans l’outil de ticketing, nouvelle fenêtre) |
| Moyen | GET /contrats/{id}/documents | 404 même avec un identifiant réel | P3.4#52(ticket dans l’outil de ticketing, nouvelle fenêtre) |
| Sinistres | |||
| Bloquant | GET /chat/history · POST /chat/send | Aucun paramètre de dossier : un seul fil par client, affiché sous chaque sinistre ; updatedAt déclaré mais absent ; POST /chat/read sans objet sur un fil unique | P3.12#11(ticket dans l’outil de ticketing, nouvelle fenêtre) |
| Moyen | GET /claims | A régressé en 500 en cours de sprint puis a été rétabli ; enveloppe migrée sans mise à jour de la spécification | — |
| Remboursements | |||
| Bloquant | Tous les téléversements | Plafond d'environ 1 Mio par fichier (valeur Spring par défaut) ; dépassement → 500 « Maximum upload size exceeded » sans nom de champ ni limite ; mesuré : 1 024 Kio accepté, 1 200 Kio refusé | P0-I#12(ticket dans l’outil de ticketing, nouvelle fenêtre) |
| Bloquant | POST /refunds | Le champ de l'acte est passé de type à codeActe sans préavis ; l'ancien nom répond 404 « Acte non trouvé » ; la spécification documente le champ retiré et omet celui qui fonctionne ; champs multipart non déclarés | P0-G#19(ticket dans l’outil de ticketing, nouvelle fenêtre)P2.6#37(ticket dans l’outil de ticketing, nouvelle fenêtre) |
| Moyen | GET /actes/groups | Ni catégorie, ni illustration ; codeActe non unique (8 prestations sociales partagent Evenement) : la classification est déduite côté mobile | P3.1#56(ticket dans l’outil de ticketing, nouvelle fenêtre) |
| Moyen | GET /refunds/{id}/documents | Régression 200 → 404 relevée par le contrôle automatisé (endpoint non consommé) | P3.11#57(ticket dans l’outil de ticketing, nouvelle fenêtre) |
| Santé Individuelle · Santé Famille · Santé Groupe | |||
| Bloquant | POST /members/addPieceJointe | Annoncé prêt par l'équipe backend ; corps multipart déclaré sans aucun champ ; toute requête → 422 « The given data was invalid. » sans nommer de champ ; plus de 25 noms de parties testés ; une seule propriété identifiée (rib) par fuite d'erreur | P0-J#13(ticket dans l’outil de ticketing, nouvelle fenêtre) |
| Bloquant | POST /ayantDroits | 500 nu quels que soient les champs | §31#14(ticket dans l’outil de ticketing, nouvelle fenêtre) |
| Bloquant | GET /members/bulletinAdhesion | 500, puis absence de réponse au-delà de 12 s ; l'étape réclamée par le message de succès de la validation d'adhérent est impossible | P3.10#15(ticket dans l’outil de ticketing, nouvelle fenêtre) |
| Majeur | POST /avenants/{id}/renouvellements/sante/devis | 409 « problem devis Renouvellement Sante » sur tout payload (6 contrats, 2 types, toutes les variantes) | P1-H#28(ticket dans l’outil de ticketing, nouvelle fenêtre) |
| Majeur | POST /avenants/{id}/adjonctions/sante/devis | Champ error typé int alors que le service amont renvoie la chaîne "409 :" : la passerelle plante sur sa propre gestion d'erreur et répond 500 | P1-I#29(ticket dans l’outil de ticketing, nouvelle fenêtre) |
| Moyen | POST /members/validation | Le drapeau declare n'est pas lu (false produit le même effet que true) | P2.12#38(ticket dans l’outil de ticketing, nouvelle fenêtre) |
| Moyen | POST /souscriptions/devis-souscription-sante | Ne prend pas en compte destination, durée ou âge (n'est pas un devis Voyage) ; montants en flottants non arrondis | — |
| Moyen | GET /members | PoliceId en PascalCase là où la spécification déclare policeId ; /members/bulletinSoins renvoie un PDF en base64 dans du JSON | — |
Risques
Ce que ces problèmes ont coûté au projet, et ce qui peut encore se produire.
Conséquences
- Sur les délais : aucun sprint d'intégration API n'a pu s'exécuter sur des endpoints validés ; les endpoints manquants ont été livrés après la fin du sprint qui devait les consommer ; la validation du paiement carte a attendu 51 jours une instance de test exploitable. Le détail chiffré est en section 2.3.
- Sur les livrables : les 48 workflows sont livrés, mais 9 ne sont pas validables sans correction serveur, et 16 fonctionnent grâce à un contournement côté application qui devra être retiré.
- Sur les workflows : chaque endpoint a dû être vérifié en conditions réelles avant d'être codé, ce qui est plus lent par construction, et c'est la seule méthode qui a évité de découvrir les défauts en recette ou en production. Codés d'après la documentation, l'enregistrement du profil santé et la déclaration de remboursement auraient échoué à 100 %.
- Sur la confiance : l'application a dû cesser de croire les réponses de succès du serveur et relire l'état réel après chaque écriture sensible.
Risques et mitigation
| Risque | Probabilité | Gravité | Workflow(s) | Mitigation |
|---|---|---|---|---|
| Déconnexion systématique après 30 minutes en usage réel (invisible en développement) | Certaine tant que /auth/refresh répond 500 | Bloquant | Tous | Correction serveur uniquement ; toute ré-authentification silencieuse exigerait de stocker le mot de passe en clair |
| Un client paie et voit un écran d'échec, ou reste sur le site vitrine | Certaine sans correctif | Bloquant | Paiement CIB | Contournement par relecture du contrat (lent) ; returnUrl à corriger côté passerelle |
| Un assuré croit avoir déclaré un sinistre | Certaine | Bloquant | Sinistres | Référentiel produit à exposer |
| Contrat créé deux fois (double appui, rejeu réseau) ou contrat partiel (coupure au 3ᵉ appel) | Possible | Majeur | Souscription | Création au consentement (pas au clic de paiement) et garde de relecture ; idempotence et atomicité à traiter côté serveur |
| Trois voyageurs sur dix assurés gratuitement | Certaine tant que non tranché | Majeur | Souscription Voyage | Décision métier A2 |
| Messages d'erreur illisibles en production | Certaine | Majeur | Tous | Un en-tête côté serveur |
| Régression silencieuse à chaque mise à jour backend (renommage sans préavis déjà survenu) | Élevée | Majeur | Tous | yarn api:contract à exécuter en CI planifiée ; annonce des renommages exigée |
| Panne d'un endpoint bloquant un écran composite | Moyenne | Moyen | Accueil, Espace santé, Mon espace | Dégradation ciblée livrée sur l'accueil ; en-tête sécurisé sur 53 écrans ; à généraliser |
| Comptes migrés jamais branchés | Élevée sans réponse | Moyen | Authentification – Connexion | 9 questions transmises ; branchement de quelques lignes une fois le déclencheur connu |
Causes des décalages
Des faits datés : la porte de qualité non franchie, les causes racines, le glissement sprint par sprint et le plan d’action.
Le fait générateur : l’API devait être validée avant l’intégration
Le planning du client organise le projet en phases successives. La phase 3, « Tests et validation API », prévoit que Amana envoie les endpoints de chaque sprint et que SHIFTIN les teste et les valide, entre le 15/03/2026 et le 30/04/2026. La phase 4, « Intégration API », ne devait commencer qu'ensuite, le 25/05/2026, sur des endpoints validés. C'est une porte de qualité explicite : aucun sprint d'intégration ne devait démarrer sur des endpoints non validés.
Cette porte n'a pas été franchie. Le planning client lui-même porte les verdicts :
| Lot de la phase 3 | Envoi des endpoints planifié | Validation planifiée | Verdict porté au planning |
|---|---|---|---|
| Onboarding, authentification, navigation, accueil, dashboard | 15/03/2026 | 18/03/2026 | ✅ Validé |
| Sinistres hors santé, profil, paramètres, menu | 23/03/2026 | 29–30/03/2026 | ✅ Validé |
| Paiement, Souscriptions voyage | 30/03/2026 | 01/04/2026 | 🔴 « Paiement : Validé — Voyage : 3 rounds – toujours pas validé ! » |
| Compte, Sinistres santé (remboursements) | 06/04/2026 | 13/04/2026 | 🔴 « Compte : 3 rounds – toujours pas validé ! — Voyage : 3 rounds – toujours pas validé ! » |
| Souscriptions scolaire et accident, contrats hors santé, carte Flash | 13/04/2026 | 19–20/04/2026 | aucun verdict porté |
| Santé individuelle, famille, groupe | 20/04/2026 et 26/04/2026 | 26/04/2026 et 29–30/04/2026 | aucun verdict porté |
Aucune de ces mentions n'a été levée avant l'été. L'intégration a donc démarré sur des endpoints non validés, puis sur des endpoints inexistants. Ce n'est pas un choix d'organisation : maintenir l'équipe à l'arrêt jusqu'à validation aurait coûté quatre mois de calendrier. Le coût réel de ce contournement est mesurable : c'est la totalité des reprises et des contournements documentés en section 2.2.
Causes racines, par impact décroissant
1. Des endpoints livrés après la fin du sprint qui devait les consommer.
Ce n'est pas une question de délai de correction (la plupart des correctifs backend ont été livrés en un à deux jours, ce qui est rapide) mais de séquencement : aucune vélocité d'équipe ne compense un composant absent.
1. Des endpoints livrés après la fin du sprint qui devait les consommer. Dépendance Date planifiée Date réelle Écart Validation des endpoints Voyage 01/04/2026 jamais prononcée — Mise en ligne de POST /simulation-voyage(le devis Voyage n'existait pas ; réponse backend du 03/08 : « concernant voyage une api est manquante »)implicite au 30/03/2026 03/08/2026 + 124 jours Devis Voyage branché en réel fin du Sprint 2 API, 20/07/2026 17/08/2026 + 28 jours Création de contrat Voyage ( POST /souscriptions/voyagene créait aucun contrat jusqu'au correctif)20/07/2026 18/08/2026 + 29 jours Endpoint de contenu produit ( /produits/{id}/detail)avant le Sprint 2 API livré le 23/07/2026 sur la base d'un fichier de référence rédigé par l'équipe mobile le 13/07 10 jours de fiche produit en statique Devis Santé ( 404« Problème lors de l'appel API Ticket »)Sprint 3 API réglé le 03/08/2026 + 8 jours PUT /members(405 METHOD_NOT_ALLOWED)Sprint 3 API résolu le 04/08/2026 + 7 jours POST /members/addPieceJointeannoncé prêt le 09/08/2026 toujours inutilisable (aucun champ nommé) ouvert Confirmation du paiement à la livraison décrite par le backend le 30/07/2026 n'existe pas dans la spécification ouvert 2. La dépendance la plus longue : la passerelle de paiement.
2. La dépendance la plus longue : la passerelle de paiement. Jalon Date Cartes CIB de test reçues 16/07/2026 Cartes Dahabia demandées 16/07/2026 — jamais transmises Signalement : l'instance Satim de test est celle d'un tiers, aucune carte ne fonctionne 26/07/2026 Proposition d'un simulateur backend à deux scénarios (succès, échec) 26/07/2026 — non retenue Première page de paiement réellement obtenue (Dahabia) 19/08/2026 Nouvelles cartes CIB transmises par le client 09/09/2026 Premier paiement carte encaissé de bout en bout (contrat passé à ENCAISSE, police émise)16/09/2026, soit 51 jours après le signalement Les quatre tunnels règlent un vrai paiement en exécution automatisée 17/09/2026 Le paiement est la dernière étape de tout parcours d'achat : son indisponibilité a interdit de déclarer un seul produit « validé » pendant toute cette période, alors que les quatre étaient câblés.
3. Une documentation d'API qui ne décrit pas le comportement réel.
38 % des opérations déclarent tous leurs champs optionnels alors que l'API valide strictement ; des champs déclarés obligatoires sont ignorés à l'exécution ; des champs non déclarés provoquent un
500; une réponse décrite comme du texte transporte du JSON ; un typeintegerlà où l'implémentation attend un tableau ; la spécification documente le champ retiré dePOST /refundset omet celui qui fonctionne. La demande transverse du 27/07/2026 (fournir un exemple de requête fonctionnelle par endpoint de création) n'a jamais reçu de réponse. La conséquence est structurelle : chaque endpoint doit être vérifié en conditions réelles avant d'être codé.4. Un succès applicatif qui ne garantit pas que l'opération a eu lieu.
Sur la seule semaine du 17 au 19/08/2026 : un contrat annoncé créé mais inexistant, une page de paiement annoncée disponible mais vide, une prime tarifée sur des paramètres incomplets et renvoyée sans avertissement. Puis, le 16/09/2026, un paiement encaissé que l'endpoint de confirmation déclare non valide. Cette classe de défaut impose de ne faire confiance à aucune réponse et d'ajouter une relecture de contrôle après chaque écriture.
5. Des règles métier découvertes par l'essai plutôt que par la spécification.
Durée de couverture imposée (Scolaire 364–366 jours, Accident 30 jours) révélée par un
409 « Tarifs invalide »le 23/07/2026 ;nombreMembresdésignant les ayants droit ; convention tarifaire obligatoire pour la Famille Voyage ; recalcul silencieux de la prime Santé le 26/08/2026 ; règle dutypeContratIdpar nombre d'assurés communiquée le 20/07/2026 après six questions écrites.6. Un environnement de pré-production incomplet.
Passerelle de paiement inexploitable du 16/07 au 16/09/2026, aucun code promo valide, solde de points insuffisant, compte de test avec des données invalides au regard des règles du backend lui-même, incident de 30 minutes le 27/08/2026 sans cause identifiée (22 endpoints en régression), groupe GitLab passé en lecture seule le 19/08/2026. Aucun de ces points n'est bloquant isolément ; ensemble, ils rendent impossible la validation de ce qui est câblé.
Décalage sprint par sprint
| Sprint | Fin planifiée (Planning V2) | Fin effective du câblage | Écart | Blocage déterminant |
|---|---|---|---|---|
| S1 — Authentification | 07/07/2026 | 10/09/2026 | + 65 jours | Priorisation des produits d'abord ; puis contrat POST /users erroné (500), absence de vérification serveur de l'OTP téléphone, quatre défauts bloquants corrigés le 10/09 |
| S2 — Souscription hors santé | 20/07/2026 | 23/07/2026 pour le Scolaire ; Accident groupe toujours en 409 | quelques jours | POST /souscriptions à corps vide (21/07), 409 groupe et « Tarifs invalide » (23/07) |
| S2 — Moyens de paiement | 20/07/2026 | 17/09/2026 pour le paiement carte ; Points verts, livraison et Dahabia non clos | + 59 jours | Satim indisponible 51 jours ; /points-verts/paiement en 500 depuis le 20/07 ; cartes Dahabia jamais reçues |
| S3 — Voyage | 13/08/2026 | 18/08/2026 (souscription), 19/08/2026 (paiement) | + 5 jours | V8 (aucun contrat créé), V8b (succès sur échec), V11 (formules non identifiables, refus puis livraison le 19/08 après argumentation) |
| S3 — Sinistres | 13/08/2026 | déclaration non déposable | ouvert | Référentiel produit non exposé, découvert le 16/09/2026 |
| S4 / S5 — Santé | 05/08/2026 | 26/08/2026 | + 21 jours | Devis Santé en 404 jusqu'au 03/08, PUT /members en 405 jusqu'au 04/08 |
| S4 / S5 — Compléter profil santé | 05/08/2026 | non clos | ouvert | Dépôt des pièces, personnes à charge et bulletin d'adhésion sans réponse exploitable au 16/09/2026 |
Plan d’action
Le plan est organisé par chantiers de blocage et non plus par sprint (section 1.4). Les lots ci-dessous restent la clé de lecture des tickets de la Partie 3 ; chaque lot est rattaché à son chantier.