PD-295 — Review Gate 3 (spécification cycle 3 v1)¶
Auditeur technique indépendant. Analyse contractuelle, testabilité, conformité. Aucune reformulation, aucune correction, aucune implémentation proposée.
Écart 1¶
Type : Contradiction Référence : spec §5.7 reuse_score_clip_threshold_pre_round + D-295-32 (fail_closed_depth 0..4) vs INV-295-17 (fail_closed_depth_max=3) Description : D-295-32 borne fail_closed_depth à 0..4 avec note « 4 autorisé uniquement au moment de l'abort », mais INV-295-17 fixe fail_closed_depth_max=3 et §5.6 étape 9 déclenche l'arrêt « si fail_closed_depth > 3 ». La borne canonique de la donnée contredit la règle d'arrêt (si la valeur 4 est « autorisée au moment de l'abort », elle n'est pas « > 3 arrêt » mais « == 4 arrêt »). Impact : ambiguïté sur la condition exacte de déclenchement de ERR-295-FAIL_CLOSED_DEPTH_EXCEEDED ; TC-NEG-17 peut interpréter de deux manières (depth=3 accepté puis depth=4 déclenche, ou depth=3 déclenche). Gravité : Majeur
Écart 2¶
Type : Contradiction Référence : INV-295-09 (spec §4.1) vs §5.15 + vecteur V6 §5.14 Description : INV-295-09 stipule « clé Vault chargée une seule fois au démarrage de /gov, immuable pour toute la session, aucune relecture Vault en cours de session ». Or §5.15 prévoit scripts/rotate-audit-hmac.sh et le vecteur V6 (event_type=audit_key_rotated, previous_key_version=1, new_key_version=2) implique une rotation en cours d'exécution. Une session /gov longue (plusieurs heures, multi-étapes) ne peut donc pas consommer une rotation sans contradiction avec l'immutabilité de session. Impact : le contrat de rotation est non applicable pendant une session en cours ; TC-STATE-02 (re-signature post-rotation) est non testable sans rupture d'INV-295-09. Gravité : Majeur
Écart 3¶
Type : Contradiction Référence : INV-295-09 vs diagramme séquence §5bis (B5 alt key absente (démarrage session)) Description : INV-295-09 exige chargement au démarrage de /gov et arrêt immédiat si Vault KO à ce moment. Le diagramme séquence B5 (fait foi contractuelle) prévoit pourtant une branche alt key absente en mémoire (démarrage session) dans laquelle B5 lit Vault et peut déclencher sys.exit. Un invariant « chargement unique au démarrage de /gov » est incompatible avec un chargement lazy déclenché par B5. Impact : le point d'initialisation de la clé et le périmètre de l'arrêt fail-closed ne sont pas consolidés ; CA-295-06 est ambigu (arrêt au démarrage /gov OU arrêt à la première injection). Gravité : Majeur
Écart 4¶
Type : Ambiguïté / Incohérence Spec↔Tests Référence : INV-295-LS-01 + §5.9.1 « STORY_ACTIVE -> DOMAIN_ACTIVE: seuil domain » Description : INV-295-LS-01 conditionne la promotion STORY_ACTIVE -> DOMAIN_ACTIVE à reuse_score>=0.3 ET nb_domains>=1. Or un learning STORY_ACTIVE est par définition attaché à une story dans un unique domaine, donc nb_domains>=1 est trivialement vrai dès la création. La seule condition effective devient reuse_score>=0.3, et le seuil nb_domains>=1 est soit mort, soit le terme nb_domains désigne un concept différent (nb de domaines dans lesquels le learning a été injecté) non défini dans les Définitions §3 ni dans D-295-*. Impact : TC-NOM-11 ne peut pas construire de cas négatif sur nb_domains ; risque de tests silencieusement tautologiques. Gravité : Majeur
Écart 5¶
Type : Non testable Référence : D-295-17 (pii_ruleset_v1) + CA-295-02 + TC-NEG-08 Description : Les patterns PII (email, phone, IBAN, adresse explicite) sont référencés sous le nom pii_ruleset_v1 mais la spécification ne fournit ni l'expression formelle (regex, cardinalités, locales), ni le document de référence opposable. Q-295-05 acte que la validation DPO reste à obtenir. CA-295-02, TC-NOM-02 et TC-NEG-08 reposent intégralement sur un ruleset dont la définition n'est pas figée dans la spec. Impact : la détection « PII » n'est pas un observable contractuel ; impossible d'arbitrer ERR-295-PII_DETECTED de manière reproductible. Gravité : Bloquant (RGPD, Article V acceptabilité)
Écart 6¶
Type : Hypothèse dangereuse Référence : §5.3 étape 3 (env CLAUDE_DISABLE_SESSION_LOG=1, TERM=dumb) + INV-295-RUNTIME-01 Description : L'isolation runtime repose sur l'hypothèse que la variable CLAUDE_DISABLE_SESSION_LOG=1 désactive effectivement la journalisation du stdin dans claude -p. Cette variable n'est ni référencée dans la documentation officielle Claude Code citée en spec, ni contractualisée comme API stable. Le périmètre exact de ce que logge claude -p (stdin, stdout, argv, messages API) n'est pas figé par PD-295. Impact : une évolution mineure du binaire claude peut casser INV-295-RUNTIME-01 sans signal. CA-295-RUNTIME-01 vérifie l'état data/ à un instant T, pas les canaux exhaustifs (session API distante, trace TLS, fichiers cache utilisateur hors data/). Gravité : Majeur
Écart 7¶
Type : Hypothèse dangereuse / Risque sécurité Référence : INV-295-RUNTIME-02 (« aucun fallback tmpfs/macOS ») + INV-295-BACKUP-01 (tmutil addexclusion) Description : INV-295-BACKUP-01 mobilise tmutil/.nobackup (macOS Time Machine) alors que H-295-06 autorise un déploiement mono-hôte « laptop dev OU serveur backend unique ». Sur un serveur backend Linux, tmutil n'existe pas, aucun mécanisme alternatif n'est contractualisé (Vault backup, rsnapshot, etc.), et l'invariant devient silencieusement inapplicable. Impact : CA-295-BACKUP-01 n'est testable que sur macOS ; en production Linux, aucune garantie contractuelle d'exclusion des artefacts sensibles des backups. Gravité : Majeur
Écart 8¶
Type : Incohérence Spec↔Tests Référence : TC-NOM-06-bis (tests.md §3) vs §5.14 Description : TC-NOM-06-bis référence « objet arbitraire schéma v3 » avec signature attendue 2afd2cd6dc…3ab0, sans préciser l'objet JSON ni la key_hex utilisés. Les vecteurs V1/V4/V6/V8 sont explicites et reproductibles, mais TC-NOM-06-bis ne l'est pas. Impact : test non déterministe / non reproductible ; CA-295-05 partiellement non falsifiable. Gravité : Majeur
Écart 9¶
Type : Incohérence Spec↔Tests Référence : §6 ERR-295-MEASURE_SCRIPT_MISSING vs D-295-22 (enum event_type) Description : ERR-295-MEASURE_SCRIPT_MISSING est défini comme erreur bloquante Gate 8 mais n'apparaît pas comme event_type dans D-295-22. Soit l'erreur ne génère aucun événement audit (contradiction avec INV-295-06 qui exige la signature des événements audit B1..B5), soit l'enum D-295-22 est incomplet. Impact : trace auditable de l'échec CA-295-meta non contractualisée. Gravité : Mineur
Écart 10¶
Type : Incohérence Spec↔Tests Référence : §5.7 clock_drift_max=500ms + TC-NEG-10 (« dérive horloge >500ms au trace ») vs §5.13 guards G-295-01..10 Description : La dérive horloge >500ms est testée en scénario négatif (TC-NEG-10 → fail-closed) mais aucun guard G-295-* ne porte l'observable « drift horloge » dans §5.13. TC-NEG-16 (« bypass guard 10 cas G-295-01..10 ») n'inclut donc pas la vérification temporelle. Impact : la règle clock_drift_max n'est pas ancrée dans un guard traçable ; TC-NEG-10 repose sur un mécanisme non nommé dans la spec. Gravité : Majeur
Écart 11¶
Type : Ambiguïté Référence : D-295-13 retention_until + §5.7 retention_clarifications=540 jours + CL-05 Description : Le format retention_until=YYYY-MM-DD est contractualisé mais la règle de calcul (à partir de quel événement : VERBATIM_IN_MEMORY, SUMMARY_VALIDATED, SUMMARY_INDEXED ?) et la source de temps (UTC, horloge session, horloge fichier) ne sont pas spécifiées. Impact : deux implémentations conformes peuvent produire un retention_until divergent de plusieurs jours ; TC-NOM-05 (« date > retention_until ») non déterministe. Gravité : Majeur
Écart 12¶
Type : Contradiction Référence : INV-295-04 (6 artefacts purgés) vs CA-295-04 (« 6 artefacts + traces query + PD-XX-clarifications.md ») Description : INV-295-04 énumère 6 paths incluant déjà docs/epics/**/PD-XX-*/PD-XX-clarifications.md. CA-295-04 ajoute « traces query » comme élément supplémentaire distinct des 6 artefacts, alors que les traces query résident dans data/sessions/*.jsonl (déjà inclus dans INV-295-04). Le décompte « 6 artefacts » est contradictoire avec l'énumération verbale de CA-295-04. Impact : TC-NOM-04 (« 6 artefacts supprimés … + traces query supprimées ») confond deux référentiels ; risque de purge incomplète selon l'interprétation. Gravité : Majeur
Écart 13¶
Type : Ambiguïté Référence : §5.12 « Réconciliation lock: stale strict >60s, suppression + retry unique » Description : La « retry unique » n'est pas bornée dans le temps. Deux interprétations : (a) retry immédiat après suppression du lock stale, (b) retry après délai de grâce non spécifié. Aucune règle sur l'intervalle entre suppression et retry, ni sur ce qui se passe si un autre process acquiert le lock dans l'intervalle. Impact : TC-ERR-09 LOCK_STALE_RECOVERY_FAILED non falsifiable avec précision. Gravité : Mineur
Écart 14¶
Type : Hypothèse dangereuse Référence : INV-295-STATE-04 (hook on_story_close sur REJECTED, DONE_WITH_ANOMALY, DONE) Description : La spec ne traite pas le cas d'une story transitant à REJECTED puis ré-ouverte (restauration manuelle, erreur de transition). Si le hook purge dès REJECTED, une ré-ouverture produit un état clarification PURGED/REJECTED terminal (INV-295-CL-07/08) incompatible avec une reprise. Impact : pas de contrat de rollback ; risque de perte de données légitime sur faux positif REJECTED. Gravité : Majeur
Écart 15¶
Type : Risque sécurité / conformité Référence : §5.6 étape 8 + INV-295-10 Description : Si ERR-295-TRACE_WRITE_FAILED survient après que B5 ait partiellement construit le bloc d'injection en mémoire, aucun contrat n'exige l'effacement actif du bloc avant retour EMPTY_BLOCK. Un caller step 0 bogué pourrait réutiliser un buffer résiduel. INV-295-10 stipule « bloc vide » mais ne contractualise pas l'effacement explicite du buffer intermédiaire côté helper. Impact : zone grise fail-closed ; ST-295-26 / TC-ERR-13 ne vérifient pas cette sémantique. Gravité : Mineur
Écart 16¶
Type : Incohérence Spec↔Tests Référence : INV-295-STATE-04 mentionne « hook on_story_close(story_id, final_state) appelé par .claude/commands/gov.md » + TC-NOM-20 Description : L'appelant du hook est fixé (.claude/commands/gov.md) mais le mode d'appel (inline, subprocess, event broker), la gestion des erreurs (fail-closed ? warn+continue ?) et l'ordonnancement vis-à-vis de la transition Jira ne sont pas contractualisés. TC-NOM-20 vérifie l'idempotence mais pas la robustesse aux erreurs du hook. Impact : un échec du hook (ex. purge partielle) n'a pas de règle — contournable silencieusement. Gravité : Majeur
Écart 17¶
Type : Ambiguïté / Cohérence diagrammes Référence : §5bis diagramme clarification.lifecycle_state Description : Le diagramme Mermaid ne représente pas explicitement REJECTED et PURGED comme états terminaux (--> [*] absent), alors que INV-295-CL-07/08 les déclarent terminaux. La transition SUMMARY_INDEXED -> EXPIRED est représentée mais la source de temps (§D-295-13 ambigu, écart 11) n'apparaît pas. Impact : diagramme cohérent avec invariants textuels sur la topologie mais sous-spécifié visuellement ; faible. Gravité : Mineur
Écart 18¶
Type : Ambiguïté / Cohérence diagrammes Référence : §5bis diagramme séquence B5 Description : Le diagramme séquence B5 ne représente pas (a) la branche fail_closed_depth > 3 (arrêt process), (b) la branche rotation de clé V6, © la lecture rwlock explicite sur learnings-scores.jsonl. Les transformations entre payload_canonique et sig_hmac_sha256 (JCS canonicalisation, exclusion du champ signature) ne sont pas matérialisées dans le diagramme séquence. Impact : diagramme incomplet vis-à-vis des scénarios critiques ; risque d'implémentation divergente sur ces branches. Gravité : Mineur
Écart 19¶
Type : Non testable Référence : INV-295-02 (invariant de code) Description : « Le verbatim PO reste en mémoire volatile et est détruit en sortie de phase 1 bis ». La destruction mémoire Python (io.StringIO GC'd) n'est pas un observable externe : scripts/check-no-verbatim-persisted.sh peut grep data/ mais ne peut pas prouver l'absence en mémoire process avant GC. TC-INV-02 dépend d'un lint statique sans critère formel publié. Impact : invariant de code faible, dépendant d'une heuristique non contractualisée. Gravité : Mineur
Écart 20¶
Type : Hypothèse dangereuse Référence : §5.14 vecteurs HMAC pré-calculés V1/V4/V6/V8 Description : Les vecteurs figent dans la spec des signatures concrètes dépendant strictement de l'implémentation JCS RFC 8785 choisie (canonicalisation des nombres, tri des clés, encodage UTF-8). Aucune bibliothèque de référence n'est nommée (json-canonical-form, jcs-rs, implémentation maison). Deux bibliothèques JCS conformes peuvent diverger sur les edge cases (nombres à virgule, clés Unicode) et produire des signatures différentes. Impact : CA-295-05 peut passer/échouer selon la dépendance choisie, sans moyen de trancher contractuellement. Gravité : Majeur
Synthèse quantitative¶
| Gravité | Nombre |
|---|---|
| Bloquant | 1 |
| Majeur | 12 |
| Mineur | 7 |
| Total | 20 |
Axes non couverts¶
- 6 Risques sécurité/conformité : couvert (écarts 5, 7, 15, 20 + hypothèses runtime).
- 5bis Cohérence diagrammes : couvert (écarts 17, 18).
- 4 Incohérences Spec↔Tests : couvert (écarts 8, 9, 10, 12, 16).
Périmètre audit tests¶
Audit limité aux ambiguïtés, contradictions et hypothèses dangereuses (conformément à l'instruction). Aucune conclusion sur la couverture quantitative.