veza/veza-docs/ORIGIN/ORIGIN_ERROR_PREVENTION_GUIDE.md
2026-03-05 19:22:31 +01:00

23 KiB

ORIGIN_ERROR_PREVENTION_GUIDE.md

📋 RÉSUMÉ EXÉCUTIF

Ce document définit le système complet de prévention d'erreurs pour le projet Veza. Il s'intègre parfaitement à la méthodologie ORIGIN_ existante et doit être appliqué AVANT de commencer toute nouvelle tâche d'implémentation. Ce guide garantit qu'aucune erreur récurrente ne sera introduite dans le codebase.

Dernière mise à jour : 2026-03-04
Statut : Document de référence officiel
Version : 2.0.0


🎯 OBJECTIFS

Objectif Principal

Établir un système de prévention d'erreurs qui garantit qu'aucune erreur récurrente ne sera introduite dans les futures implémentations.

Objectifs Secondaires

  • Réduire le temps de correction d'erreurs (< 5% du temps de développement)
  • Maintenir la qualité du code (0 erreurs P0/P1)
  • Faciliter l'onboarding (checklists claires)
  • Standardiser les patterns de code (templates validés)

🔒 RÈGLES IMMUABLES

  1. Pre-Flight Check OBLIGATOIRE avant toute nouvelle tâche
  2. Templates OBLIGATOIRES pour créer de nouveaux fichiers
  3. Quality Gates BLOQUANTS en CI/CD
  4. Aucune exception sans approbation Lead Engineer
  5. Documentation OBLIGATOIRE de tout nouveau pattern d'erreur

📖 TABLE DES MATIÈRES

  1. Pre-Flight Checklists
  2. Implementation Patterns
  3. Validation Gates
  4. Templates de Code
  5. Workflow Intégré
  6. Références

1. PRE-FLIGHT CHECKLISTS

1.1 Checklist Globale (Avant TOUTE Tâche)

OBLIGATOIRE : Exécuter cette checklist avant de commencer une nouvelle tâche.

# Exécuter le script de pre-flight check
./scripts/pre-flight-check.sh

Checklist manuelle :

  • Aucune erreur P0/P1 existante (vérifier avec ./scripts/discover-errors.sh)
  • Tests existants passent (go test ./... / npm test)
  • Linter ne produit aucune erreur (golangci-lint run / npm run lint)
  • Code est à jour avec main (git pull origin main)
  • Branche créée pour la tâche (git checkout -b feature/TXXXX-description)

1.2 Checklist Backend Go

Avant de créer/modifier du code Go :

  • Vérifier qu'aucun import cycle ne sera créé

    # Visualiser le graphe de dépendances
    cd veza-backend-api
    go mod graph | grep -i "cycle"
    
  • Valider la cohérence des types (string vs *string)

    • Consulter ORIGIN_ERROR_PATTERNS.md PAT-002
    • Décider une stratégie cohérente avant de modifier un modèle
  • Vérifier que tous les packages importés existent

    go build ./...  # Doit réussir sans erreur
    
  • Tests unitaires du composant parent passent

    go test ./internal/services/... -v
    
  • go vet ne produit aucun warning

    go vet ./...
    
  • golangci-lint ne produit aucune erreur

    golangci-lint run
    
  • Vérifier la configuration JWT (issuer/audience) au démarrage

    // Dans main.go ou init — valider dès le boot
    if config.JWTIssuer == "" || config.JWTAudience == "" {
        log.Fatal("JWT_ISSUER and JWT_AUDIENCE must be set")
    }
    
  • Propager le contexte de la requête (c.Request.Context()) — jamais context.Background() dans un handler

    # Linter rule (golangci-lint contextcheck)
    golangci-lint run --enable contextcheck
    
  • Goroutines avec mécanisme d'arrêt (context.Context + sync.WaitGroup)

    • Vérifier avec goleak dans les tests : go.uber.org/goleak
  • Pagination bornée — vérifier que MaxPageSize est appliqué

    grep -rn "parsePagination\|MaxPageSize" internal/
    
  • Réponses d'erreur standardisées — aucun gin.H{"error" dans les handlers

    grep -rn 'gin.H{"error"' internal/handlers/  # Doit retourner 0 résultat
    

