PD-275 — Revue d'acceptabilité¶
Prérequis acceptabilité¶
- Tests CI : 260/260 passing (13 suites, anchor module complet)
- Coverage : ~95% statements, ~82% branches sur fichiers PD-275
- TODO non tracés : aucun
- Code DEV ONLY : aucun
Phase 1 — Reviews automatisées¶
Lint (ESLint)¶
- Résultat : 0 erreurs, 0 warnings sur
src/modules/anchor/ - Date : 2026-02-28
Types (TypeScript)¶
- Résultat :
npx tsc --noEmit— 0 erreurs - Date : 2026-02-28
Tests unitaires¶
- Résultat : 260/260 tests passing
- Suites : 13 suites (finality-guard, signer-registry, signer-controller, anchor-batch, blockchain-anchor-processor, anchor-robustness, ...)
- Coverage PD-275 :
finality-guard.service.ts: 100% statements, 100% branchessigner-registry.service.ts: 97% statements, 85% branchessigner.controller.ts: 95% statements, 80% branchessigner-status.enum.ts: 100% statements, 100% branchesfinality-guard.entity.ts: 100% statementssigner-registry.entity.ts: 100% statements
Format¶
- Résultat : conforme (Prettier)
Phase 1.5 — Analyse Sonar¶
- Quality Gate : NON DISPONIBLE
- Raison : Token Sonar absent du Vault (
kv/data/ci/sonarvide) - Atténuation : lint + tsc + tests 260/260 + coverage >= 80% couvrent les axes principaux
- Action requise : configurer le secret Sonar dans Vault pour les prochaines stories
Phase 2 — Reviews LLM (ChatGPT gpt-5.3-codex)¶
Review Code (développeur senior)¶
Verdict ChatGPT : REJETÉ (1 écart BLOQUANT identifié)
| ID | Type | Criticité | Description | Analyse orchestrateur |
|---|---|---|---|---|
| E-01 | DIV | BLOQUANT | INV-275-07 (state machine complete) : Q-02 spec signale liste canonique batch manquante | FAUX POSITIF — L'enum BatchStatus TypeORM (PENDING, BUILDING, SUBMITTED, PENDING_FINALITY, FINALIZED, FAILED) EST la liste canonique. Q-02 était une question ouverte en spec, résolue par l'implémentation. L'enum est vérifié formellement par TLA+ (StatusValues ⊆ RealStates). |
| E-02 | AMB | MAJEUR | Code contracts YAML absents des entrées | FAUX POSITIF — Les CC étaient inclus dans le prompt via assemble-prompt.sh (77KB). Le modèle n'a pas trouvé la section. |
| E-03 | ECT | MAJEUR | Validation structurelle non prouvée | FAUX POSITIF — Hors scope review code (c'est une review de code, pas de doc). |
| E-04 | AMB | MINEUR | Anti-spoofing revokedBy : refus strict vs ignore | VALIDE MINEUR — L'implémentation choisit le refus strict (400 BadRequest). Documenté dans le controller. |
Invariants : 11/12 PASS. INV-275-08 PASS avec réserve (vacuously satisfied — pas de nouvel artefact crypto PD-275).
Review Tests (QA engineer)¶
Verdict ChatGPT : RÉSERVES
| ID | Type | Criticité | Description | Analyse orchestrateur |
|---|---|---|---|---|
| T-01 | ECT | MAJEUR | Preuve contractuelle guards @Roles() incomplète — 403 couvert mais pas body {} et audit ACCESS_DENIED | ATTÉNUÉ — PD-275 utilise OR-semantics au service level (pas @Roles() decorator). Le test vérifie ForbiddenException. Le body contient {code, message} pas {}. L'audit ACCESS_DENIED est un pattern de la couche AuthorizationGuard globale, pas spécifique PD-275. |
| T-02 | AMB | MAJEUR | TC-ERR-09 non-déterministe (refus OU ignore) | VALIDE — L'implémentation a choisi refus strict (400). Le test doit asserter ce comportement unique. Cependant, le test unitaire signer.controller.spec.ts vérifie bien le 400 BadRequest. L'ambiguïté est dans le document de tests, pas dans le code. |
| T-03 | ECT | MINEUR | Isolation des tests non explicitée | Non applicable — tests unitaires Jest avec mocks, pas d'état partagé. |
| T-04 | ECT | MINEUR | Duplication cas NOM/ERR | Acceptable — couverture exhaustive intentionnelle. |
Matrice : 24/24 TC couverts dans le document.
Review Sécurité (pentester adversarial)¶
Verdict ChatGPT : RÉSERVES
| ID | Type | Criticité | Description | Analyse orchestrateur |
|---|---|---|---|---|
| S-01 | SEC | MAJEUR | Bypass d'autorisation interne possible — appel service sans actorRoles | ATTÉNUÉ — SignerRegistryService.revokeSigner() prend actorRoles en paramètre obligatoire et vérifie ADMIN ∣ SIGNER_ADMIN. Tout appel DOIT fournir les rôles. Le controller les extrait du JWT. Un appel interne sans rôles provoquerait une ForbiddenException (fail-closed). |
| S-02 | SEC | MINEUR | Anti-spoofing revokedBy partiel (body seulement, pas query/header) | ACCEPTABLE — Le DTO RevokeSigner ne déclare pas de champ revokedBy. NestJS ignore les champs non-déclarés du body. Les query params et headers ne sont pas mappés sur revokedBy. Risque résiduel négligeable. |
| S-03 | SEC | MINEUR | TOCTOU sur submit (pré-check puis re-check transactionnel) | PAR DESIGN — Le pré-check (hors transaction) est une optimisation fail-fast. Le re-check transactionnel (SELECT ... FOR UPDATE) est la vraie garde. Un batch créé puis refusé est géré par failBatch(). Pattern documenté dans le plan (FT4, double fenêtre). |
Bypass tentés : injection revokedBy bloquée (400), injection SQL non reproductible (TypeORM), bypass auth bloqué (JWT guard), JWT sans rôle bloqué (403).
Synthèse des écarts¶
| Criticité | Total identifiés | Faux positifs | Réels | Atténués |
|---|---|---|---|---|
| BLOQUANT | 1 | 1 (E-01) | 0 | — |
| MAJEUR | 5 | 2 (E-02, E-03) | 1 (T-02) | 2 (T-01, S-01) |
| MINEUR | 5 | 0 | 5 | 3 (S-02, S-03, E-04) |
Écarts réels non atténués¶
- T-02 (MAJEUR → MINEUR après analyse) : L'ambiguïté "refus OU ignore" est dans le document de tests
PD-275-tests.md, pas dans le code. Le code implémente le refus strict (400). Impact : documentation uniquement — pas d'impact code.
Écarts atténués¶
- T-01 : Le guard utilise OR-semantics au service level, testé via ForbiddenException.
- S-01 : Le service exige
actorRolesen paramètre, fail-closed si absent. - S-02 : NestJS DTO ne mappe pas les champs non-déclarés.
- S-03 : Double fenêtre pré-check/re-check est by design (FT4).
- E-04 : L'implémentation choisit refus strict, documenté.
Verdict global¶
CONFORME AVEC RÉSERVES
- 0 écart BLOQUANT (1 identifié = faux positif)
- 1 écart MAJEUR résiduel (T-02, réduit à documentation)
- 5 écarts MINEUR (tous atténués ou par design)
- 260/260 tests passing, coverage >= 80%
- Lint + TSC : 0 erreur
- Sonar : non disponible (token manquant)
Réserves¶
- Mettre à jour
PD-275-tests.mdpour fixer l'ambiguïté TC-ERR-09 (refus strict, pas "refus ou ignore") - Configurer le token Sonar dans Vault pour les prochaines stories