Aller au contenu

PD-172 — Review de spécification (audit indépendant)

0. Synthèse exécutive

Indicateur Valeur
Nombre d'écarts identifiés 28
Bloquants 4
Majeurs 13
Mineurs 11
Verdict de testabilité (vu auditeur) Partiellement testable, contrat insuffisamment fermé

L'audit confirme que la spécification reconnaît elle-même un nombre élevé de paramètres absents (§5.2/§5.3/§5.7 marqués NON FOURNI / HORS PÉRIMÈTRE), 12 Q-172-* ouvertes (§10.2), et une contradiction RGPD/diagnostic non arbitrée (§5.1). Au-delà de ces auto-déclarations, la review identifie des contradictions internes structurantes (regex route vs redis_key, états terminaux §5.4 vs clearing/réconciliation §5.7), des ambiguïtés terminologiques (« Redis indisponible », « fail-open contrôlé », « dégradation contrôlée »), et des risques sécurité/conformité non encore tracés (audit fail-closed sur crash post-décision, IP brute en clé, exhaustivité routes V1).


1. Ambiguïtés

A-01

Type        : Ambiguïté
Référence   : §3 Définitions + §5.4 + §5.7 — terme « Redis indisponible »
Description : Le terme « Redis indisponible » conditionne les états BYPASS_DEGRADED / DENIED_DEGRADED (§5.4) et la stratégie fail-open/fail-closed (§5.7), mais n'est défini nulle part de manière observable : timeout unique ? circuit-breaker ouvert ? taux d'erreur seuil ? perte de connexion TCP ? Aucun seuil temporel ni protocole de détection (PING, OBJECT ENCODING) n'est spécifié.
Impact      : INV-172-05 et CA-172-08 ne sont pas testables sans hypothèse implémentation. TC-NOM-09/10/11 et TC-NEG-08 supposent une simulation d'« indisponibilité » dont le déclencheur n'est pas contractualisé.
Gravité     : Bloquant

A-02

Type        : Ambiguïté
Référence   : §3 Définitions — termes « fail-open contrôlé », « dégradation contrôlée »
Description : Le qualificatif « contrôlé » apparaît 4 fois (§3, §5.4 transition `BYPASS_DEGRADED`, §5.7 réconciliation, ERR-172-08 dégradation contrôlée) sans définition. La définition §3 de `fail-open` est binaire (autorisée/refusée) et n'inclut pas la notion « contrôlé ». Aucun observable (limite secondaire ? quota local ? throttling proportionnel ?) n'est attaché.
Impact      : Tests TC-NOM-09 et TC-ERR-08 reformulent ce terme sans définition ancrée. CA-172-08 n'est pas un critère mesurable au-delà du binaire allowed/denied.
Gravité     : Majeur

A-03

Type        : Ambiguïté
Référence   : §3 Définitions — terme « Endpoint sensible »
Description : « Endpoint avec protection plus restrictive que le profil générique » est circulaire — un endpoint est sensible parce qu'il est traité plus strictement, pas l'inverse. Aucune liste fermée n'est fournie. La table §5.8 cite `/auth/login`, `/auth/otp`, `/auth/refresh`, `/documents/upload`, `/exports`, `/proof/generate`, `+ routes lecture (liste exhaustive non fournie)`.
Impact      : INV-172-04 (« endpoints sensibles plus restrictifs ») non testable globalement : on ne peut pas garantir qu'un endpoint sensible non listé sera correctement classé. CA-172-03 partiellement seulement.
Gravité     : Majeur

A-04

Type        : Ambiguïté
Référence   : §5.2 — `latency_rate_limit_check_p99` (Min=0, Max=5, Default=5)
Description : Une latence Min=0 ms est physiquement impossible et un Default égal au Max signifie que toute mesure conforme par défaut est à la limite supérieure. La table mélange « budget contractuel » (Max=5) et « valeur observée » (Min/Default).
Impact      : Le critère CA-172-09 (P99 ≤ 5 ms) est mesurable mais l'absence de définition de la charge, de la fenêtre d'agrégation et de l'environnement cible rend la valeur publiable arbitrairement.
Gravité     : Mineur

A-05

Type        : Ambiguïté
Référence   : §5.2 — `redis_rtt_per_check_max` (Min=Max=Default=1)
Description : Trois bornes identiques signalent un constant, pas un paramètre. La spec ne précise pas si cette contrainte est imposée à l'implémentation (1 RTT autorisé) ou observée a posteriori (1 RTT mesuré).
Impact      : TC-NOM-12 vérifie « au plus 1 RTT par requête » sans observable contractualisé : aucun champ de log ou métrique ne porte cette information.
Gravité     : Mineur

