Aller au contenu

PD-295 — Specification Review (cycle 3 v2)

Auditeur : Claude (revue contractuelle indépendante) Documents audités : PD-295-specification.md (cycle 3 v2), PD-295-tests.md (cycle 3 v2) Périmètre tests : ambiguïtés, contradictions, testabilité a priori, hypothèses dangereuses, risques sécu/conformité (pas de conclusion sur la couverture).


E-01

  • Type : Contradiction
  • Référence : spec §4.1 INV-295-11 vs §5.1 D-295-26 vs §5.14 vecteur V8
  • Description : INV-295-11 fixe count_configured à 5/3/3 de manière rigide ("B5 fixe count_configured à 5/3/3"). D-295-26 élargit l'énumération à "5/3/3 (ou 6 pour purge RGPD)", et le vecteur V8 utilise count_configured=6 pour un événement clarification_purged en brique B2. L'invariant B5 est appliqué à un événement qui n'est pas B5, sans règle explicite couvrant le cas B2/purge.
  • Impact : Testeur ne peut pas déterminer si count_configured=6 est conforme ou une violation d'invariant. Implémenteur et reviewer auront des lectures divergentes. Vecteur V8 risque d'être invalidé par un contrôle strict INV-295-11.
  • Gravité : Majeur

E-02

  • Type : Contradiction
  • Référence : spec §4.1 INV-295-STATE-04 vs §5.9.2 vs INV-295-CL-05/06/15
  • Description : INV-295-STATE-04 déclenche purge-clarifications.py --story sur on_story_close(REJECTED, DONE_WITH_ANOMALY, DONE). Mais la machine à états §5.9.2 n'autorise que SUMMARY_INDEXED -> EXPIRED -> PURGED, conditionné par date > retention_until. Purger une clarification SUMMARY_INDEXED non expirée à la clôture de story = transition SUMMARY_INDEXED -> PURGED interdite par INV-295-15 (ERR-295-STATE_TRANSITION_FORBIDDEN).
  • Impact : Flux on_story_close produira soit une violation d'invariant d'état, soit une impossibilité de purger avant 540 jours. TC-NOM-20/22 sont testables mais contradictoires avec TC-NEG-07.
  • Gravité : Bloquant

E-03

  • Type : Incohérence Spec↔Tests
  • Référence : spec §8 (ST-295-01..28) vs tests.md
  • Description : La spec §8 référence des scénarios ST-295-01..28. Le document tests utilise exclusivement TC-NOM-*, TC-ERR-*, TC-NEG-*, TC-RUNTIME-*, TC-STATE-*, TC-META-*, TC-INV-*, TC-NR-*. Aucune table de correspondance ST → TC.
  • Impact : Traçabilité Spec↔Tests exigée par Gate 5 cassée. Un auditeur ne peut pas vérifier si ST-295-21..28 sont tous couverts.
  • Gravité : Majeur

E-04

  • Type : Hypothèse dangereuse
  • Référence : spec §5.3 (B2 steps 2-3) + INV-295-RUNTIME-01
  • Description : L'architecture impose un sous-subprocess claude -p appelé depuis b2-sanitizer.py, lui-même déjà lancé depuis une session Claude Code active (step 0). L'env imposé se limite à CLAUDE_DISABLE_SESSION_LOG=1 et TERM=dumb. La variable CLAUDECODE n'est pas désarmée alors qu'elle est connue pour faire échouer claude -p lancé depuis une session Claude Code existante (learning universel 2026-02-20, CLAUDE.md). Hypothèse implicite : claude -p fonctionne dans ce contexte sans unset CLAUDECODE.
  • Impact : Brique B2 échouera à l'exécution en conditions réelles. Tous les CA B2 et tests TC-RUNTIME-01/02/03 échoueront. Aucune détection possible avant implémentation.
  • Gravité : Bloquant

