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)