A-06

Type        : Ambiguïté
Référence   : §5.5 — « Crash post-décision / pré-observabilité »
Description : Le tableau §5.5 qualifie ce cas de « non-conformité INV-172-03 (incident d'exploitation) » sans définir : (a) comment le détecter, (b) comment l'écart est signalé, (c) si une compensation post-mortem (replay log) est attendue.
Impact      : Trou d'auditabilité. INV-172-03 est inviolable contractuellement mais admet un cas où il ne peut être respecté, sans mécanisme de mitigation.
Gravité     : Majeur

A-07

Type        : Ambiguïté
Référence   : §6 ERR-172-06 — « comportement aligné ERR-172-02/fail-open selon famille de route »
Description : La phrase suggère que le timeout backend de quota est traité comme une indisponibilité Redis, mais ne l'affirme pas. Un timeout peut être transitoire (réseau saturé) sans que Redis soit réellement indisponible.
Impact      : TC-ERR-06 et TC-NEG-08 supposent l'alignement timeout=indisponibilité. Si l'implémentation distingue les deux, les tests passent ou échouent selon une convention non écrite.
Gravité     : Majeur

A-08

Type        : Ambiguïté
Référence   : §5.4 — terme « endpoint coûteux » (mappé `costly`)
Description : §3 définit « Endpoint coûteux » comme « endpoint à coût CPU/IO élevé » sans seuil ni méthode de classement. Aucune table de routes `costly` n'est fournie en §5.8.
Impact      : INV-172-05 fail-closed sur `costly` n'est pas auditable : un endpoint mal classé sera protégé selon une famille incorrecte.
Gravité     : Majeur

A-09

Type        : Ambiguïté
Référence   : §6 ERR-172-08 — « saturation cardinalité clé »
Description : « Volumétrie anormale », « dégradation contrôlée + alerte exploitation » : aucun seuil, fenêtre d'observation, ni canal d'alerte (Sentry, Prometheus, syslog) n'est défini.
Impact      : TC-NEG-09 non déterministe. Le risque DoS par spray cardinalité (classique en rate-limit Redis) reste sans contrat de protection.
Gravité     : Majeur

A-10

Type        : Ambiguïté
Référence   : §10.2 Q-172-09 — granularité labels métriques
Description : La spec liste comme question ouverte « Granularité exacte métriques obligatoires (labels, cardinalité max) ». Or CA-172-07 affirme « Les métriques permettent exploitation SRE sans ambiguïté ».
Impact      : Critère affirmatif bâti sur question ouverte non résolue. CA-172-07 non testable rigoureusement avant arbitrage.
Gravité     : Majeur

A-11

Type        : Ambiguïté
Référence   : §5.8 — « routes lecture (liste exhaustive non fournie) »
Description : La protection rate-limit s'applique « ciblé par modules/routes ». Sans liste fermée des routes V1, la couverture du rate-limit est elle-même indéfinie.
Impact      : Risque de routes en production sans protection. H-172-02 (§9) acte cette ambiguïté comme hypothèse mais son impact (« risque de routes non protégées ») reste sans mitigation contractuelle.
Gravité     : Majeur

A-12

Type        : Ambiguïté
Référence   : §7 CA-172-09 — « budget de latence P99 du check rate-limit »
Description : « Mesure perf instrumentée P99 sur environnement cible » sans précision : nombre d'échantillons mini, charge représentative, hardware (RTX cloud vs runner), version Redis, isolation noisy-neighbor.
Impact      : Critère acceptable au sens littéral mais non reproductible. ST-172-09 hérite de cette ambiguïté.
Gravité     : Mineur

2. Contradictions

C-01

Type        : Contradiction interne
Référence   : §5.1 — regex `route` vs regex `redis_key`
Description : `route` est défini par `^/[A-Za-z0-9._~!$&'()*+,;=:@%/-]{1,255}$` (le caractère `:` est autorisé). `redis_key` est défini par `^rate_limit:[^:]{1,256}:(ip|user_id|tenant|route):[A-Za-z0-9._-]{1,128}$` : le segment route entre les deux premiers `:` doit ne contenir aucun `:`. Une route REST contenant `:` (ex: `/api/v1/users/me:export`) ne peut produire de `redis_key` valide.
Impact      : Erreurs ERR-172-04 systémiques sur des routes pourtant conformes au regex `route`. INV-172-09 (« source de vérité unique ») violée par incohérence interne au §5.1.
Gravité     : Bloquant

C-02

Type        : Contradiction interne
Référence   : §5.4 (états terminaux par requête) vs §5.7 (« degraded_clearing_cycles ») et §10.2 (Q-172-07 « bloque sortie de mode dégradé »)
Description : §5.4 affirme que tous les états (`ALLOWED`, `THROTTLED`, `BYPASS_DEGRADED`, `DENIED_DEGRADED`, `REJECTED_INVALID_CONTEXT`) sont terminaux **par requête** et que « transition retour intra-requête : non applicable ». §5.7 puis §10.2 introduisent un retour `HEALTHY` qui ne peut concerner qu'un état **système** (pas une requête).
Impact      : Confusion entre état requête et état système. La machine d'états §5.4 ne couvre donc pas la totalité du contrat — il manque un automate « état du backend de quota ». INV-172-06 (« toute transition non listée est interdite ») devient ambigu (s'applique à quoi ?).
Gravité     : Majeur

