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)