Manager technique · Analyse technique détaillée
Manager technique
L’API est rapide ; c’est sa forme qui coûte. Aucune rupture de contrat sur les endpoints consommés, mais 20 problèmes bloquants 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, 107 opérations
Opérations sans champ requis déclaré
41 sur 107 (38 %)
alors que l’API valide strictement
Enveloppes d’erreur distinctes
5
avec trois formats d’horodatage
Rupture de contrat sur un endpoint consommé
0 rupture
contrôle automatisé à chaque mise à jour
Section 2.1 : Audit technique de l’API
L’état mesuré de l’API, les problèmes relevés workflow par workflow, la dette technique et les risques qui en découlent.
État actuel de l’API
- 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
Analyse des problèmes techniques identifiés, 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 (+ 7 transverses) | 18 | 17 | 27 | 1 | 63 |
| 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 | P2.5#35(ticket dans l’outil de ticketing, nouvelle fenêtre)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 | GET /points-verts/paiement/{quittanceId} | 500 systématique, 500 nu sur solde insuffisant ; un GET qui exécute un débit (rejouable par préchargement, robot ou rafraîchissement : un balayage en lecture seule a déclenché une tentative de paiement) | P0-B#7(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) |
| Moyen | POST /carteFlash/checkTicketValidation | statut vaut la chaîne "false" : booléen déguisé sous un nom d'énumération | P2.8#36(ticket dans l’outil de ticketing, nouvelle fenêtre) |
| Sinistres | |||
| Bloquant | POST /claims | Le champ produit doit correspondre à un référentiel « produit sinistre » qu'aucun endpoint mobile n'expose (10 valeurs testées → produit_sinistre_id on null) ; le validateur nomme date_naiss / date_sinistre / path_files alors que l'endpoint lit dateNaiss / dateSinistre / pieces | P0-K#10(ticket dans l’outil de ticketing, nouvelle fenêtre) |
| 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 | Types de contrat | Seuls 530 (Individuel) et 531 (Famille) existent : aucun niveau Groupe pour la Santé | P3.13#58(ticket dans l’outil de ticketing, nouvelle fenêtre) |
| Moyen | GET /members | PoliceId en PascalCase là où la spécification déclare policeId ; /members/bulletinSoins renvoie un PDF en base64 dans du JSON | — |
Transverse : ce qui touche tous les workflows
| Sév. | Constat | Réf. |
|---|---|---|
Erreurs servies en charset=ISO-8859-1 alors que JSON doit être en UTF-8 : le message français affiché en repli à l'utilisateur arrive en mojibake, précisément sur le chemin d'erreur | À créer | |
500 utilisé comme code métier : dix cas confirmés où un 4xx est attendu (page absente, date absente, partie multipart absente, identifiant inconnu, fichier trop lourd, solde insuffisant, champs invalides, jeton valide) | P2.2#30(ticket dans l’outil de ticketing, nouvelle fenêtre), P2.10#42(ticket dans l’outil de ticketing, nouvelle fenêtre) | |
Documentation qui décrit une API qui n'existe pas : 38 % de required: [], cinq « mensonges actifs » (/refunds, /members, /auth/me, enveloppes de /refunds et /claims, /simulation-voyage), aucun exemple de requête fonctionnelle malgré une demande formelle restée sans réponse | P2.9#41(ticket dans l’outil de ticketing, nouvelle fenêtre) | |
Trois conventions de casse, deux langues, un pluriel non systématique dans les chemins ; verbes dans l'URL ; POST utilisé en lecture pour les devis | P2.7#40(ticket dans l’outil de ticketing, nouvelle fenêtre) | |
| Cinq enveloppes d'erreur, trois horodatages, messages de validation en français non traduisibles | P2.10#42(ticket dans l’outil de ticketing, nouvelle fenêtre) | |
Énumérations livrées en libellés d'affichage français ("Masculin", "Marié", "droitier") : interdit de traduire en arabe et de comparer de façon stable | P2.8#36(ticket dans l’outil de ticketing, nouvelle fenêtre) | |
Environnement de pré-production incomplet : compte de test avec données invalides au regard des règles du backend lui-même (corrigées par l'équipe mobile), aucun code promo, solde de points insuffisant pour le cas nominal ; le compte jetable (khaddoucheTest) est fourni mais n'a pas d'enregistrement souscripteur dans /auth/me P1-J#25(ticket dans l’outil de ticketing, nouvelle fenêtre) | — |
Points de dette technique et de non-conformité
| Où | Dette ou non-conformité | Portée |
|---|---|---|
| Backend | Spécification OpenAPI non fiable : le générateur de code ne peut pas être utilisé, 1 968 lignes de client HTTP sont maintenues à la main | Tout futur client (web, back-office) paiera cette dette une seconde fois, intégralement |
| Backend | Sémantique HTTP dégradée : GET avec effet de bord monétaire, DELETE à corps multipart, 201 avec success: false, aucune idempotence sur les écritures d'argent | Souscription, Paiement |
| Backend | Absence de transaction sur la création de contrat Voyage | Souscription Voyage |
| Backend | Hétérogénéité des formats (dates, casse, énumérations, enveloppes, pagination) | Tous |
| Backend | Rôles de back-office dans le jeton d'un client mobile ; divulgation de chemins internes dans les 500 | Sécurité |
| Application | Règles métier hébergées dans les adaptateurs faute de source côté serveur (classification des actes, déduction du sexe depuis un libellé, base d'index de pagination codée par appel) | Remboursements, Compte, Dashboard |
| Application | Identifiants de pré-production versionnés dans le dépôt ; stockage local non chiffré avec cache persisté contenant le questionnaire médical ; télémétrie non branchée ; URL de production factice | Sécurité, exploitation |
| Application | Un seul reliquat de contenu simulé : fixture marketing de la fiche produit en dernier recours si l'endpoint tombe | Menu – Produits |
Risques techniques associés
| 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 |
Section 2.2 : Impacts, conséquences & contournements
Ce que les problèmes ont coûté au projet, puis, workflow par workflow, le contournement en place et ses limites.
Conséquences globales des problèmes identifiés
- 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.
Impacts, contournements et limites, par workflow
Chaque contournement est une dette à retirer dès le correctif serveur ; les cases « aucun » sont les fonctions qu’aucun travail côté mobile ne peut débloquer.
Authentification5 points
| Problème | Conséquence | Contournement en place | Limites et risques du contournement |
|---|---|---|---|
/auth/refresh → 500 | Chaque 401 mène à une déconnexion forcée ; toute la machinerie de rafraîchissement (rotation, verrou anti-concurrence, rejeu unique) est inatteignable | Aucun. Attente du correctif serveur, surveillance du taux de déconnexions | Rien de propre n'est possible côté client |
Contrat POST /users erroné, OTP téléphone non vérifié, jeton statique et mot de passe OTP partagé embarqués dans le bundle | Quatre défauts bloquants sur la branche d'authentification ; un tiers aurait pu relayer des OTP contre le backend | Opération réelle newUser ; secrets retirés ; OTP téléphone collecté mais étape marquée non vérifiée | Le champ CIN est abandonné à l'envoi ; la vérification serveur dépend de la décision A1 |
Défis 419/420 sans champ status ; connexion rejetée si un JWT périmé est attaché | Le mappeur d'erreurs standard aurait jeté le jeton du défi ; « les données n'apparaissent qu'après réinstallation » | Reconnaissance par le jeton, gardé en mémoire seule et restreint par liste blanche ; /auth/login et /auth/refresh partent toujours sans en-tête d'authentification | Hiérarchie d'erreurs dédiée à maintenir |
| Inscription : OTP avant création du compte ; mot de passe oublié : mauvais endpoint et jeton non conservé (défauts applicatifs) | Aucun nouvel utilisateur ne peut s'inscrire ni récupérer son mot de passe | Aucun : correctif applicatif planifié pour le premier APK (créer le compte avant l'OTP ; /auth/otp → /auth/otp/validate → PATCH /auth/password avec le jeton renvoyé) | Dépend de la décision backend sur l'ordre officiel du parcours A17#64(ticket dans l’outil de ticketing, nouvelle fenêtre) et de la délivrance du code MFA A22#16(ticket dans l’outil de ticketing, nouvelle fenêtre) |
| Déclencheur des comptes migrés inconnu | Quatre écrans construits, traduits et testés, invisibles pour l'utilisateur | Routes de recette accessibles par lien profond hors production ; flows Maestro en attente | Aucun appel à /auth/attach tant que le contrat n'est pas confirmé (pas de retour arrière connu) |
Accueil2 points
| Problème | Conséquence | Contournement en place | Limites et risques |
|---|---|---|---|
| Panne d'un endpoint bloquant l'écran entier (incident de pré-production) | Accueil inutilisable après connexion pendant l'incident | Dégradation ciblée : seul le carrousel produits disparaît ; outillage api:probe / api:contract pour qualifier un incident en minutes | À généraliser aux autres écrans composites |
Pas de prix minimum sur /produits ; pas de compteur de non-lus | 4 + N requêtes à l'accueil ; page de 50 notifications chargée pour un badge | Fan-out parallèle, staleTime par usage, dé-duplication de /auth/me | Compteur plafonné par la taille de page ; N+1 permanent |
Dashboard3 points
| Problème | Conséquence | Contournement en place | Limites et risques |
|---|---|---|---|
Enveloppe de /refunds migrée sans préavis (data → content) | L'écran d'accueil entier est tombé en erreur | pageItems() : lecture tolérante des deux enveloppes | 3 lignes qui ont évité une refonte ; dette si une troisième forme apparaît |
| Base d'index de pagination non uniforme | Erreur silencieuse : une page sautée au lieu d'une erreur | Base codée en dur par appel | Indevinable, à documenter côté serveur |
| Pas de numéro de police, tri non confirmé | Champ omis ; « dernier contrat » non fiable | Pagination infinie côté application | — |
Compte6 points
| Problème | Conséquence | Contournement en place | Limites et risques |
|---|---|---|---|
PUT /members = remplacement complet | Envoyer le seul bloc modifié efface le reste ou échoue | Relecture de la fiche complète et fusion avant chaque écriture ; téléphone complété à 10 chiffres | Un aller-retour supplémentaire ; la wilaya du profil santé n'a aucun champ dans le contrat et n'est pas persistée |
| Réponses « Oui »/« Non », libellé « Féminin » accentué | Toutes les assurées affichées « Homme », toutes les réponses médicales affichées « Non » | Correspondances tolérantes, safeEnum avec journalisation des valeurs inconnues | Règle métier déportée côté mobile |
| Règle des 100 % non documentée, compte de test incohérent | Aucune répartition enregistrable sur le compte de test | Vérification côté application avec message explicite | Le compte de test reste inutilisable pour cette fonction tant qu'il n'est pas re-seedé |
| Énumérations fermées | « frère/sœur » et « autre » sans valeur possible | Repli sur « Ascendant » avec indicateur à l'écran | Donnée métier approximée |
Date compacte AAAAMMJJ | Format documenté refusé | Convertisseur dédié à ce seul endpoint | Un cinquième format de date à maintenir |
| Photo de profil en binaire sous JWT | Impossible à afficher directement | Téléchargement du blob puis conversion en data: URI | Image encodée en base64 dans le cache persisté |
Menu5 points
| Problème | Conséquence | Contournement en place | Limites et risques |
|---|---|---|---|
/documents → 500 hors adhérent santé et en-tête perdu sur erreur | Cul-de-sac : l'utilisateur ne pouvait plus revenir en arrière, sur la majorité des comptes réels | En-tête rendu hors de la branche d'erreur (53 écrans) | Le 500 reste : écran vide pour la majorité des souscripteurs |
| Chemin de fichier serveur au lieu d'une URL | Aucun document ouvrable | Bouton de téléchargement masqué | Aucun contournement possible : promesse fonctionnelle non tenable |
| Contenu éditorial en français seulement | Titres traduits autour de contenus français en arabe | Aucun | Aucun contournement possible |
/sos mêle prose et liste, /professions non trié | Présentation à reconstruire | Séparation heuristique et tri dans les adaptateurs | Règle de présentation côté client |
| Pièces jointes du formulaire de contact plafonnées | Photo de téléphone refusée | Compression JPEG qualité 0,6, mesure avant envoi, refus expliqué ; la copie « 8 Mo » corrigée à « 1 Mo » | Le cas nominal reste en échec |
Souscription10 points
| Problème | Conséquence | Contournement en place | Limites et risques |
|---|---|---|---|
201 success: true sur un échec (V8b) | Le client serait envoyé payer un contrat fantôme | Relecture de GET /contrats/{id} après chaque souscription ; un 404 fait échouer l'étape | Un aller-retour réseau par vente ; à retirer dès correction |
| Création Voyage en trois appels non atomiques (V8c) | Contrat vendu pour N voyageurs mais couvrant une partie d'entre eux en cas de coupure | Aucun ; risque assumé, le contrat restant impayé | Risque résiduel non couvert |
Champs obligatoires ignorés, prix faux en 200 (V5) | Prix plausible mais faux affiché | L'endpoint n'est appelé qu'une fois tous les champs réunis | Protection défensive permanente ; la garantie devrait venir de l'API |
| Prime Famille plafonnée au 7ᵉ voyageur (V13) | Trois voyageurs sur dix assurés gratuitement | Aucun : prix serveur affiché tel quel, sur décision produit ; règle « 2 adultes + 8 enfants » appliquée à l'écran et revérifiée avant l'appel | Impact recette tant que non tranché |
| Formules non identifiables (V11), montant total absent (V4), encodage ISO-8859-1 sur le devis (V6) | Cartes nommées par position, capitaux retriés, décodage octet par octet | Retirés après correctifs backend, obtenus après argumentation écrite | — |
| Le backend recalcule la prime Santé et ignore le montant transmis | Un prix affiché, un autre facturé, sans aucune erreur | Devis et contrat construits pour concorder par construction | Toute divergence de règle côté serveur serait invisible |
Durées de couverture imposées, nombreMembres = ayants droit, convention tarifaire Famille | Règles découvertes par 409 et essais | Règles appliquées à l'écran et revérifiées avant l'appel | Règles métier codées côté mobile, à synchroniser à chaque changement |
| Contrat créé au clic de paiement | Un échec de règlement suivi d'une nouvelle tentative créait deux contrats | Création à l'étape de consentement sur les quatre produits | Un contrat en attente subsiste si le client abandonne au paiement |
| Compte sans enregistrement souscripteur | « Paiement non abouti » en fin de tunnel alors que rien n'a été créé | Tunnels rejoués sur le compte santé | Un client réel dans ce cas est bloqué en fin de parcours |
| Aucune idempotence | Double contrat possible | Bouton désactivé pendant l'appel | Un rejeu réseau n'est pas couvert |
Paiement6 points
| Problème | Conséquence | Contournement en place | Limites et risques |
|---|---|---|---|
confirmPayment nie un paiement encaissé | Écran d'échec après un paiement réussi, invitation à repayer | Relecture du contrat : ENCAISSE / ACTIF / PAYE valent règlement (« le contrat est ce qui facture, donc c'est le contrat qui tranche ») | Jusqu'à 90 s avant l'écran de succès ; le détail de la passerelle n'est pas reconstituable |
returnUrl vers le site vitrine | Le client reste dans le navigateur | Aucun | Aucun contournement possible côté mobile |
formUrl: null avec errorCode: 0 | Redirection vers une page vide | Les deux champs contrôlés avant redirection | — |
Points verts en 500 nu sur solde insuffisant | Aucun message exploitable | Contrôle du solde avant appel, erreur typée avec le montant manquant | Logique métier assumée côté client ; cas nominal jamais exécuté |
| Confirmation du paiement à la livraison inexistante | Passage à l'état encaissé invisible | Aucun | Fonction incomplète |
| Instance Satim de test d'un tiers, cartes Dahabia absentes | Validation carte impossible pendant 51 jours ; Dahabia jamais réglé | Proposition d'un simulateur backend à deux scénarios (non retenue) ; cartes CIB finalement fournies | Page marchande au nom d'un tiers non livrable |
Contrats4 points
| Problème | Conséquence | Contournement en place | Limites et risques |
|---|---|---|---|
| Détail de contrat incomplet | Trois données absentes | Second appel GET /coverages/{produitId} ; champs omis à l'écran | +1 requête par écran |
| Justificatif de résiliation en base64 | La demande part sans ses pièces | Motif et commentaire envoyés seuls | Produire du base64 exigerait une nouvelle dépendance native |
| Statut carte Flash en texte libre | Un statut inconnu ne doit jamais débloquer une souscription | Liste blanche : tout statut inconnu échoue fermé | — |
Documents de contrat en 404 | Quatre boutons « documents » inactifs | Masqués | Bloqué par P0-H#4(ticket dans l’outil de ticketing, nouvelle fenêtre) de toute façon |
Sinistres2 points
| Problème | Conséquence | Contournement en place | Limites et risques |
|---|---|---|---|
Référentiel produit non exposé | Déclaration jamais déposée | Contrat reconstitué à un champ près ; l'écran ne simule plus un succès | Fonction inopérante tant que P0-K#10(ticket dans l’outil de ticketing, nouvelle fenêtre) est ouvert |
| Chat rattaché au client | Même fil sous chaque sinistre | L'identifiant du sinistre ne sert plus qu'au cache | Fonctionnalité dégradée par le modèle de données ; à ne pas remonter comme anomalie en recette tant que A3 n'est pas tranché |
Remboursements4 points
| Problème | Conséquence | Contournement en place | Limites et risques |
|---|---|---|---|
| Plafond de 1 Mo par fichier | Une photo de justificatif (3 à 5 Mo) est refusée | Compression, mesure avant envoi, refus expliqué | Le cas nominal reste en échec ; redimensionner exigerait une dépendance native |
Renommage type → codeActe sans préavis | Déclaration codée d'après l'indication backend échouait silencieusement | Contrat écrit à la main d'après les essais ; test encodant le piège | Tout futur renommage silencieux se répétera |
| Catalogue d'actes sans catégorie ni illustration, code non unique | Sélecteur moins lisible | Catégorie et illustration déduites du code dans l'adaptateur | Règle de classification métier hébergée côté mobile |
| Sélecteur de personne assurée listait les bénéficiaires en cas de décès | Mauvaise population | Corrigé : ayants droit couverts | — |
Santé Individuelle · Famille · Groupe6 points
| Problème | Conséquence | Contournement en place | Limites et risques |
|---|---|---|---|
addPieceJointe sans contrat découvrable | Les fichiers sélectionnés n'ont nulle part où aller | Sélecteur de fichiers réel, dépôt non branché | Non branchable tant que P0-J#13(ticket dans l’outil de ticketing, nouvelle fenêtre) est ouvert |
POST /ayantDroits → 500 nu | Personnes à charge jamais soumises | Conservées dans le brouillon, brouillon vidé seulement après accord du serveur | Étape silencieusement incomplète pour l'utilisateur |
Bulletin d'adhésion en 500 | Étape réclamée par le message de succès impossible | Écran présent, rien à télécharger | Le profil reste non activé (validation à 0) |
Renouvellement en 409 opaque, adjonction en 500 sur DTO d'erreur | Deux parcours avec interface complète et rien à appeler | Tarification du renouvellement sur donnée simulée, seul reliquat explicite | À exclure de la version 1 ou planifier (A5) |
| Aucun type Groupe | Souscription Santé Groupe sans objet | — | À confirmer (A4) |
| Plafond de téléversement | Idem Remboursements | Idem | Idem |
Transverse5 points
| Problème | Conséquence | Contournement en place | Limites et risques |
|---|---|---|---|
| Spécification non fiable | Générateur de code inutilisable | Client HTTP écrit à la main, chaque DTO daté de sa vérification live, fonctions nommées d'après l'operationId pour qu'un futur codegen soit un remplacement direct | 1 968 lignes maintenues à la main |
| 3 casses, 5 formats de date, énumérations en français, booléens textuels | Aucun DTO directement consommable par un écran | Couche d'anti-corruption : 26 modules d'adaptateurs purs, synchrones, totaux | 4 051 + 2 762 lignes ; un second client paiera à nouveau |
| Erreurs en ISO-8859-1 | Messages illisibles | Aucun | Décoder en latin-1 selon le content-type serait un correctif laid, réservé au cas où le serveur ne bouge pas |
| Dérive silencieuse du contrat | Régressions invisibles | yarn api:probe (54 lectures, sans écriture) et yarn api:contract (comparaison à un contrat figé, wired-aware) ; playbook de dérive documenté | Ne couvre que les lectures ; les écritures peuvent désormais être automatisées sur le compte jetable khaddoucheTest |
| Environnement de pré-production incomplet | Validation impossible de ce qui est câblé | Compte de test corrigé par l'équipe mobile pour le rendre enregistrable ; données de recette préfixées REC | Résiliation et retrait d'ayant droit restent à rejouer sur le compte jetable khaddoucheTest |
Section 2.3 : Causes & justification des décalages
Des faits datés : la porte de qualité non franchie, les causes racines par impact décroissant, le glissement sprint par sprint, les justificatifs, le travail supplémentaire et le plan d’action.
Le fait générateur : l’API devait être prête et validée avant le début de 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é.
Éléments techniques ayant entraîné le 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 |
Justificatifs et éléments factuels
| Fait | Source |
|---|---|
| Mentions « 3 rounds – toujours pas validé ! » sur Voyage (01/04/2026) et Compte (13/04/2026) | Planning client, feuilles « Timeline actualisée » et « Timeline ajustée avec notes », colonne Note de la phase 3 |
| 60 signalements écrits entre le 13/07 et le 19/08/2026, 48 ayant reçu une réponse (80 %), 12 sans réponse, 4 interlocuteurs backend ; 25 des 60 messages concernent le seul workflow Voyage | Extraction Teams du canal « MyAmana – Dev Team », classée par workflow |
| « concernant voyage une api est manquante, je retournerai vers vous une fois finalisée » | Réponse backend du 03/08/2026 |
| Refus initial d'ajouter l'identifiant et le libellé de formule (« changements sur process actuel déjà en production »), puis livraison le jour même | Fil Teams du 19/08/2026 |
| Matrices de tests Scolaire / Accident (23/07/2026) et Voyage (11/08 et 17/08/2026) transmises au backend, avec la précision « le prix n'est pas la cause : on envoie exactement la valeur renvoyée par votre propre simulation » | Fil Teams |
Cause racine de V8 identifiée par l'équipe mobile (404 sur l'appel interne save-contrat-details), correctif backend le lendemain | Fil Teams des 17 et 18/08/2026 |
Contrat PUT /members, renommage type → codeActe, format AAAAMMJJ, plafond de 1 Mo : découverts en exerçant les endpoints d'écriture en réel les 15 et 16/09/2026 | Journal des écarts de contrat (33 sections avec curl), rapport PM du 16/09/2026 |
POST /auth/refresh → 500 avec un jeton valide émis 2 minutes plus tôt, mesuré le 18/09/2026 | Audit « API réelle vs spécification » |
| Relevés automatisés du contrat d'API des 03/08, 15/09 et 17/09/2026 : verdict intact sur les endpoints consommés | yarn api:contract |
| Incident de pré-production du 27/08/2026 qualifié en 3 minutes (relevé à 15 h 03 : 22 endpoints en régression ; à 15 h 32 : contrat intact) | Relevés automatisés |
| Paiement carte encaissé sur les quatre produits, contrats réels avec police émise, 17 vidéos de recette | Campagne E2E des 16 et 17/09/2026 |
| Chaîne OTP rejouée le 19/09/2026 avec la boîte mail de test sur un compte de recette : 18 appels, deux défauts applicatifs (inscription, mot de passe oublié) et sept défauts backend (A17#64(ticket dans l’outil de ticketing, nouvelle fenêtre) à A23#69(ticket dans l’outil de ticketing, nouvelle fenêtre)) | Note de recette docs/rapports-internes/recette-otp-boite-mail-2026-09-19.md |
Le travail supplémentaire réalisé pour pallier ces manques
Ce travail n'était pas prévu au planning. Il a été nécessaire parce que l'API n'était ni prête ni fiable au moment où l'intégration devait la consommer.
| Travail supplémentaire | Ce qu'il a coûté | Ce qu'il a évité |
|---|---|---|
| Vérification en conditions réelles de chaque endpoint avant de le coder, y compris les endpoints d'écriture (8 sur 11 exercés en réel le 16/09/2026) | Plus lent par construction que coder d'après une spécification | Sur 8 endpoints d'écriture, 4 étaient mal documentés au point de rendre le code inutilisable : profil santé et déclaration de remboursement auraient échoué à 100 % en recette ou en production |
| Client HTTP écrit à la main (1 968 lignes), chaque DTO daté de sa vérification live, parce que le générateur de code produirait des types qui compilent mais mentent | Maintenance manuelle permanente | Des types faux sur une part importante des endpoints |
| Couche d'anti-corruption complète : 26 modules d'adaptateurs (4 051 lignes) et 2 762 lignes de tests, pour absorber 3 casses, 5 formats de date, 5 enveloppes d'erreur, 2 enveloppes de pagination, des énumérations en libellés français | ~66 lignes de traduction par endpoint | Une réécriture d'écrans à chaque migration silencieuse (la migration de /refunds a été absorbée en 3 lignes) |
| Spécification produite à la place du backend : fichier de référence du payload de la fiche produit, avec un exemple par produit, rédigé le 13/07/2026 ; l'endpoint a été livré sur cette base | Travail de conception hors périmètre mobile | Dix jours supplémentaires de fiche produit en statique |
| Matrices de tests et causes racines remontées au backend (matrice Scolaire / Accident du 23/07, tableaux comparatifs Voyage des 11/08 et 17/08, trace de l'erreur interne de V8 le 17/08/2026) | Temps d'investigation sur un système dont l'équipe mobile n'a pas le code | Correctifs backend obtenus en un jour au lieu de cycles d'aller-retour |
| 60 signalements écrits en 38 jours, chacun avec la requête exacte, la réponse obtenue et le comportement attendu | Rédaction et suivi | Un taux de réponse de 80 % sur un canal sans outil de ticketing |
| Gardes de relecture après chaque souscription Voyage et chaque paiement carte, contrôle du solde de points avant appel, appel du devis seulement une fois tous les champs réunis, contrat créé au consentement | Un aller-retour réseau par vente ; jusqu'à 90 s avant l'écran de succès | Envoyer un client payer un contrat fantôme ; afficher un échec après un paiement encaissé ; créer deux contrats pour une intention d'achat |
Outillage de surveillance du contrat : yarn api:probe, yarn api:contract, harnais de capture des 54 lectures et des 31 mutations avec les payloads réels de l'application, playbook de dérive | Développement d'outillage hors périmètre fonctionnel | Une journée perdue à chercher un défaut applicatif inexistant lors de l'incident du 27/08 ; une régression silencieuse à chaque mise à jour backend |
| Correction du compte de test pour le rendre enregistrable au regard des règles du backend (téléphone à 10 chiffres, code postal à 5 chiffres, date d'entrée en service) | — | Un compte de test impossible à enregistrer |
| Revue de sécurité ayant conduit au retrait d'un jeton statique et d'un mot de passe OTP partagé embarqués dans le bundle | — | Un tiers relayant des OTP contre le backend |
| Campagne E2E Maestro : 17 parcours écrits, exécutés et filmés, chaque mur backend nommé par un carton ; 66 tests construits sur des charges utiles capturées en live | Environ dix minutes par parcours, 2,1 Go de vidéos | Neuf anomalies ouvertes contre l'équipe mobile pour des murs backend ; sept défauts applicatifs invisibles en relisant le code |
| Outil de ticketing alimenté (55 tickets rattachés à leur workflow), dashboard de reporting construit, déployé et mis en ligne, planning V3 recalculé en jours ouvrés | Livrables de pilotage hors périmètre initial | Une vision commune de l'état d'avancement et des blocages |
Écrans des comptes migrés construits par anticipation et 9 questions transmises sur POST /auth/attach | — | Refaire les écrans une fois le contrat connu |
Plan d’action pour résorber les écarts
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.