C-03

Type        : Contradiction interne (auto-déclarée)
Référence   : §5.1 note finale — « contradiction identifiée entre pseudonymisation des clés Redis (RG métier) et exemple de clé avec IP brute. Règle non testable tant que format final d'`identifier` n'est pas arbitré en §10 (HORS PÉRIMÈTRE) »
Description : La spec admet explicitement une contradiction non résolue qui touche à la fois RG métier (pseudonymisation), conformité RGPD (IP est donnée personnelle si brute), et observabilité (un identifier hashé empêche le diagnostic d'incident).
Impact      : Auto-aveu de non-fermeture. INV-172-09 (« source de vérité unique ») violée par la spec elle-même. Tous les CA et tests qui touchent `identifier` (CA-172-04, TC-NOM-04) reposent sur un format non arbitré.
Gravité     : Bloquant

C-04

Type        : Contradiction Spec ↔ Tests
Référence   : §6 ERR-172-02 (« code HTTP exact à clarifier, hypothèse 503 ») vs TC-ERR-02 (« Vérification du code HTTP exact = NON TESTABLE »)
Description : La spec contractualise un code d'erreur sans en fixer le code HTTP, et le test reflète cette indétermination. INV-172-03 (« observable ») est satisfait, mais le contrat client (§7 CA-172-02 affirme « 429 + headers standard ») est rompu pour le cas fail-closed : aucun code HTTP n'est garanti aux clients.
Impact      : Le contrat API publique est incomplet. Les clients ne peuvent pas implémenter de gestion d'erreur déterministe pour le fail-closed.
Gravité     : Bloquant

C-05

Type        : Contradiction interne
Référence   : §5.7 « Lock distribué : Non applicable » vs §5.7 « Évaluation quota atomique par clé »
Description : Si l'évaluation quota est atomique sur une clé Redis multi-fenêtres (burst+sustained) avec deux opérations distinctes (INCR burst, INCR sustained), l'atomicité multi-clé exige soit MULTI/EXEC soit Lua. Le tableau dit « non applicable » alors que l'atomicité est nécessaire.
Impact      : Implémentation possible avec script Lua (donc atomique), mais le contrat ne l'exige pas. Race possible entre les deux INCR avec décompte incohérent (un quota effectivement < CA-172-05).
Gravité     : Majeur

C-06

Type        : Contradiction Spec ↔ Tests
Référence   : §7 CA-172-07 (« comptes par route/famille/cause/refus ») vs §10.2 Q-172-09 (granularité non définie) vs TC-NOM-08 (vérifie ces labels)
Description : Le test impose des labels `route`, `route_family`, `cause`, `decision_state` que la spec ne contractualise pas. Soit le test va plus loin que la spec, soit la spec a une cardinalité métrique implicite non documentée.
Impact      : Risque d'explosion cardinalité Prometheus si `route` (jusqu'à 256 chars cardinalité ouverte) entre comme label. Conflit avec H-172-02 (routes non exhaustives).
Gravité     : Majeur

C-07