E-05

  • Type : Ambiguïté
  • Référence : spec §5.12 + INV-295-BACKUP-01 + tests TC-NOM-21
  • Description : La spec impose tmutil addexclusion (macOS) ou .nobackup comme mécanisme d'exclusion backup. §5.12 autorise "laptop dev OU serveur backend unique", impliquant Linux côté serveur. tmutil n'existe pas sur Linux. TC-NOM-21 utilise tmutil isexcluded. Aucun critère ne précise quel mécanisme s'applique selon OS, ni ce que fait le système si tmutil est indisponible.
  • Impact : CA-295-BACKUP-01 non testable en environnement serveur. INV-295-BACKUP-01 non vérifiable universellement.
  • Gravité : Majeur

E-06

  • Type : Ambiguïté
  • Référence : spec §5.13 G-295-09 + §5.8 + §6
  • Description : G-295-09 combine deux préconditions hétérogènes ("drift horloge + clé Vault disponible") en une seule règle avec deux codes d'erreur possibles. De plus, timestamp_ntp_reference n'est défini nulle part (quel serveur NTP ? quel pool ? procédure offline ?). §5.8 mentionne clock_drift_max=500ms sans préciser la source de référence.
  • Impact : Test TC-NEG-10 non reproductible : impossible de savoir contre quelle référence la dérive est mesurée. Un implémenteur peut choisir une source arbitraire, l'auditeur ne peut pas détecter la dérive.
  • Gravité : Majeur

E-07

  • Type : Contradiction
  • Référence : spec §7 CA-295-02 vs §10 Q-295-05
  • Description : CA-295-02 exige que pii_ruleset_v1.yaml soit "validé DPO par commit signé" comme condition d'acceptabilité. §10 Q-295-05 liste "Validation DPO des patterns PII pii_ruleset_v1" comme point à clarifier non résolu.
  • Impact : Le critère d'acceptation dépend d'un input non fourni. Gate 8 ne pourra pas être passée sans résolution préalable de Q-295-05.
  • Gravité : Majeur

E-08

  • Type : Ambiguïté
  • Référence : spec §5.14 (vecteurs V1, V4, V6, V8) + §7 CA-295-05
  • Description : CA-295-05 exige la vérification des "vecteurs V1/V4/V6/V8". Les numéros manquants (V2, V3, V5, V7) suggèrent l'existence d'autres vecteurs non fournis dans la spec, ou un choix arbitraire de numérotation. Aucune note explique la discontinuité.
  • Impact : Un testeur ignore s'il manque 4 vecteurs clés ou si la numérotation est simplement lacunaire. Risque de croire à un corpus de tests incomplet.
  • Gravité : Mineur

E-09

  • Type : Hypothèse dangereuse
  • Référence : spec INV-295-09 + §5.15
  • Description : INV-295-09 affirme "clé immuable pour toute la session" et "rotation hors session". §5.15 mentionne scripts/rotate-audit-hmac.sh qui re-signe l'existant. Aucun invariant ou guard ne garantit qu'aucune session /gov n'est active pendant l'exécution de rotate-audit-hmac.sh (pas de lock exclusif entre le script de rotation et les sessions).
  • Impact : Fenêtre de course : session démarre avec clé v1, rotate re-signe en v2, session écrit une trace v1 dans un fichier déjà re-signé v2 → lignes incohérentes filtrées silencieusement à la lecture (INV-295-STATE-02). Perte d'audit invisible.
  • Gravité : Majeur

E-10

  • Type : Contradiction
  • Référence : spec §5.6 B5 step 8 + step 9 + D-295-32 + INV-295-17
  • Description : Step 8 : si écriture trace échoue et fail_closed_depth < 3, incrémenter puis retourner EMPTY_BLOCK. Step 9 : nouvelle erreur qui impose d'incrémenter de 3 vers 4 → arrêt process. Comportement non spécifié quand fail_closed_depth == 3 ET l'écriture trace échoue sans erreur imbriquée : la condition du step 8 est fausse, le step 9 pas déclenché.
  • Impact : Trou contractuel : l'implémenteur choisira arbitrairement entre arrêter, retourner EMPTY_BLOCK, ou boucler. TC-NEG-17 ne couvre pas ce cas intermédiaire.
  • Gravité : Majeur

