Files
roadwave/docs/INCONSISTENCIES-ANALYSIS.md
2026-01-31 11:45:11 +01:00

22 KiB
Raw Blame History

Analyse des Incohérences entre ADR et Règles Métier

Date d'analyse : 2026-01-28 Analysé par : Audit Architecture RoadWave Scope : 18 ADR × Règles Métier (17 fichiers)


Résumé Exécutif

Cette analyse a identifié 15 incohérences entre les décisions d'architecture (ADR) et les règles métier du projet RoadWave.

Répartition par Sévérité

Sévérité Nombre % Total Statut Action Required
🔴 CRITICAL 2 13% RÉSOLU avant implémentation
🟠 HIGH 4 27% 3 restants (1 résolu) Résolution Sprint 1-2
🟡 MODERATE 8 53% En cours Résolution Sprint 3-5
🟢 LOW 1 7% En cours À clarifier lors du développement

Impact par Domaine

Domaine Nombre d'incohérences Criticité maximale
Streaming & Géolocalisation 3 🔴 CRITICAL
Données & Infrastructure 3 🟠 HIGH
Authentification & Sécurité 2 🟠 HIGH
Tests & Qualité 2 🟡 MODERATE
Coûts & Déploiement 3 🟡 MODERATE
UX & Engagement 2 🟡 MODERATE

🔴 Incohérences Critiques (Blocantes)

#1 : HLS ne supporte pas les Notifications Push en Arrière-plan

Statut : RÉSOLU (ADR-019 créé)

Élément Détail
ADR concerné ADR-002 (Protocole Streaming)
Règle métier Règle 05, section 5.1.2 (Mode Piéton, lignes 86-120)
Conflit HLS est unidirectionnel (serveur→client), ne peut pas envoyer de push quand l'app est fermée
Impact Mode piéton non fonctionnel : notifications "Point d'intérêt à 200m" impossibles

Scénario d'échec :

Utilisateur: Marie se promène, app fermée
Position: 150m de la Tour Eiffel
Attendu: Push notification "🗼 À proximité: Histoire de la Tour Eiffel"
Réel: Rien (HLS ne peut pas notifier)

Solution implémentée :

  • ADR-019 : Architecture hybride WebSocket + Firebase Cloud Messaging
  • Phase 1 (MVP) : Push serveur via FCM/APNS
  • Phase 2 : Geofencing natif iOS/Android pour mode offline

Actions requises :

  • Backend : Implémenter endpoint WebSocket /ws/location
  • Backend : Worker PostGIS avec requête ST_DWithin (30s interval)
  • Mobile : Intégrer Firebase SDK (firebase_messaging)
  • Tests : Validation en conditions réelles (10 testeurs, Paris)

#2 : Latence HLS Incompatible avec ETA de 7 Secondes

Statut : RÉSOLU (ADR-002 mis à jour)

Élément Détail
ADR concerné ADR-002 (Protocole Streaming, lignes 40-41)
Règle métier Règle 05 (lignes 16-20), Règle 17 (lignes 25-30, 120-124)
Conflit ETA de 7s avant le point, mais HLS a 5-30s de latence → audio démarre APRÈS avoir dépassé le point
Impact UX catastrophique : utilisateur entend "Vous êtes devant le château" 100m APRÈS l'avoir dépassé

Calcul du problème (90 km/h = 25 m/s) :

t=0s    → Notification "Suivant: Château dans 7s" (175m avant)
t=7s    → Utilisateur arrive au château
t=15s   → HLS démarre (latence 15s)
Résultat: Audio démarre 200m APRÈS le point ❌

Solution implémentée :

  • ADR-002 mis à jour : Section "Gestion de la Latence et Synchronisation Géolocalisée"
  • Pre-buffering à ETA=30s (15 premières secondes en cache local)
  • ETA adaptatif : 5s si cache prêt, 15s sinon
  • Mesure dynamique de latence HLS par utilisateur