Type        : Contradiction interne
Référence   : §10.2 Q-172-06 (« RG-8 vs no-whitelist V1 ») vs INV-172-07 (« Aucun mécanisme de whitelist/bypass n'est autorisé en V1 »)
Description : INV-172-07 est non négociable. Q-172-06 admet une tension non résolue avec une RG-8 mentionnée mais non détaillée dans la spec présente. Si la santé/monitoring (probes Kubernetes) est rate-limitée, un load balancer peut sortir l'instance du pool.
Impact      : Soit RG-8 est abandonnée (à acter), soit INV-172-07 est édulcoré (à acter). En l'état, contradiction non tranchée et risque opérationnel non négligeable.
Gravité     : Majeur

C-08

Type        : Contradiction interne
Référence   : §5.2 — paramètres NON FOURNI vs §7 CA-172-02/03/05/09 affirmatifs
Description : Six paramètres numériques (`burst.max_requests`, `burst.window_seconds`, `sustained.max_requests`, `sustained.window_seconds`) sont marqués `NON FOURNI` et `HORS PÉRIMÈTRE`. Or CA-172-02/03/05 affirment des comportements observables qui dépendent de ces valeurs.
Impact      : Critères acceptables sur le mécanisme (présence headers, différenciation), mais inacceptables sur la valeur (combien de req → 429 ?). Le contrat de blocage n'existe pas.
Gravité     : Bloquant (auto-déclaré H-172-03)

C-09

Type        : Contradiction interne
Référence   : §3 « fail-open : Requête autorisée si backend de quota indisponible » vs §5.4 transition `BYPASS_DEGRADED`
Description : La définition §3 du fail-open est binaire. Le `BYPASS_DEGRADED` ne dit pas si la requête est comptabilisée localement (compteur in-memory de secours) ou simplement laissée passer. La table §5.7 « Évaluation quota atomique par clé » est silencieuse sur le mode dégradé.
Impact      : Possibilité d'un by-pass total non protégé en cas de Redis down sur famille `read`, exploitable par un attaquant qui force un timeout vers Redis.
Gravité     : Majeur

C-10

Type        : Contradiction Spec ↔ Tests
Référence   : §5.4 transitions vs TC-NOM-15 (« cinq contextes contrôlés couvrant toutes transitions »)
Description : §5.4 liste 5 transitions sortantes de `RECEIVED`. Le test exige 5 contextes mais ne précise pas comment provoquer `REJECTED_INVALID_CONTEXT` sans entrer dans la branche `400 ERR-172-03` qui appartient à §6 (avant-RECEIVED ?). L'ordre validation contexte vs entrée RECEIVED n'est pas spécifié.
Impact      : TC-NOM-15 ambigu : deux interprétations possibles (la requête entre RECEIVED puis va REJECTED, ou la requête est rejetée avant RECEIVED).
Gravité     : Mineur

3. Règles non testables

N-01

Type        : Règle non testable
Référence   : §4 INV-172-05 — « auth=fail-closed, lecture=fail-open contrôlé, coûteux=fail-closed »
Description : Sans (a) liste exhaustive de routes par famille, (b) définition de « Redis indisponible » (cf. A-01), (c) définition de « contrôlé » (cf. A-02), l'invariant ne peut être validé sur l'ensemble du périmètre.
Impact      : Couverture INV-172-05 limitée aux quelques routes nommément citées en §5.8.
Gravité     : Majeur

N-02

Type        : Règle non testable
Référence   : §6 ERR-172-08 — saturation cardinalité
Description : Aucun seuil chiffré, aucune fenêtre d'observation, aucun observable de l'alerte.
Impact      : Risque DoS par spray non couvert. TC-NEG-09 non déterministe.
Gravité     : Majeur

N-03

Type        : Règle non testable
Référence   : §5.7 « Réconciliation : applicable, intervalle cron et seuil orphelin NON FOURNIS »
Description : Le mécanisme est requis (« applicable ») mais non spécifié. Pas de critère pour valider sa correction.
Impact      : Robustesse mode dégradé non auditable.
Gravité     : Majeur

N-04

Type        : Règle non testable
Référence   : §5.3 — `burst_window_ttl`, `sustained_window_ttl`, `degraded_clearing_cycles`
Description : Trois SLA temporels marqués NON FOURNI. Sans ces valeurs, on ne peut pas affirmer qu'un compteur expire effectivement (potentielle accumulation de clés Redis sans TTL = fuite mémoire si bug d'expiration).
Impact      : Risque mémoire Redis non testable. Pas de bord supérieur sur l'âge maximum d'une décision.
Gravité     : Majeur

N-05

Type        : Règle non testable
Référence   : §5.5 « Crash post-décision / pré-observabilité »
Description : Cas d'incident reconnu sans observable ni mécanisme de détection. Aucun test ne peut prouver la couverture.
Impact      : INV-172-03 inviolable mais admet un trou.
Gravité     : Mineur

N-06

Type        : Règle non testable
Référence   : INV-172-04 (« hiérarchie de protection ») sans liste sensitive vs générique
Description : Sans liste fermée des « endpoints sensibles » (cf. A-03), l'invariant n'est testable que sur exemples ponctuels, pas exhaustivement.
Impact      : CA-172-03 partiel seulement.
Gravité     : Mineur

N-07

