Aller au contenu

PD-46 — Rétrospective

1. Contexte

Champ Valeur
Story ID PD-46
Titre Download sécurisé avec pre-signed URLs
Domaine storage
Projet infra
Date complétion 2026-02-XX
Verdict ACCEPTÉ (GO 8.625/10)

2. Métriques

Métrique Valeur
Durée totale ~11h (-21% vs estimé)
Tests contractuels 70/70 passés
Gate iterations Gate 3: 2, Gate 5: 1, Gate 8: 1
Score convergence moyen 8.79/10
Écarts totaux 7 (4 ECT, 2 AMB, 1 SEC)

3. Learnings clés

  • Scope explicite = protection contre faux positifs : Documenter les TODO dès le plan évite les pénalités en Gate 8 sur fonctionnalités hors scope.

  • Invariants observables dès Gate 3 : Un invariant non testable (INV-46-06 Zero-Knowledge) causera une itération supplémentaire. Ajouter assertion sur absence d'appel.

  • Confrontation ChatGPT/Claude indispensable : Le delta de scoring révèle les biais des reviewers et les faux positifs.

  • Zero-Knowledge testable par absence : Vérifier qu'une opération N'EST PAS appelée (GetObject jamais invoqué) est un test valide.

  • Dette technique externe bloque le workflow : Erreurs TypeScript pré-existantes dans notifications/ bloquent merge même si PD-46 correct.

4. Patterns applicables

Nouveau pattern : Pre-signed URL avec TTL + droits temps réel

interface PreSignedUrlOptions {
  documentId: string;
  userId: string;
  ttlSeconds: number;  // Centralisé, jamais dans appel client
}

async generatePreSignedUrl(options: PreSignedUrlOptions): Promise<string> {
  // Vérification droits TEMPS RÉEL (pas cache)
  const hasAccess = await this.accessService.checkAccess(
    options.userId,
    options.documentId
  );

  if (!hasAccess) {
    throw new ForbiddenException('Access revoked');
  }

  // INV-46-06: Backend ne lit JAMAIS le contenu (Zero-Knowledge)
  // Pas de GetObject, seulement getSignedUrl
  return this.s3Client.getSignedUrl('getObject', {
    Bucket: this.bucket,
    Key: this.buildKey(options.documentId),
    Expires: options.ttlSeconds,
  });
}

Nouveau pattern : Test d'absence (Zero-Knowledge)

// TC-ZK-01: Vérifier que GetObject n'est JAMAIS appelé
it('should never call GetObject (Zero-Knowledge)', async () => {
  const getObjectSpy = jest.spyOn(s3Mock, 'send');

  await service.generatePreSignedUrl(validOptions);

  const getObjectCalls = getObjectSpy.mock.calls.filter(
    call => call[0] instanceof GetObjectCommand
  );

  expect(getObjectCalls).toHaveLength(0);  // ABSENCE = succès
});

5. Signal CLAUDE.md

Priorité moyenne : Pattern test d'absence pour invariants Zero-Knowledge.

### Jest — Tests d'absence (Zero-Knowledge) (2026-02-XX)

**Contexte** : Invariants Zero-Knowledge exigent que certaines opérations ne soient JAMAIS appelées.

**Pattern** :
```typescript
it('should never call forbidden operation', async () => {
  const spy = jest.spyOn(service, 'forbiddenMethod');

  await service.safeMethod();

  expect(spy).not.toHaveBeenCalled();  // ABSENCE = succès
});

Cas d'usage ProbatioVault : - Backend ne lit jamais le contenu chiffré (GetObject interdit) - Clés de déchiffrement jamais transmises au serveur - Données sensibles jamais loguées ```

6. Conclusion

PD-46 a livré le download sécurisé avec pre-signed URLs, TTL centralisé et vérification droits temps réel, en respectant l'invariant Zero-Knowledge (INV-46-06). La Gate 3 à 2 itérations illustre l'importance de rendre les invariants observables dès la spec. Les patterns pre-signed URL et test d'absence sont réutilisables pour toutes les opérations Zero-Knowledge.


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