Actions requises :

  • Backend : Endpoint /api/v1/audio/poi/:id/intro (retourne 15s d'audio)
  • Mobile : Service PreBufferService avec cache local (max 100 MB)
  • Mobile : Loader visuel avec progression si buffer > 3s
  • Tests : Validation synchronisation ±10m du POI

🟠 Incohérences Importantes (Sprint 1-2)

#3 : Souveraineté des Données (Français vs Suisse)

Statut : RÉSOLU (ADR-008 mis à jour)

Élément Détail
ADR concernés ADR-004 (CDN, ligne 26), ADR-008 (Auth, mis à jour)
Règle métier Règle 02 (RGPD, section 13.10)
Conflit ADR-004 revendique "100% souveraineté française" mais ADR-008 utilisait Zitadel (entreprise suisse)
Impact Contradiction marketing + risque juridique si promesse "100% français"

Solution implémentée : Self-hosting Zitadel sur OVH France

  • Container Docker sur le même VPS OVH (Gravelines, France)
  • Base de données PostgreSQL partagée (schéma séparé pour Zitadel)
  • Aucune donnée ne transite par des serveurs tiers
  • Souveraineté totale garantie : 100% des données en France
  • Cohérence complète avec ADR-004 (CDN 100% français)

Changements apportés :

  • ADR-008 mis à jour avec architecture self-hosted détaillée
  • TECHNICAL.md mis à jour (tableau + diagramme architecture)
  • Clarification : Zitadel est open source, donc aucune dépendance à une entreprise suisse

Actions complétées :

  • Décision validée : Self-host sur OVH
  • ADR-008 mis à jour avec architecture self-hosted
  • TECHNICAL.md mis à jour

#4 : ORM sqlc vs Types PostGIS

Élément Détail
ADR concernés ADR-013 (ORM, lignes 12, 33-40), ADR-005 (BDD, lignes 47-56)
Règle métier N/A (problème technique pur)
Conflit sqlc génère types Go depuis SQL, mais PostGIS geography/geometry ne mappent pas proprement
Impact Risque de type interface{} ou []byte pour géographie → perte de type safety revendiquée

Nature du problème :

sqlc génère du code Go depuis SQL, mais les types PostGIS (geography, geometry) ne sont pas mappés proprement en Go. Résultat : types opaques ([]byte, interface{}) qui perdent la type safety revendiquée dans ADR-013.

Solution retenue :

  1. Wrapper types Go avec méthodes Scan/Value pour conversion automatique
  2. Utiliser les fonctions PostGIS de conversion :
    • ST_AsGeoJSON() → struct GeoJSON typée
    • ST_AsText() → string WKT
    • geography brut → pgtype.Point (lib pgx)
  3. Documenter le pattern dans ADR-013 section "Gestion des Types PostGIS"

Action :

  • Créer package internal/geo avec wrappers GeoJSON, WKT
  • Mettre à jour ADR-013 section "Types PostGIS"
  • Documenter pattern dans README backend

#5 : Cache Redis (TTL 5min) vs Mode Offline (30 jours)

Élément Détail
ADR concerné ADR-005 (BDD, ligne 60)
Règle métier Règle 11 (Mode Offline, lignes 58-77)
Conflit Redis avec TTL 5min pour géolocalisation, mais contenu offline valide 30 jours
Impact En mode offline, impossible de rafraîchir le cache géolocalisation → POI proches non détectés

Analyse du flux :

Mode connecté:
1. Requête POI proches → Redis (cache 5min)
2. Si miss → PostGIS → Cache Redis
3. ✅ Fonctionne

Mode offline (Règle 11):
1. Requête POI proches → Redis (expiré depuis 6 min)
2. Impossible de requêter PostGIS (pas de réseau) ❌
3. Aucun POI détecté

Solution :

Stratégie de cache à 2 niveaux :

Cache TTL Usage Invalidation
Redis (L1) 5 min Mode connecté Automatique
SQLite local (L2) 30 jours Mode offline Manuelle lors sync

Architecture :

[Mode Connecté]
  → Redis (L1) → PostGIS → Cache local SQLite (L2)

[Mode Offline]
  → SQLite local (L2) uniquement

Action :

  • Backend : Ajouter endpoint /sync/nearby-pois?lat=X&lon=Y&radius=10km
  • Mobile : Créer OfflineCacheService avec SQLite + index spatial
  • Mettre à jour ADR-005 section "Cache" avec stratégie 2 niveaux
  • Règle 11 : Clarifier sync automatique vs manuel

#6 : Package Geofencing vs Permissions iOS/Android

Élément Détail
ADR concerné ADR-014 (Frontend Mobile, ligne 48)
Règle métier Règle 05 (lignes 86-134), Règle 11 (RGPD, lignes 51-86)
Conflit Package geofence_service choisi, mais pas de doc sur compatibilité permissions "optionnelles"
Impact Risque de rejet App Store/Play Store si permissions obligatoires mal gérées

Problématiques :

  1. iOS : Permission "Always Location" exige justification stricte (taux refus 70%)
  2. Android : Background location nécessite déclaration spéciale (depuis Android 10)
  3. Règle métier : Permissions optionnelles (app utilisable sans "Always Location")

Package geofence_service :

  • Supporte iOS/Android
  • ⚠️ Documentation peu claire sur permissions optionnelles
  • ⚠️ Pas de fallback natif si permission refusée

Solution :

Stratégie de permissions progressive :

enum LocationPermissionLevel {
  denied,           // Pas de permission
  whenInUse,        // "Quand l'app est ouverte" (iOS)
  always,           // "Toujours" (iOS) / Background (Android)
}

class GeofencingService {
  Future<void> requestPermissions() async {
    // Étape 1: Demander "When In Use" (moins intrusif)
    var status = await Permission.locationWhenInUse.request();

    if (status.isGranted) {
      // Mode basique: détection seulement app ouverte
      _enableBasicGeofencing();

      // Étape 2 (optionnelle): Proposer upgrade vers "Always"
      _showUpgradePermissionDialog();
    }
  }

  Future<void> upgradeToAlwaysPermission() async {
    // Demandé seulement si utilisateur veut mode piéton complet
    await Permission.locationAlways.request();
  }
}

Actions :

  • Mettre à jour ADR-014 avec stratégie permissions progressive
  • Créer doc "Permissions Strategy" dans /docs/mobile/
  • Tests : Validation rejet App Store (TestFlight beta)

🟡 Incohérences Modérées (Sprint 3-5)

#7 : Points vs Pourcentages dans les Jauges

Élément Détail
ADR concerné ADR-010 (Commandes Volant, lignes 15-21)
Règle métier Règle 03 (Centres d'intérêt, lignes 7-14)
Conflit ADR dit "+2 points", Règle dit "+2**%**" pour même action
Impact Ambiguïté sur calcul : +2 points absolus ou +2% relatifs ?

Exemple du conflit :

  • ADR-010 (ligne 18) : "≥80% d'écoute = +2 points"
  • Règle 03 (ligne 9) : "≥80% d'écoute = +2**%** à la jauge"

Scénario :

Jauge "Automobile" = 45%
Utilisateur écoute 85% d'un podcast voiture

Option A (points absolus): 45 + 2 = 47%
Option B (pourcentage relatif): 45 * 1.02 = 45.9%

Recommandation : Option A (points absolus) pour simplicité

Justification :

  • Progression linéaire plus intuitive
  • Évite effet "rich get richer" (jauges hautes progressent + vite)
  • Cohérent avec système de gamification classique

Actions :

  • Clarifier ADR-010 : remplacer "points" par "points de pourcentage"
  • Clarifier Règle 03 : uniformiser terminologie
  • Backend : Documenter formule exacte dans code

#8 : OAuth2 Complexe vs Email/Password Simple

Élément Détail
ADR concerné ADR-008 (Auth, lignes 12, 52-68)
Règle métier Règle 01 (Auth, lignes 5-10)
Conflit ADR implémente OAuth2 PKCE complet, mais Règle dit " Pas d'OAuth tiers, email/password uniquement"
Impact Sur-ingénierie : OAuth2 conçu pour tiers (Google, Facebook) mais non utilisé ici

Analyse :

  • ADR-008 : Architecture OAuth2 avec PKCE, refresh tokens, etc.
  • Règle 01 : " Pas de Google, Apple, Facebook OAuth"

Zitadel supporte :

  • OAuth2 (pour intégrations tierces)
  • Email/Password natif (ce dont on a besoin)

Question : Pourquoi implémenter OAuth2 si pas de tiers ?

Options :

Option Complexité Justification
A. Garder OAuth2 Haute Future-proof pour API partenaires
B. Session simple Basse Suffit pour MVP email/password

Recommandation : Option A (garder OAuth2) si :

  • Vision long-terme : API pour partenaires (créateurs, annonceurs)
  • Coût marginal : Zitadel gère OAuth2 nativement

Sinon Option B (session simple) si MVP pur.

Actions :

  • Décision : Confirmer besoin OAuth2 avec product owner
  • Si A : Mettre à jour Règle 01 "OAuth tiers en Phase 2"
  • Si B : Simplifier ADR-008 (session JWT classique)

#9 : GeoIP Database (MaxMind)

Élément Détail
ADR concerné ADR-005 (non mentionné)
Règle métier Règle 02 (RGPD, lignes 146-149)
Conflit Règle cite "MaxMind GeoLite2 (gratuit)", mais offre a changé en 2019
Impact Coût caché : MaxMind nécessite compte + API calls (plus de base offline gratuite)

Historique :

  • Avant 2019 : GeoLite2 database téléchargeable gratuitement
  • Après 2019 : Compte requis + limite 1000 requêtes/jour (gratuit)
  • Dépassement : 0.003$/requête

Utilisation RoadWave :

  • Mode dégradé (sans GPS) → GeoIP pour localisation approximative
  • Estimation : 10% des utilisateurs (1000 users × 10% = 100 requêtes/jour)

Options :

Option Coût/mois Précision Maintenance
A. MaxMind API ~10€ ±50 km Nulle
B. IP2Location Lite Gratuit ±50 km Maj mensuelle
C. Self-hosted GeoIP Gratuit ±50 km +2h/mois

Recommandation : Option C (self-hosted avec IP2Location Lite DB)

Architecture :

[Backend Go] → [GeoIP Service]
                     ↓
               [IP2Location SQLite DB]
               (màj mensuelle via cron)

Actions :

  • Backend : Implémenter service GeoIP avec IP2Location
  • DevOps : Cron job màj mensuelle de la DB
  • Mettre à jour Règle 02 ligne 147

#10 : Tests BDD Synchronisés (Backend + Mobile)

Élément Détail
ADR concernés ADR-007 (Tests BDD, lignes 30-68), ADR-015 (Stratégie, lignes 59-62)
Règle métier Toutes (Gherkin)
Conflit Features partagées /features, step definitions séparées → qui exécute quoi ?
Impact Risque de divergence backend/mobile si tests pas synchronisés

Architecture actuelle :

/features/*.feature           (partagé)
/backend/tests/bdd/           (step definitions Go)
/mobile/tests/bdd/            (step definitions Dart)

Question non résolue :

  • Un test "Authentification" concerne-t-il backend ET mobile ?
  • Qui est responsable de l'exécuter ?
  • Si les implémentations divergent ?

Recommandation : Catégoriser les features

/features/
  /api/          → Backend uniquement (tests API REST)
  /ui/           → Mobile uniquement (tests interface)
  /e2e/          → End-to-end (backend + mobile ensemble)

Exemple :

# features/api/authentication.feature (backend)
Scénario: Création de compte via API
  Étant donné une requête POST /api/v1/auth/register
  Quand j'envoie email "test@example.com" et password "Pass123!"
  Alors le statut HTTP est 201
  Et la réponse contient un token JWT

# features/ui/authentication.feature (mobile)
Scénario: Création de compte via interface
  Étant donné que je suis sur l'écran d'inscription
  Quand je saisis email "test@example.com"
  Et je saisis mot de passe "Pass123!"
  Et je clique sur "S'inscrire"
  Alors je vois l'écran d'accueil

Actions :

  • Réorganiser /features en 3 catégories (api, ui, e2e)
  • Mettre à jour ADR-007 avec convention de nommage
  • CI/CD : Séparer jobs backend-bdd et mobile-bdd

#11 : 70/30 Split Paiements (Vérification Manquante)

Élément Détail
ADR concerné ADR-009 (Paiement, lignes 32-52)
Règle métier Règle 18 (Monétisation, non fournie complète)
Conflit ADR assume 70/30 split sans référence règle métier
Impact Risque de mauvaise répartition revenus créateurs

ADR-009 spécifie :

  • 70% créateur
  • 30% plateforme

Question : Est-ce validé par les règles métier business ?

Actions :

  • Lire Règle 18 (Monétisation Créateurs) complète
  • Vérifier si 70/30 correspond aux attentes
  • Si divergence : mettre à jour ADR-009

#12 : Monorepo Path Filters vs Features Partagées

Élément Détail
ADR concernés ADR-016 (Monorepo, ligne 80), ADR-015 (Tests)
Règle métier N/A (problème CI/CD)
Conflit Path filters pour éviter rebuild tout, mais features partagées déclenchent tout
Impact Optimisation CI/CD inefficace

Problème :

# .github/workflows/backend.yml
on:
  push:
    paths:
      - 'backend/**'
      - 'features/**'  # ❌ Change sur n'importe quel .feature → rebuild backend

Solution : Path filters par catégorie (suite de #10)

# .github/workflows/backend.yml
on:
  push:
    paths:
      - 'backend/**'
      - 'features/api/**'   # ✅ Seulement features API
      - 'features/e2e/**'   # ✅ E2E impacte backend

# .github/workflows/mobile.yml
on:
  push:
    paths:
      - 'mobile/**'
      - 'features/ui/**'    # ✅ Seulement features UI
      - 'features/e2e/**'   # ✅ E2E impacte mobile

Actions :

  • Implémenter catégorisation features (dépend de #10)
  • Mettre à jour workflows CI/CD
  • Mettre à jour ADR-016 avec stratégie path filters

#13 : Coûts Email (Transition Free → Paid)

Élément Détail
ADR concernés ADR-018 (Email, lignes 49-52), ADR-017 (Hébergement)
Règle métier N/A (économique)
Conflit ADR cite "gratuit" mais limite 9000 emails/mois → plan transition manquant
Impact Coût surprise lors de la croissance

ADR-018 spécifie :

  • Brevo gratuit : 300 emails/jour = 9000/mois
  • Phase MVP : 0-10K utilisateurs

Calcul réaliste :

Emails par utilisateur/mois:
- Vérification email: 1
- Reset password: 0.1 (10%)
- Notifications (opt-in 30%): 4
- Paiements créateurs (5%): 1

Total: ~2 emails/user/mois (moyenne)

10K users × 2 = 20K emails/mois → dépassement tier gratuit

Coût Brevo :

  • Free: 0-9K emails
  • Lite: 19€/mois (20K emails)
  • Business: 49€/mois (50K emails)

Actions :

  • Mettre à jour ADR-018 avec projection coûts
  • Implémenter alertes (90% quota atteint)
  • Plan B : Self-hosted SMTP (Postfix) si budget serré

#14 : Kubernetes vs VPS MVP

Élément Détail
ADR concernés ADR-017 (Hébergement, ligne 12), ADR-001 (Go, ligne 27)
Règle métier N/A (infrastructure)
Conflit ADR-001 justifie Go pour "Kubernetes first-class", mais ADR-017 utilise VPS simple
Impact Sur-architecture : pourquoi choisir Go pour K8s si pas utilisé ?

Analyse :

  • ADR-001 : Go choisi notamment pour "excellent support Kubernetes"
  • ADR-017 : MVP sur OVH VPS Essential (single VM, Docker Compose)
  • ADR-012 : Mentionne migration K8s "à 1M+ users"

Question : Justification K8s prématurée ?

Réponse : Non, acceptable si :

  • Vision long-terme claire (1M users = besoin K8s)
  • Go apporte autres avantages (perf, concurrence, typing)
  • Coût marginal (Go vs Node.js comparable en complexité MVP)

Recommandation : Clarifier la vision dans ADR

Actions :

  • Mettre à jour ADR-001 : "Go pour scalabilité future (K8s), mais aussi perf/typage"
  • ADR-017 : Ajouter section "Roadmap Infrastructure" (VPS → K8s)

🟢 Incohérences Mineures (Clarification)

#15 : Unlike Manuel sur Contenu Auto-liké

Élément Détail
ADR concerné ADR-010 (ligne 15-21)
Règle métier Règle 05 (lignes 248-323), Règle 03 (lignes 93-99)
Conflit Auto-like +2% documenté, mais unlike manuel non spécifié
Impact Ambiguïté : faut-il annuler (+2%) si unlike ?

Scénario :

1. Utilisateur écoute 85% → auto-like → jauge +2%
2. Utilisateur clique "Unlike" (toggle)
3. Que se passe-t-il ?
   Option A: Jauge -2% (annulation)
   Option B: Jauge reste (unlike n'affecte pas)

Recommandation : Option A (annulation symétrique)

Justification : Unlike explicite = signal fort "pas intéressé"

Actions :

  • Clarifier Règle 03 : section "Unlike Manuel"
  • Backend : Implémenter logique annulation dans GaugeService

Plan d'Action Global

Phase 1 : Résolutions Critiques (Avant Implémentation)

# Tâche Responsable Effort Deadline
1 Créer ADR-019 (Notifications) Architecture 2h Fait
2 Mettre à jour ADR-002 (Pre-buffering) Architecture 1h Fait
3 Implémenter WebSocket backend Backend Lead 3j Sprint 1
4 Implémenter Pre-buffer mobile Mobile Lead 2j Sprint 1

Phase 2 : Résolutions Importantes (Sprint 1-2)

# Tâche Responsable Effort Statut
5 Décision souveraineté (Zitadel self-host) CTO 1h Fait
6 Package geo types (PostGIS) Backend 1j Sprint 2
7 Cache 2 niveaux (Redis + SQLite) Backend + Mobile 3j Sprint 2
8 Stratégie permissions progressive Mobile 2j Sprint 2

Phase 3 : Résolutions Modérées (Sprint 3-5)

# Tâche Responsable Effort Deadline
9-15 Clarifications ADR/Règles Tech Writer 5j Sprint 3-4
16 Réorganisation features BDD QA Lead 2j Sprint 4
17 Optimisation CI/CD path filters DevOps 1j Sprint 5

Métriques de Suivi

Métrique Valeur Initiale Cible Actuel
Incohérences CRITICAL 2 0 0 (2/2 résolues)
Incohérences HIGH 4 0 3 (1/4 résolue)
Incohérences MODERATE 8 ≤2 8
ADR à jour 66% (12/18) 100% 78% (14/18)
Coverage documentation N/A >90% 80%

Dernière mise à jour : 2026-01-30


Contacts et Ressources

  • Analyse complète : Ce document
  • ADR-019 : /docs/adr/019-notifications-geolocalisees.md
  • ADR-002 (mis à jour) : /docs/adr/002-protocole-streaming.md
  • Questions : Créer une issue GitHub avec tag [architecture]

Prochaine revue : 2026-02-15 (après Sprint 2)