Type        : Règle non testable
Référence   : §5.7 « Idempotence : Non applicable (requête API), pas de déduplication V1 »
Description : Une requête retransmise par un client (timeout, retry mobile, double-tap UI) sera comptée 2 fois. Aucun mécanisme contractuel d'aide aux clients (idempotency-key, request-id côté client).
Impact      : Le rate-limit pénalise les clients qui retransmettent légitimement. Hors de portée de cette spec, mais le « non applicable » mérite un commentaire explicite.
Gravité     : Mineur

4. Incohérences de testabilité (Spec ↔ Tests)

S-01

Type        : Incohérence Spec↔Tests
Référence   : Spec §6 ERR-172-04 vs TC-ERR-04 (« capacité d'injection de faute test activée »)
Description : ERR-172-04 (`500`) est déclenché si une `redis_key` hors regex est construite. Le test exige un crochet d'injection de faute spécifique au code de test. Or aucun crochet n'est mentionné dans la spec, et l'introduction d'une `redis_key` invalide en environnement réel devrait être impossible si l'implémentation respecte §5.1.
Impact      : Test runtime artificiellement créé. Risque de test « tautologique » qui ne protège que le hook de test, pas la logique réelle (cf. learning reward-hacking WEASEL).
Gravité     : Majeur

S-02

Type        : Incohérence Spec↔Tests
Référence   : Spec §5 (aucune mention `request_id`) vs Tests §8 (« log structuré corrélable request_id »), TC-NOM-07
Description : `request_id` est utilisé comme observable obligatoire des tests (corrélation refus → log → métrique) mais n'apparaît dans aucune table normative §5.1 ni invariant §4. INV-172-03 ne mentionne pas la corrélation.
Impact      : Test exige un attribut non contractualisé. La spec doit ajouter `request_id` aux observables ou les tests doivent renoncer à cette assertion.
Gravité     : Majeur

S-03

Type        : Incohérence Spec↔Tests
Référence   : Spec §7 CA-172-09 « <= 5 ms » vs §5.2 (Default=Max=5)
Description : La spec impose un budget exact (5 ms = défaut) mais ne distingue pas budget effectif et budget contractuel. Le test TC-NOM-12 exige `<= 5 ms` strict ; aucune marge n'est laissée au profil de production (warm-up JIT, GC, contention runner).
Impact      : Critère trop strict en CI / runner partagé, risque de flakiness non documenté.
Gravité     : Mineur

S-04

Type        : Incohérence Spec↔Tests
Référence   : Spec INV-172-02 (atomicité quota-métier) vs Tests TC-NOM-02 (« compteur inchangé »)
Description : « Compteur inchangé » suppose un sonde métier observable (cf. P0 commun « Sonde de logique métier disponible pour prouver exécution/non-exécution ») non contractualisée dans la spec. La spec ne dit pas comment INV-172-02 est observable au-delà d'un effet absent.
Impact      : Le test introduit un mécanisme d'observabilité non normé.
Gravité     : Mineur

S-05

Type        : Incohérence Spec↔Tests
Référence   : Tests §5 INV-172-09 — TC-INV-09 « vérification documentaire/contrat, pas purement runtime »
Description : Le test TC-INV-09 reconnaît qu'il s'agit d'une revue de schémas plutôt que d'un scénario runtime. Or la matrice §2 marque INV-172-09 comme « Partiel ». L'invariant §4 n'indique pas que sa vérification est documentaire.
Impact      : INV-172-09 (qui revendique source de vérité unique) n'est pas vérifié dynamiquement. Toute divergence introduite après la review documentaire passera inaperçue.
Gravité     : Mineur

S-06

Type        : Incohérence Spec↔Tests
Référence   : Spec §5.4 transition `BYPASS_DEGRADED` vs Tests §3 (P0 « Sonde de logique métier ») et TC-NOM-09
Description : `BYPASS_DEGRADED` exécute la logique métier (« fail-open ») mais aucun comptage ni indicateur d'usage en mode dégradé n'est imposé par la spec. TC-NOM-09 vérifie l'exécution métier mais pas la traçabilité d'un usage en mode dégradé.
Impact      : Volumétrie en mode dégradé non observable → un attaquant qui maintient Redis-down voit son trafic massif mais non isolé dans les métriques produit.
Gravité     : Majeur

S-07

Type        : Incohérence Spec↔Tests
Référence   : Spec §10.1 (stack TypeScript/NestJS) vs Tests §3 P0 (« 2 instances backend ») sans nœud de partage de configuration
Description : Les tests supposent deux instances avec config identique sans contrat sur la procédure de boot synchronisé (déploiement rolling, immutabilité runtime). En cas de rolling deploy avec config changée, INV-172-08 et CA-172-10 peuvent être violés transitoirement.
Impact      : Cohérence inter-instance INV-172-01 non garantie pendant un déploiement.
Gravité     : Mineur

5. Hypothèses dangereuses

H-01

Type        : Hypothèse dangereuse
Référence   : §9 H-172-01 — fail-closed = HTTP 503 hypothétique
Description : Hypothèse documentée mais non tranchée. Le code 503 implique « Service Unavailable » (interprété par CDN/LB comme ré-essayable), 429 (Too Many Requests) signale autre chose, 451 (Unavailable For Legal Reasons) est inapplicable. Selon le code retenu, le comportement client (Retry-After lib clients) diffère.
Impact      : Risque d'amplification de charge vers Redis si retry sur 503 vs back-off sur 429 distincts.
Gravité     : Majeur

H-02

Type        : Hypothèse dangereuse
Référence   : §9 H-172-02 — liste de routes non exhaustive
Description : « Risque de routes non protégées en production ». Hypothèse acceptée mais sans plan de mitigation : pas de filet par défaut (deny-all si route inconnue), pas de règle « toute route doit avoir un profil sinon démarrage refusé » (CA-172-10 ne l'exige pas).
Impact      : Bypass possible du rate-limit V1 par exploitation d'une route oubliée.
Gravité     : Majeur

H-03

Type        : Hypothèse dangereuse
Référence   : §5.1 — résolution IP à partir de chaîne proxy
Description : `ip` est une dimension V1 obligatoire. La résolution dépend de `X-Forwarded-For`, `X-Real-IP` ou d'une convention runner. La spec impose ERR-172-07 si « non résolue de manière fiable » sans définir « fiable » ni les en-têtes de confiance.
Impact      : Spoofing X-Forwarded-For possible si proxy mal configuré → contournement rate-limit IP. Touch Q-172-11 ouverte (§10.2).
Gravité     : Majeur

H-04

Type        : Hypothèse dangereuse
Référence   : §5.5 « Évaluation quota Redis : Synchrone Décision atomique par requête »
Description : Atomicité Redis non précisée (script Lua ? MULTI/EXEC ? `INCRBYFLOAT` simple ?). Sans Lua, l'opération burst+sustained est multi-clé non atomique → race condition entre deux instances qui s'envoient des INCR concurrents.
Impact      : INV-172-01 (cohérence inter-instance) potentiellement violé sous charge.
Gravité     : Majeur

H-05

Type        : Hypothèse dangereuse
Référence   : §5.4 transition `BYPASS_DEGRADED` sur `read`
Description : Suppose qu'aucune route `read` ne peut servir de canal d'attaque. Or une route lecture qui retourne des résumés (`/auth/me`, `/users/me/orgs`) peut être exploitée pour énumération si le rate-limit est désactivé en mode dégradé.
Impact      : Surface d'énumération étendue durant un incident Redis.
Gravité     : Majeur

H-06

Type        : Hypothèse dangereuse
Référence   : Spec §3 / §5 — comptage par fenêtre fixe (implicite)
Description : Aucune mention de l'algorithme : fenêtre fixe (vulnérable au burst à cheval), sliding window, token bucket, leaky bucket. La table §5.3 mentionne `burst_window_ttl` et « réévaluation complète à la requête suivante » — sémantique fenêtre fixe TTL.
Impact      : Si fenêtre fixe TTL Redis, un attaquant peut envoyer 2× burst en envoyant à l'instant T-ε puis T+ε (deux fenêtres). Mécanisme non protégé.
Gravité     : Majeur

H-07

Type        : Hypothèse dangereuse
Référence   : §9 H-172-04 — format `identifier` pseudonymisé arbitré
Description : Si `identifier` finit pseudonymisé (hashé), tout diagnostic d'incident perd l'observabilité IP/user/tenant brute. Si non pseudonymisé, RGPD pourrait exiger une analyse d'impact (DPIA). Le choix conditionne la conception.
Impact      : Refonte ultérieure non triviale (changement de regex `redis_key`, migration TTL en masse). Risque RGPD si IP non hashée.
Gravité     : Majeur

5bis. Cohérence des diagrammes (§5bis)

D-01

Type        : Cohérence diagramme — état
Référence   : §5bis stateDiagram-v2 vs §5.4 transitions
Description : Le diagramme couvre les 5 transitions sortantes de `RECEIVED` listées en §5.4. Tous les états terminaux pointent vers `[*]`. Les transitions interdites sont rappelées en commentaire textuel sous le diagramme. Aucune divergence détectée.
Impact      : Aucun.
Gravité     : Conforme

D-02

Type        : Cohérence diagramme — séquence
Référence   : §5bis sequenceDiagram
Description : Le diagramme nomme `redis_status` retourné par `RL` mais ce champ n'apparaît pas dans la table §5.1 (modèle de données). Aucune définition de format/enum n'est donnée pour `redis_status` (DOWN observé, mais pas d'autres valeurs).
Impact      : Le contrat d'interface entre l'API et le store de rate-limit (RL) est incomplet. Toute implémentation alternative du store devra inférer le format de `redis_status`.
Gravité     : Mineur

D-03

Type        : Cohérence diagramme — séquence
Référence   : §5bis sequenceDiagram, ligne `atomic quota evaluation (burst + sustained)`
Description : « atomic » figure dans le diagramme, mais §5.7 « Lock distribué : Non applicable » ne contractualise pas l'atomicité multi-clé. Cohérence diagramme/§5.7 contredite (cf. C-05).
Impact      : Le diagramme implique une garantie qui n'est pas portée par le texte normatif.
Gravité     : Majeur

D-04

Type        : Cohérence diagramme — séquence
Référence   : §5bis sequenceDiagram, alt `redis_status=DOWN`
Description : Le diagramme distingue `redis_status=DOWN` mais pas le cas timeout (ERR-172-06). Il n'illustre pas non plus la branche `REJECTED_INVALID_CONTEXT` (avant ou après `normalize` ?), alors que §5.4 inclut cette transition.
Impact      : Alignement diagramme/spec partiel. Le lecteur tiers peut conclure que l'invalid context est traité hors séquence rate-limit (ce qui est conforme à C-10 mais reste implicite).
Gravité     : Mineur

6. Risques sécurité / conformité

R-01

Type        : Risque sécurité/conformité (RGPD)
Référence   : §5.1 note finale + §10.2 Q-172-05 — IP brute en clé Redis
Description : Une IP est une donnée à caractère personnel (RGPD recital 30). En clé Redis (TTL court), elle reste néanmoins traçable par toute personne accédant à Redis (ops, backups). Aucun chiffrement at-rest n'est imposé.
Impact      : Risque RGPD si registre des traitements ne couvre pas ce stockage transitoire. Q-172-05 reste ouverte.
Gravité     : Majeur

R-02

Type        : Risque sécurité (DoS)
Référence   : §6 ERR-172-08 + N-02 + A-09
Description : « Saturation cardinalité » sans seuil ni mitigation. Un attaquant peut spammer des requêtes avec des `identifier` aléatoires pour exploser le set de clés Redis (`SCAN` ralenti, mémoire saturée → crash Redis → fail-closed sur auth/costly = blackout).
Impact      : DoS amplifié sur l'ensemble du backend, pas seulement la route ciblée.
Gravité     : Majeur

R-03

Type        : Risque sécurité (Bypass)
Référence   : H-05 + H-172-02 + INV-172-07
Description : Combinaison de (a) routes `read` non exhaustives (H-172-02), (b) fail-open en mode dégradé sur `read` (H-05), (c) interdiction de whitelist (INV-172-07). Une route non listée tombe par défaut dans la famille générique (laquelle ?), avec stratégie Redis-down indéterminée.
Impact      : Routes non protégées qui le sont par défaut, ou inversement. Faille V1 par omission.
Gravité     : Majeur

R-04

Type        : Risque conformité (auditabilité)
Référence   : §5.5 + N-05
Description : « Crash post-décision / pré-observabilité » reconnu comme cas non couvert. Au moment d'un audit (ANSSI, ISO 27001, SOC 2), un incident où des refus rate-limit sont décidés mais non journalisés n'est pas démontrable.
Impact      : Trou d'audit. Article III CONSTITUTIONAL (Traçabilité) exige audit complet — INV-172-03 admet une exception non mitigée.
Gravité     : Majeur

R-05

Type        : Risque sécurité (énumération)
Référence   : §6 ERR-172-03 (`400`)
Description : ERR-172-03 distingue dimension invalide / contexte incomplet / regex échouée derrière un même `400`. Bonne pratique anti-énumération (cf. learning « Anti-enumeration sur messages d'erreur » 2026-03-08), mais aucun observable empêche l'implémentation de produire des messages distincts en payload (« missing tenant », « missing user »).
Impact      : Risque d'énumération si messages détaillés exposés en payload `400`. Spec silencieuse sur le contenu du body d'erreur.
Gravité     : Mineur

R-06

Type        : Risque sécurité (réseau)
Référence   : §10.1 « Backend de rate limiting distribué : Redis » sans isolation ni TLS
Description : Aucun contrat sur l'authentification Redis (ACL Redis 6+, TLS), la ségrégation réseau (VPC privé), ou la rotation des credentials. Un Redis exposé = bypass du rate-limit entier (vol de quota, manipulation des compteurs).
Impact      : Hors périmètre PD-172 strict mais risque acutel et non documenté comme dépendance externe.
Gravité     : Majeur

R-07

Type        : Risque conformité (article VIII formel)
Référence   : §10.1 (TypeScript/NestJS) + manque de spec formelle en §10.2
Description : INV-172-06 (machine d'états) est candidat à vérification formelle TLA+/Alloy (états, transitions, invariants distribués). La spec ne mentionne pas si une vérification formelle est attendue par l'article VIII CONSTITUTIONAL pour la cohérence inter-instance INV-172-01.
Impact      : Si fail-closed Article VIII activé sur ce scope, blocage Gate 5/8.
Gravité     : Mineur

R-08

Type        : Risque sécurité (réplay-mode-dégradé)
Référence   : §5.4 BYPASS_DEGRADED + H-05
Description : En BYPASS_DEGRADED, aucun comptage local de secours n'est imposé. Un attaquant qui détecte le mode dégradé (par latence ou en provoquant l'incident Redis) peut exploiter une fenêtre de bruteforce illimité sur les routes `read` (énumération, scraping).
Impact      : Fenêtre d'attaque non bornée durant incident Redis.
Gravité     : Majeur

7. Synthèse par invariant

Invariant Tests Gravité d'écart Bloquant Gate 3 ?
INV-172-01 TC-NOM-01/04/05/06 Hypothèse atomicité (H-04) + C-01 Oui (C-01)
INV-172-02 TC-NOM-02/10/11, TC-ERR-03 Atomicité non spécifiée (H-04) Non
INV-172-03 TC-NOM-07, TC-ERR-01/02/06 Trou crash post-décision (A-06, R-04) Non
INV-172-04 TC-NOM-03 Liste sensible incomplète (A-03) Non
INV-172-05 TC-NOM-09/10/11, TC-ERR-06 « Indisponibilité » non définie (A-01) Oui (A-01)
INV-172-06 TC-NOM-15/16 Contradiction état requête vs système (C-02) Non
INV-172-07 TC-NOM-14 Tension RG-8 vs no-whitelist (C-07) Non
INV-172-08 TC-NOM-13, TC-ERR-05 Aucun écart majeur Non
INV-172-09 TC-INV-09 C-01, C-03 (auto-déclarée) Oui (C-01, C-03)
INV-172-10 TC-NOM-17 Aucun écart majeur Non

8. Synthèse par critère d'acceptation

CA Tests Testabilité Bloquants associés
CA-172-01 TC-NOM-01 Partielle (atomicité H-04) C-01
CA-172-02 TC-NOM-02, TC-ERR-01 Partielle (seuils absents) C-04, C-08
CA-172-03 TC-NOM-03 Partielle (liste sensible) A-03
CA-172-04 TC-NOM-04 Partielle (format identifier) C-03
CA-172-05 TC-NOM-05/06 Partielle (seuils absents) C-08
CA-172-06 TC-NOM-07 OK sur principe A-06 (mineur)
CA-172-07 TC-NOM-08 Partielle (granularité) A-10, C-06
CA-172-08 TC-NOM-09/10/11 Partielle (« indisponibilité ») A-01
CA-172-09 TC-NOM-12 Partielle (charge non définie) A-12
CA-172-10 TC-NOM-13 OK

9. Verdict d'auditeur (sans correction proposée)

  • Bloquants : 4 (C-01 regex, C-03 identifier, C-04 code HTTP fail-closed, C-08 seuils numériques) — la spec ne ferme pas le contrat sur des éléments observables exigés par les CA.
  • Majeurs : 13 — contradictions ou ambiguïtés qui empêchent une implémentation tierce déterministe et un audit complet.
  • Mineurs : 11 — précisions ou clarifications utiles mais non bloquantes.

La spécification PD-172 est partiellement testable comme les auteurs le reconnaissent en §10. Les 4 bloquants identifiés débordent le champ des questions ouvertes auto-déclarées (Q-172-*) et touchent à la cohérence interne du document (regex, machine d'états, source de vérité unique). Tant que ces 4 points ne sont pas tranchés, INV-172-01 / INV-172-09 et CA-172-02/04/05 ne sont pas validables contractuellement.

⚠️ Aucune correction n'est proposée dans ce document. ⚠️ Aucune reformulation des règles existantes n'est suggérée. ⚠️ Aucune implémentation n'est décrite.