E-11

  • Type : Contradiction
  • Référence : spec §7 CA-295-04 vs §4.1 INV-295-04
  • Description : INV-295-04 énumère exactement 6 artefacts purgés. CA-295-04 lit "Purge RGPD 6 artefacts + traces query + PD-XX-clarifications.md", ce qui laisse entendre 6 + 2 = 8 éléments, alors que les "traces query" et clarifications.md sont déjà deux des 6.
  • Impact : Confusion sur le périmètre exact de la purge. Auditeur RGPD peut conclure à un sous-ensemble manquant.
  • Gravité : Mineur

E-12

  • Type : Non testable
  • Référence : spec INV-295-14 + §5.6 step 1 + §5.12
  • Description : INV-295-14 restreint explicitement rwlock à deux fichiers : learnings.jsonl et learnings-scores.jsonl. §5.6 step 1 de B5 dit "Acquisition locks lecture rwlock" sans préciser lesquels, alors que B5 lit aussi veille.jsonl, clarifications.jsonl, index FAISS, et écrit en session. §5.12 parle de "fichiers ciblés" sans liste exhaustive.
  • Impact : Impossible de tester TC-NOM-15 sans choix d'implémentation arbitraire sur le périmètre exact des fichiers lockés. Couverture concurrence B5 indéterminable.
  • Gravité : Majeur

E-13

  • Type : Incohérence Spec↔Tests
  • Référence : spec INV-295-CL-04 vs tests matrice §2
  • Description : La matrice déclare INV-295-CL-04 (SUMMARY_VALIDATED -> SUMMARY_INDEXED après écriture résumé) couvert par TC-NOM-14. TC-NOM-14 vérifie transitions CL-02/03 (validation PO), pas explicitement CL-04 (écriture du résumé indexé).
  • Impact : Transition SUMMARY_VALIDATED -> SUMMARY_INDEXED potentiellement non observée en Gate 8.
  • Gravité : Mineur

E-14

  • Type : Hypothèse dangereuse
  • Référence : spec §5.12 "Non-répudiation" + INV-295-06..08
  • Description : La spec reconnaît que HMAC symétrique ne fournit pas la non-répudiation forte ("administrateur avec accès Vault peut forger") et reporte la non-répudiation en candidat G33. Pour autant, les preuves d'audit PD-295 sont utilisées comme base de CA-295-05..08 et de la conformité RGPD (CA-295-04). Aucune mention de l'exposition résiduelle dans les hypothèses §9.
  • Impact : Sous-estimation du risque juridique : preuves HMAC peuvent être contestées. Risque de non-conformité si audit externe exige preuves non-répudiables.
  • Gravité : Majeur

