Aller au contenu

PD-60 — Rétrospective

1. Contexte

Champ Valeur
Story ID PD-60
Titre Document upload avec acte probatoire
Domaine docs-api
Projet backend
Date complétion 2026-01-XX
Verdict ACCEPTÉ

2. Métriques

Métrique Valeur
Tests contractuels 102/102 PASS (91 Jest + 11 Vitest)
Invariants 17 (INV-60-01..17)
Critères d'acceptation 21 (CA-60-01..21)
Décisions contractuelles 13 (PC-60-01..13)
Itérations acceptabilité 4

3. Learnings clés

  • Les guards et pipes NestJS échappent au code métier : Toute exigence d'audit exhaustive nécessite un mécanisme au niveau framework (exception filter), pas uniquement au niveau service.

  • Les exclusions de périmètre contractuelles doivent être rappelées à chaque gate : Les décisions PC-60-13 et PC-60-12 documentées dans la spec ont été oubliées lors de l'acceptabilité, causant des itérations superflues.

  • La dualité Jest/Vitest est un coût structurel des dépendances ESM-only : jose v6 (JWS) est ESM-only, incompatible avec Jest (CJS). Décision à prendre au moment du choix de la dépendance.

  • L'injection de dépendances NestJS impose des contraintes sur les tests unitaires : @UseFilters(Class) (pour DI) vs @UseFilters(new Instance()) a des implications sur la configuration des modules de test.

  • La transaction unique englobante est le mécanisme le plus sûr pour l'atomicité probatoire : Encapsuler l'ensemble (audit + reçu + persistance) dans une seule transaction DB garantit l'atomicité sans complexité de saga.

4. Patterns applicables

Nouveau pattern : Exception filter pour audit exhaustif

@Catch()
export class DepositAuditExceptionFilter implements ExceptionFilter {
  constructor(private readonly auditService: DepositAuditService) {}

  catch(exception: unknown, host: ArgumentsHost) {
    const ctx = host.switchToHttp();
    const request = ctx.getRequest<Request>();
    const response = ctx.getResponse<Response>();

    const status = exception instanceof HttpException
      ? exception.getStatus()
      : HttpStatus.INTERNAL_SERVER_ERROR;

    // Ne pas re-auditer les exceptions déjà auditées par le service
    if (![409, 422, 404].includes(status)) {
      this.auditService.recordRejection({
        action: 'DEPOSIT_ATTEMPT',
        reason: this.extractReason(exception),
        clientRequestId: request.body?.client_request_id,
        status,
      });
    }

    response.status(status).json(this.formatError(exception, status));
  }
}

Pattern confirmé : Transaction englobante pour atomicité

async depositDocument(dto: DepositDto): Promise<DepositResult> {
  return this.dataSource.transaction(async (manager) => {
    // Phase 0: Advisory lock (idempotence)
    await manager.query(
      'SELECT pg_advisory_xact_lock($1)',
      [this.hashClientRequestId(dto.client_request_id)]
    );

    // Phase A: Audit + Reçu probatoire
    const audit = await this.createAuditRecord(manager, dto);
    const receipt = await this.createProofReceipt(manager, dto);

    // Phase B: Persistance document
    const document = await this.persistDocument(manager, dto);

    // Tout atomique: commit unique
    return { document, receipt };
  });
}

5. Signal CLAUDE.md

Priorité haute : Exception filter pour audit NestJS exhaustif.

### NestJS — Audit des rejets framework (2026-02-XX)

**Contexte** : Les guards (JwtAuthGuard, AuthorizationGuard) rejettent AVANT le contrôleur.

**Problème** : L'audit au niveau service ne capture pas les 401/403 du framework.

**Solution** : Exception filter au niveau contrôleur.

```typescript
@Controller('documents')
@UseFilters(DepositAuditExceptionFilter)  // ← Capture tous les rejets
export class DocumentsController { ... }

Attention : Le filtre doit exclure les exceptions déjà auditées par le service (409, 422, 404) pour éviter les doublons. ```

6. Conclusion

PD-60 a livré POST /documents/upload avec acte probatoire daté, traçable, juridiquement imputable, et vérifiable par un tiers via JWKS public. Les 4 itérations d'acceptabilité illustrent l'importance de rappeler les exclusions de périmètre et d'inclure les mécanismes framework dans le plan. Le pattern exception filter + transaction englobante est réutilisable pour toute US probatoire.


Rétrospective générée 2026-02-19 (Étape 10 batch petits-domaines)