Patterns à éviter :

  • Services qui importent handlers
  • Handlers qui importent services directement
  • Types partagés dans les packages qui les utilisent
  • Mélange de string et *string pour champs optionnels
  • context.Background() dans les handlers HTTP
  • Goroutines sans mécanisme d'arrêt (go func() nu)
  • Pagination sans MaxPageSize
  • gin.H{"error": ...} au lieu de RespondWithAppError
  • JWT parsing sans validation issuer/audience

Patterns sûrs :

  • Interfaces dans internal/types/ ou internal/interfaces/
  • Services dépendent uniquement d'interfaces
  • Handlers dépendent uniquement d'interfaces
  • Types cohérents (toujours string OU toujours *string)
  • c.Request.Context() propagé dans toute la chaîne handler → service → repo
  • Goroutines avec ctx.Done() + sync.WaitGroup
  • parsePagination() centralisé avec MaxPageSize = 100
  • RespondWithAppError() pour toutes les erreurs
  • JWT issuer/audience validés au parsing et vérifiés au boot

1.3 Checklist Frontend React/TypeScript

Avant de créer/modifier du code TypeScript/React :

  • Vérifier que tsconfig.json est correct
    cd apps/web
    npx tsc --noEmit --strict
    
  • Linter ne produit aucune erreur sur fichiers modifiés
    npm run lint
    
  • Tests existants passent avant modification
    npm test -- --run
    
  • Types TypeScript sont stricts (pas de any)
    • Vérifier avec tsc --noEmit --strict
    • Utiliser unknown si le type est vraiment inconnu
  • JSX syntax validée (Prettier)
    npm run format
    
  • Pas de console.log en production
    • Utiliser un logger configuré
    • ESLint devrait bloquer automatiquement

Patterns à éviter :

  • Regex non terminées dans les tests
  • Tags JSX non fermés
  • Types any explicites
  • Variables non utilisées
  • console.log en production

Patterns sûrs :

  • Types explicites pour toutes les fonctions
  • Self-closing tags JSX (<Component />)
  • Mocks configurés pour tous les tests
  • Logger au lieu de console.log

1.4 Checklist Services Rust

Avant de créer/modifier du code Rust :

  • cargo check passe
    cd veza-chat-server  # ou veza-stream-server
    cargo check
    
  • cargo clippy ne produit aucun warning
    cargo clippy -- -D warnings
    
  • SQLx queries validées avec schema
    cargo sqlx prepare --check
    
  • Tests unitaires passent
    cargo test
    