E-15

  • Type : Risque sécurité/conformité
  • Référence : spec §5.3 B2 + INV-295-RUNTIME-02
  • Description : INV-295-RUNTIME-02 interdit "tout fichier disque" pour le verbatim, mais autorise os.pipe(). Le contenu transite via buffers kernel en mémoire, visible dans /proc/<pid>/fd/* tant que le pipe est ouvert. Aucune garantie de zéroisation mémoire après effacement des buffers (§5.3 step 9 mentionne "effacement" sans exiger zeroize()/overwrite explicite).
  • Impact : Résidus verbatim potentiels en mémoire process. TC-RUNTIME-01 ne couvre pas la mémoire process (seulement disque et argv). CA-295-RUNTIME-01 testable partiellement.
  • Gravité : Majeur

E-16

  • Type : Ambiguïté
  • Référence : spec D-295-22 (enum event_type)
  • Description : L'énumération event_type inclut injection_failed_fail_closed qui n'apparaît nulle part dans les flux §5.2..5.6, ni dans les invariants, ni dans les CA, ni dans les tests. Valeur d'enum orpheline.
  • Impact : Un implémenteur peut émettre cet événement dans des conditions non spécifiées sans être détecté comme non conforme.
  • Gravité : Mineur

E-17

  • Type : Incohérence Spec↔Tests (cohérence diagramme)
  • Référence : spec §5bis — Diagramme de séquence B5
  • Description : Le diagramme de séquence B5 montre uniquement le chemin "fail-closed minimal" (guard → rwlock → key → signed event). Il n'explicite pas les transformations de données (construction du payload_canonique, étape JCS, étape HMAC, exclusion du champ signature avant HMAC), alors que §5.14 exige JCS RFC 8785 + HMAC-SHA256 avec champ signature exclu. Implémenteur peut placer la canonicalisation au mauvais endroit, cassant la reproductibilité des vecteurs V1/V4/V6/V8.
  • Impact : Risque de divergence de signature non détectable avant TC-NOM-06. Axe 5bis (testabilité séquence).
  • Gravité : Mineur

E-18

  • Type : Incohérence Spec↔Tests (cohérence diagramme)
  • Référence : spec §5bis — Diagramme d'état learning.scope vs INV-295-LS-01
  • Description : Le diagramme montre STORY_ACTIVE --> DOMAIN_ACTIVE: seuil domain sans expliciter que la condition est reuse_score>=0.3 ET nb_domains>=1. INV-295-LS-01 ajoute "un learning jamais injecté (nb_domains=0) reste scope:story", non représenté dans le diagramme (axe 5bis : transition documentée en texte absente du diagramme). TC-NOM-11 teste ce cas mais le diagramme le masque.
  • Impact : Gap visuel sur un cas fonctionnel testé. Lecture rapide trompeuse.
  • Gravité : Mineur

E-19

  • Type : Non testable
  • Référence : spec §5.12 "Clearing: MEMORY_DEGRADED puis MEMORY_HEALTHY après 2 cycles conformes" + §11
  • Description : Aucun invariant formel ne définit ce qu'est un "cycle conforme". Les états MEMORY_DEGRADED/MEMORY_HEALTHY sont mentionnés dans §11 (observabilité) mais aucun CA ni test ne les couvre. Aucune règle de transition entre états mémoire (qui incrémente le compteur ? comment reset ?).
  • Impact : Observabilité non testable. Gate 8 ne peut pas vérifier le comportement du clearing.
  • Gravité : Majeur

E-20

  • Type : Hypothèse dangereuse
  • Référence : spec §5.14 vecteur V6 + INV-295-09
  • Description : Le vecteur V6 (rotation clé v1→v2) est signé avec key_hex=aaaaaaaa... (nouvelle clé v2), pas avec l'ancienne clé v1. Sémantiquement, un événement "rotation v1→v2" devrait être signé par la clé sortante (v1) pour être vérifiable par un auditeur ne connaissant que v1. Aucun contrat n'explique cette convention.
  • Impact : Un auditeur lisant l'historique avec la clé archivée v1 ne pourra pas vérifier l'événement de rotation lui-même, cassant la chaîne d'audit exactement au point de changement de clé.
  • Gravité : Majeur

Synthèse

Gravité Nombre IDs
Bloquant 2 E-02, E-04
Majeur 11 E-01, E-03, E-05, E-06, E-07, E-09, E-10, E-12, E-14, E-15, E-19, E-20
Mineur 7 E-08, E-11, E-13, E-16, E-17, E-18

Les deux écarts bloquants : (1) machine à états clarifications vs hook on_story_close (transition interdite appelée sur clôture de story non expirée), (2) architecture B2 subprocess qui ignore le learning connu sur claude -p depuis une session Claude Code active (absence d'unset CLAUDECODE).