Patterns à éviter :

  • unwrap() en production (utiliser ? ou gestion d'erreur)
  • Types i32 pour IDs (utiliser Uuid)
  • Queries SQL non validées

Patterns sûrs :

  • Gestion d'erreur avec Result<T, E>
  • Types Uuid pour tous les IDs
  • SQLx queries validées avec sqlx::query!

2. IMPLEMENTATION PATTERNS

2.1 Backend Service Pattern

Pattern sûr pour créer un nouveau service :

// ✅ PATTERN SÛR - Évite import cycles
// internal/services/user_service.go
package services

import (
    "context"
    "veza-backend-api/internal/types"  // Interfaces dans package neutre
    "veza-backend-api/internal/models"
)

// UserService implémente l'interface définie dans types
type UserService struct {
    repo types.UserRepository  // Dépend de l'interface, pas de l'implémentation
    logger types.Logger
}

// NewUserService crée une nouvelle instance
func NewUserService(repo types.UserRepository, logger types.Logger) *UserService {
    return &UserService{
        repo: repo,
        logger: logger,
    }
}

// CreateUser crée un nouvel utilisateur
func (s *UserService) CreateUser(ctx context.Context, req *CreateUserRequest) (*models.User, error) {
    // Validation
    if err := s.validateCreateRequest(req); err != nil {
        return nil, err
    }
    
    // Business logic
    user := &models.User{
        Email:    req.Email,
        Username: req.Username,
    }
    
    // Persistence
    if err := s.repo.Create(ctx, user); err != nil {
        return nil, fmt.Errorf("failed to create user: %w", err)
    }
    
    return user, nil
}

// validateCreateRequest valide la requête
func (s *UserService) validateCreateRequest(req *CreateUserRequest) error {
    if req.Email == "" {
        return types.ErrValidation("email is required")
    }
    // ... autres validations
    return nil
}

Interfaces dans package séparé :

// internal/types/interfaces.go
package types

import (
    "context"
    "veza-backend-api/internal/models"
)

// UserRepository définit les opérations de persistence
type UserRepository interface {
    Create(ctx context.Context, user *models.User) error
    FindByID(ctx context.Context, id uuid.UUID) (*models.User, error)
    FindByEmail(ctx context.Context, email string) (*models.User, error)
}

// Logger définit les opérations de logging
type Logger interface {
    Info(msg string, fields ...interface{})
    Error(msg string, fields ...interface{})
    Debug(msg string, fields ...interface{})
}

2.2 Backend Handler Pattern

Pattern sûr pour créer un nouveau handler :

// ✅ PATTERN SÛR - Évite import cycles
// internal/handlers/user_handlers.go
package handlers

import (
    "net/http"
    "github.com/gin-gonic/gin"
    "veza-backend-api/internal/types"  // Interfaces seulement
    "veza-backend-api/internal/models"
)

// UserHandlers gère les requêtes HTTP pour les utilisateurs
type UserHandlers struct {
    userService types.UserService  // Interface, pas l'implémentation
}

// NewUserHandlers crée une nouvelle instance
func NewUserHandlers(userService types.UserService) *UserHandlers {
    return &UserHandlers{
        userService: userService,
    }
}

// CreateUser gère POST /api/v1/users
func (h *UserHandlers) CreateUser(c *gin.Context) {
    var req CreateUserRequest
    if err := c.ShouldBindJSON(&req); err != nil {
        c.JSON(http.StatusBadRequest, gin.H{"error": "invalid request"})
        return
    }
    
    user, err := h.userService.CreateUser(c.Request.Context(), &req)
    if err != nil {
        handleError(c, err)
        return
    }
    
    c.JSON(http.StatusCreated, user)
}

// handleError gère les erreurs de manière cohérente
func handleError(c *gin.Context, err error) {
    // Logique de gestion d'erreur centralisée
    // ...
}

2.3 Frontend Component Pattern

Pattern sûr pour créer un nouveau composant React :

// ✅ PATTERN SÛR - Évite import hell
// src/components/user/UserProfile.tsx
import type { User } from '@/types';  // Types séparés
import { useUserStore } from '@/stores/user';  // State management
import { userService } from '@/services/user';  // API calls
import { useUser } from '@/hooks/useUser';  // Custom hook

interface UserProfileProps {
  userId: string;
  className?: string;
}

// Component NE fait PAS de logic business
export const UserProfile: React.FC<UserProfileProps> = ({ 
  userId, 
  className 
}) => {
  // Custom hook gère la logique
  const { data: user, isLoading, error } = useUser(userId);
  
  // États de chargement et d'erreur
  if (isLoading) return <Spinner />;
  if (error) return <ErrorMessage error={error} />;
  if (!user) return <div>User not found</div>;
  
  // Rendu simple
  return (
    <div className={className}>
      <h2>{user.name}</h2>
      <p>{user.email}</p>
    </div>
  );
};

Custom Hook Pattern :

// ✅ PATTERN SÛR - Logique réutilisable
// src/hooks/useUser.ts
import { useQuery } from '@tanstack/react-query';
import { userService } from '@/services/user';

export function useUser(userId: string) {
  return useQuery({
    queryKey: ['user', userId],
    queryFn: () => userService.getUser(userId),
    enabled: !!userId,
  });
}

2.4 Frontend Service Pattern

Pattern sûr pour créer un nouveau service API :

// ✅ PATTERN SÛR - Types explicites
// src/services/user.ts
import type { User, CreateUserRequest } from '@/types';
import { apiClient } from './apiClient';

export const userService = {
  async getUser(userId: string): Promise<User> {
    const response = await apiClient.get<User>(`/api/v1/users/${userId}`);
    return response.data;
  },
  
  async createUser(request: CreateUserRequest): Promise<User> {
    const response = await apiClient.post<User>('/api/v1/users', request);
    return response.data;
  },
  
  async updateUser(userId: string, request: Partial<User>): Promise<User> {
    const response = await apiClient.put<User>(`/api/v1/users/${userId}`, request);
    return response.data;
  },
};

2.5 Test Pattern (Frontend)

Pattern sûr pour écrire des tests :

// ✅ PATTERN SÛR - Mocks configurés
// src/components/user/UserProfile.test.tsx
import { describe, it, expect, vi, beforeEach } from 'vitest';
import { render, screen } from '@testing-library/react';
import { UserProfile } from './UserProfile';
import { useUser } from '@/hooks/useUser';

// Mock le hook
vi.mock('@/hooks/useUser');

describe('UserProfile', () => {
  beforeEach(() => {
    vi.clearAllMocks();
  });
  
  it('should render user profile', () => {
    // Arrange
    const mockUser = {
      id: '123',
      name: 'John Doe',
      email: 'john@example.com',
    };
    
    vi.mocked(useUser).mockReturnValue({
      data: mockUser,
      isLoading: false,
      error: null,
    } as any);
    
    // Act
    render(<UserProfile userId="123" />);
    
    // Assert
    expect(screen.getByText('John Doe')).toBeInTheDocument();
    expect(screen.getByText('john@example.com')).toBeInTheDocument();
  });
  
  it('should show loading state', () => {
    // Arrange
    vi.mocked(useUser).mockReturnValue({
      data: null,
      isLoading: true,
      error: null,
    } as any);
    
    // Act
    render(<UserProfile userId="123" />);
    
    // Assert
    expect(screen.getByRole('status')).toBeInTheDocument();  // Spinner
  });
});

2.6 Audit Prevention Patterns (2026-03-04)

Les patterns suivants ont été identifiés lors de l'audit du 2026-03-04. Chaque pattern a un mécanisme de prévention automatisé.

JWT Configuration Validation at Startup

// ✅ PATTERN SÛR - Valider la configuration JWT au démarrage
// internal/auth/config.go
func ValidateJWTConfig(cfg *config.Config) error {
    if cfg.JWTSecret == "" {
        return fmt.Errorf("JWT_SECRET is required")
    }
    if cfg.JWTIssuer == "" {
        return fmt.Errorf("JWT_ISSUER is required")
    }
    if cfg.JWTAudience == "" {
        return fmt.Errorf("JWT_AUDIENCE is required")
    }
    if cfg.JWTExpiration <= 0 {
        return fmt.Errorf("JWT_EXPIRATION must be positive")
    }
    return nil
}

// main.go — appeler au boot, fail-fast si invalide
func main() {
    cfg := config.Load()
    if err := auth.ValidateJWTConfig(cfg); err != nil {
        log.Fatalf("JWT configuration error: %v", err)
    }
}

Context Propagation Enforcement

# ✅ PATTERN SÛR - golangci-lint config (.golangci.yml)
linters:
  enable:
    - contextcheck

linters-settings:
  contextcheck:
    # Détecte context.Background() et context.TODO() dans les fonctions
    # qui reçoivent déjà un context en paramètre
// ✅ PATTERN SÛR - Toujours propager le contexte
func (h *Handler) GetResource(c *gin.Context) {
    ctx := c.Request.Context()
    result, err := h.service.Get(ctx, id)     // ctx propagé
}

func (s *Service) Get(ctx context.Context, id string) (*Resource, error) {
    return s.repo.FindByID(ctx, id)           // ctx propagé
}

Goroutine Lifecycle Management

// ✅ PATTERN SÛR - Goroutine avec lifecycle complet
type BackgroundWorker struct {
    ctx    context.Context
    cancel context.CancelFunc
    wg     sync.WaitGroup
}

func NewBackgroundWorker(parentCtx context.Context) *BackgroundWorker {
    ctx, cancel := context.WithCancel(parentCtx)
    return &BackgroundWorker{ctx: ctx, cancel: cancel}
}

func (w *BackgroundWorker) Start(task func(ctx context.Context)) {
    w.wg.Add(1)
    go func() {
        defer w.wg.Done()
        task(w.ctx)
    }()
}

func (w *BackgroundWorker) Stop() {
    w.cancel()
    w.wg.Wait()
}

Pagination Limit Enforcement

// ✅ PATTERN SÛR - Pagination centralisée avec limites
// internal/api/pagination.go
const (
    DefaultPageSize = 20
    MaxPageSize     = 100
)

type PaginationParams struct {
    Limit  int
    Offset int
}

func ParsePagination(c *gin.Context) PaginationParams {
    limit, _ := strconv.Atoi(c.DefaultQuery("limit", strconv.Itoa(DefaultPageSize)))
    offset, _ := strconv.Atoi(c.DefaultQuery("offset", "0"))

    if limit <= 0 {
        limit = DefaultPageSize
    }
    if limit > MaxPageSize {
        limit = MaxPageSize
    }
    if offset < 0 {
        offset = 0
    }
    return PaginationParams{Limit: limit, Offset: offset}
}

Error Response Standardization

// ✅ PATTERN SÛR - Réponse d'erreur standardisée unique
// internal/api/errors.go

// RespondWithAppError gère TOUTES les réponses d'erreur
func RespondWithAppError(c *gin.Context, err error) {
    var appErr *apperrors.AppError
    if errors.As(err, &appErr) {
        c.JSON(appErr.HTTPStatus(), appErr.ToResponse())
        return
    }
    c.JSON(http.StatusInternalServerError, apperrors.InternalError().ToResponse())
}

// INTERDIT : c.JSON(400, gin.H{"error": "..."})
// OBLIGATOIRE : RespondWithAppError(c, apperrors.NewValidationError("...", err))

3. VALIDATION GATES

3.1 Pre-Commit Gates (Husky)

Configuration automatique : Les hooks Husky sont configurés dans .husky/pre-commit.

Gates activés :

  • Formatage automatique (Prettier, gofmt)
  • Linter (ESLint, golangci-lint)
  • Tests unitaires rapides (go test -short, npm test -- --run)
  • Type checking (TypeScript)

Si un gate échoue :

  • Le commit est bloqué
  • Corriger les erreurs
  • Réessayer le commit

3.2 Pre-Merge Gates (GitHub Actions)

Configuration : .github/workflows/error-prevention.yml

Gates activés :

  1. Architecture Validation

    • Vérification des import cycles (Go)
    • Vérification de la structure des packages
  2. Type Safety

    • TypeScript strict mode
    • Go type checking
  3. Test Coverage

    • Coverage ≥ 80% pour nouveau code
    • Tous les tests passent
  4. Linter

    • Zero linter errors
    • Zero linter warnings (ou < 5)
  5. Build

    • Backend compile sans erreur
    • Frontend build réussit

Si un gate échoue :

  • La PR ne peut pas être mergée
  • Corriger les erreurs
  • Push les corrections
  • Les gates se relancent automatiquement

3.3 Pre-Deployment Gates

Gates activés :

  • Tous les tests passent (unit, integration, E2E)
  • Coverage ≥ 80% (global)
  • Performance tests passent
  • Security scan pass
  • Smoke tests passent en staging

4. TEMPLATES DE CODE

4.1 Utilisation des Templates

Avant de créer un nouveau fichier :

  1. Consulter la liste des templates disponibles dans /dev-environment/templates/
  2. Copier le template approprié
  3. Remplacer les placeholders ({{PLACEHOLDER}})
  4. Adapter selon les besoins spécifiques

Exemple :

# Créer un nouveau service Go
cp dev-environment/templates/backend-service.template.go \
   veza-backend-api/internal/services/my_service.go

# Éditer et remplacer les placeholders
# {{SERVICE_NAME}} → MyService
# {{PACKAGE_NAME}} → myservice

4.2 Templates Disponibles

Backend Go :

  • backend-service.template.go - Service avec interface
  • backend-handler.template.go - Handler HTTP
  • backend-repository.template.go - Repository pattern

Frontend React/TypeScript :

  • frontend-component.template.tsx - Composant React
  • frontend-hook.template.ts - Custom hook
  • frontend-service.template.ts - Service API

Rust :

  • rust-service.template.rs - Service Rust

Voir : /dev-environment/templates/ pour les templates complets.


5. WORKFLOW INTÉGRÉ

5.1 Workflow Complet

graph TD
    A[Nouvelle Tâche TXXXX] --> B[Pre-Flight Check]
    B -->|FAIL| C[Corriger Erreurs Existantes]
    C --> B
    B -->|PASS| D[Choisir Template]
    D --> E[Implémenter avec Pattern Sûr]
    E --> F[Tests Unitaires TDD]
    F --> G{Coverage ≥ 80%?}
    G -->|Non| F
    G -->|Oui| H[Lint Check]
    H --> I{Zero Errors?}
    I -->|Non| E
    I -->|Oui| J[Pre-Commit Hook]
    J --> K{Hook Pass?}
    K -->|Non| E
    K -->|Oui| L[Commit]
    L --> M[Push & Create PR]
    M --> N[CI/CD Gates]
    N --> O{All Gates Pass?}
    O -->|Non| E
    O -->|Oui| P[Code Review]
    P --> Q{Approved?}
    Q -->|Non| E
    Q -->|Oui| R[Merge]

5.2 Checklist par Étape

Étape 1: Pre-Flight Check

  • Exécuter ./scripts/pre-flight-check.sh
  • Vérifier qu'aucune erreur P0/P1 existe
  • Tests existants passent
  • Linter clean

Étape 2: Implémentation

  • Utiliser template approprié
  • Suivre pattern sûr (voir section 2)
  • Éviter les anti-patterns (voir ORIGIN_ERROR_PATTERNS.md)
  • Tests en TDD (Red-Green-Refactor)

Étape 3: Validation Locale

  • Tests unitaires passent
  • Coverage ≥ 80%
  • Linter zero errors
  • Type check passe
  • Build réussit

Étape 4: Commit

  • Pre-commit hook passe
  • Message de commit suit format: TXXXX: type: description
  • Commit atomique (une fonctionnalité par commit)

Étape 5: PR & Review

  • CI/CD gates passent
  • Code review approuvé (2 reviewers)
  • Documentation mise à jour si nécessaire

6. RÉFÉRENCES

Documents ORIGIN

  • ORIGIN_ERROR_PATTERNS.md - Catalogue des patterns d'erreurs
  • ORIGIN_CODE_STANDARDS.md - Standards de code
  • ORIGIN_MASTER_ARCHITECTURE.md - Architecture du projet
  • ORIGIN_TESTING_STRATEGY.md - Stratégie de tests
  • ORIGIN_IMPLEMENTATION_TASKS.md - Tâches d'implémentation

Scripts Utilitaires

  • ./scripts/pre-flight-check.sh - Validation pré-tâche
  • ./scripts/discover-errors.sh - Découverte d'erreurs
  • ./scripts/generate-error-summary.sh - Rapport d'erreurs

Outils

  • Go : go vet, golangci-lint, go test
  • TypeScript : tsc, eslint, prettier
  • Rust : cargo check, cargo clippy, cargo test

CHECKLIST DE VALIDATION

Avant de Commencer une Tâche

  • Pre-flight check exécuté et passé
  • Template choisi et copié
  • Pattern sûr identifié
  • Checklist spécifique (Backend/Frontend/Rust) complétée

Pendant l'Implémentation

  • Pattern sûr suivi
  • Anti-patterns évités
  • Tests écrits en TDD
  • Linter activé en temps réel

Avant le Commit

  • Tests passent
  • Coverage ≥ 80%
  • Linter zero errors
  • Type check passe
  • Build réussit

Avant le Merge

  • CI/CD gates passent
  • Code review approuvé
  • Documentation mise à jour

🔄 MAINTENANCE

Mise à Jour du Guide

  • Fréquence : Mensuelle ou après découverte d'un nouveau pattern
  • Responsable : Lead Engineers
  • Processus :
    1. Identifier nouveau pattern d'erreur
    2. Documenter dans ORIGIN_ERROR_PATTERNS.md
    3. Mettre à jour ce guide si nécessaire
    4. Communiquer à l'équipe

Amélioration Continue

  • Analyser les erreurs qui passent malgré les gates
  • Améliorer les templates si nécessaire
  • Ajuster les checklists selon les retours

Dernière mise à jour : 2026-03-04
Version : 2.0.0
Statut : APPROUVÉ ET VERROUILLÉ

"Prevention is better than cure."