PD-295 — Review de spécification (Gate 3, v3)¶
Auditeur indépendant. Aucune correction proposée. Aucune reformulation. Aucune implémentation.
Documents audités :
PD-295-specification.md(v3)PD-295-tests.md(v3)
Limitation tests : audit borné aux ambiguïtés, contradictions, hypothèses dangereuses, risques, sans conclusion sur la couverture globale.
Axe 1 — Ambiguïtés¶
Type : Ambiguïté
Référence : spec §5.1.6 (« Clé HMAC »), INV-295-20
Description : La clé HMAC est décrite comme « binaire 32 octets » mais générée
via `openssl rand -hex 32` (qui produit 64 caractères hex
représentant 32 octets). Le format effectivement stocké dans
Vault (`kv/pd-295/memory-audit-hmac`) — bytes bruts ou string
hex — n'est pas contractualisé. La fonction `vault_read(...)
.data.key` consommée par le signataire est sous-spécifiée.
Impact : Deux implémentations conformes à la lettre peuvent produire
des signatures incompatibles ; la vérification a posteriori
(TC-NOM-26 / TC-NOM-26-bis) peut diverger entre version active
et archive si l'encodage n'est pas fixé.
Gravité : Majeur
Type : Ambiguïté
Référence : spec §5.1.6 (« Format événement »), INV-295-20
Description : Le champ signé est nommé `payload_canonique` mais aucune
définition n'est donnée : ni la liste exacte des champs
inclus, ni l'ordre, ni l'encodage (JSON canonique RFC 8785 ?
concaténation ?), ni l'inclusion ou non du champ
`signature_hmac_sha256` lui-même.
Impact : Vérification HMAC non reproductible entre implémentations.
Bloque la promesse d'audit probatoire de l'INV-295-20.
Gravité : Bloquant
Type : Ambiguïté
Référence : spec §5.12 (« Horloge »)
Description : Le seuil de dérive d'horloge déclenchant `MEMORY_DEGRADED`
n'est pas chiffré ; aucun observable, aucune valeur min/max
dans la table §5.2.
Impact : Règle non implémentable de manière déterministe.
Gravité : Mineur
Type : Ambiguïté
Référence : spec INV-295-21, §5.6 N2 étape 7, tests TC-NOM-27
Description : « Rétention 18 mois » et « purger >18 mois » coexistent sans
clarification stricte vs large : à exactement 18 mois
(`retention_until == today`), l'entrée est-elle conservée ou
purgée ? TC-NOM-27 fixe « 18 mois conservé », mais §5.3.1
définit `retention_until = date + 18 mois`, ce qui
déclencherait normalement la purge à cette date.
Impact : Comportement bord indéterminé entre spec et test.
Gravité : Mineur
Type : Ambiguïté
Référence : spec INV-295-22, §5.7 étape 7
Description : « Succès observable par code retour 0 » est l'unique critère
testable. Aucun observable ne valide que les 3 entrées
publiées sont effectivement les top reuse_score (un
`/morning` qui publie 3 entrées arbitraires sortirait 0 et
passerait TC-NOM-08).
Impact : INV-295-22 (« top reuse_score ») n'est pas testable a priori
dans l'observable retenu.
Gravité : Majeur
Type : Ambiguïté
Référence : spec §5.1.6 (`schema_version: 3`)
Description : Le champ `schema_version` apparaît dans l'exemple
d'événement mais aucun invariant ne définit la politique de
compatibilité (acceptation des versions antérieures par le
vérificateur, refus, migration).
Impact : Vérification d'événements historiques après évolution du
schéma indéfinie.
Gravité : Mineur
Type : Ambiguïté
Référence : spec INV-295-07, §3
Description : `reuse_score` est défini « borné dans `[0,1)` » et
« sérialisé à 6 décimales ». Pour `reuse_score_brut`
suffisamment grand, `tanh(x/10)` arrondi à 6 décimales
atteindra `1.000000`, contredisant la borne ouverte.
Impact : Règle d'invariant violable par arrondi ; ambiguïté entre
valeur mathématique et valeur sérialisée.
Gravité : Mineur
Axe 2 — Contradictions¶
Type : Contradiction
Référence : spec ERR-295-12 vs INV-295-13
Description : INV-295-13 impose « L'injection step 0 trace chaque source ».
ERR-295-12 autorise « Injection maintenue, observabilité
dégradée » en cas d'échec d'écriture trace. Ces deux règles
sont incompatibles : soit la trace est obligatoire (et
l'injection doit échouer si trace impossible), soit elle est
best-effort.
Impact : Trou d'auditabilité légitimé par ERR-295-12, en violation de
l'invariant non négociable INV-295-13.
Gravité : Bloquant
Type : Contradiction
Référence : spec INV-295-12 (« cardinalités fixes : learnings=5,
veille=3, clarifications=3 ; si `count=0`, section présente
avec "aucun résultat" »)
Description : « Cardinalités fixes » et « section avec aucun résultat »
sont contradictoires en termes : une section à 0 résultat
n'a pas de cardinalité 5/3/3. La règle voulue (max ou exact ?)
n'est pas explicite.
Impact : Implémentations divergentes possibles : tronquer à
max(top_k, count) vs. exiger exact 5/3/3 et échouer.
Test TC-NOM-13 ne tranche pas (cas mixte avec une seule
source à 0).
Gravité : Majeur
Type : Contradiction
Référence : tests §9 (« Règles non testables ») vs §3 TC-NOM-21/22/23/29/
30/31/32 et §5
Description : §9 du document de tests classe explicitement
CA-295-17/18/19/27 en « non testables a priori ». Pourtant
les tests TC-NOM-21..23, TC-NOM-29..31 et TC-NOM-32 sont
écrits comme des Given/When/Then exécutables sur ces mêmes
critères, avec assertions chiffrées (`>=30%`, `>=1/mois`,
`>=5/5`, `> 0`).
Impact : Statut testable contradictoire dans le document de tests
lui-même ; ambigu sur ce qui sera réellement exécuté en
Gate 8.
Gravité : Majeur
Type : Contradiction
Référence : spec §5.7 N3 étape 8 vs §10.2 et §7 bis
Description : L'étape 8 du flux nominal B3 (« Geler baseline CS-1 via
`scripts/measure-cs1-baseline.py` à la date merge B5 »)
décrit une opération one-shot pré-merge à l'intérieur du
flux périodique de scoring. Soit le gel est répété à chaque
exécution B3 (incohérent avec « gel »), soit il n'a pas sa
place dans ce flux nominal.
Impact : Sémantique du flux N3 incohérente ; un implémenteur peut
soit re-geler à chaque run (perte de la baseline), soit
ignorer l'étape 8.
Gravité : Majeur
Type : Contradiction
Référence : spec INV-295-20 vs §5.1.6 (« Bootstrap initial clé »)
Description : INV-295-20 + ERR-295-16 imposent fail-closed si Vault est
indisponible « au démarrage », mais le bootstrap initial est
une « opération humaine documentée, pas d'auto-bootstrap ».
La séquence est sous-spécifiée : le tout premier appel sans
clé pré-provisionnée déclenche-t-il ERR-295-16 ou un mode
d'exception bootstrap ?
Impact : Premier déploiement potentiellement bloqué sans procédure
contractualisée.
Gravité : Mineur
Type : Contradiction
Référence : spec §3 (`nb_domains` peut valoir `0`) et INV-295-09
Description : `nb_domains` est défini sur la base des events
`learning_injected`. Une promotion `story->domain` exige
`nb_domains >= 1`, donc au moins une injection préalable.
Or `reuse_score_brut = 0.4*nb_inj + 0.4*nb_gate8 +
0.2*nb_dom` : pour atteindre `reuse_score >= 0.30`, il faut
`reuse_score_brut >= ~3.1`, ce qui implique déjà plusieurs
injections (donc nb_domains >= 1 quasi-systématique).
La double garde nb_domains>=1 est redondante en pratique
mais la spec ne le mentionne pas — laissant ouverte la
question d'un cas dégénéré (injections concentrées sur un
seul domaine sans event correspondant suite à filtre).
Impact : Pas une contradiction stricte mais incohérence sémantique
entre §3 (« peut valoir 0 ») et INV-295-09.
Gravité : Mineur
Axe 3 — Règles non testables¶
Type : Non testable
Référence : spec INV-295-22, tests TC-NOM-08
Description : « Top reuse_score » : aucun observable ne permet de vérifier
que les 3 entrées publiées par `/morning` sont bien les 3
meilleures. Le code retour 0 ne discrimine pas un top
correct d'un top arbitraire.
Impact : L'invariant n'est qu'à moitié testable (cardinalité oui,
ordre/sélection non).
Gravité : Majeur
Type : Non testable
Référence : spec §5.12 (« Horloge »), INV-295-18
Description : Aucun seuil chiffré pour la dérive d'horloge déclenchant
`MEMORY_DEGRADED` ; aucun test associé dans le document de
tests.
Impact : Règle non observable.
Gravité : Mineur
Type : Non testable
Référence : spec §5.12 (« Réconciliation »), INV-295-18
Description : « Orphelin > 15 min => MEMORY_DEGRADED » : un « orphelin »
n'est pas défini formellement (ligne JSONL sans entrée FAISS ?
entrée FAISS sans JSONL ? trace sans ligne source ?). Sans
définition opérationnelle, le test TC-NOM-20 n'est pas
reproductible.
Impact : Test exécutable mais sémantique non vérifiable.
Gravité : Majeur
Type : Non testable
Référence : tests §9, CA-295-17/18/19/27
Description : Critères horizontaux T+90j, mensuel, 5 premières stories
réelles : non testables avant exécution réelle. Pas un
écart en soi (déjà déclaré par les auteurs) mais incohérent
avec la présence simultanée de scénarios TC-NOM-21..23,
29..32 prétendant les valider.
Impact : Voir Axe 2 (contradiction interne).
Gravité : Mineur
Type : Non testable
Référence : spec INV-295-20, §5.1.6 (« payload_canonique »)
Description : Sans définition de la sérialisation canonique, la
vérification HMAC n'est pas reproductible et donc pas
testable de manière déterministe entre deux outils.
Impact : TC-NOM-26 et TC-NOM-26-bis peuvent passer dans une
implémentation et échouer dans une autre conforme à la
lettre.
Gravité : Bloquant
Axe 4 — Incohérences Spec ↔ Tests¶
Type : Incohérence Spec↔Tests
Référence : spec §8 (« ST-295-01..24 ») vs tests §3 (TC-NOM-XX)
Description : La spec liste des scénarios de test sous la nomenclature
`ST-295-01..24` mais le document de tests n'utilise que
`TC-NOM-XX`, `TC-ERR-XX`, `TC-NEG-XX`, `TC-NR-XX`. Aucun
mapping ST<->TC n'est fourni. La traçabilité spec→tests
passe par la matrice §2 du doc tests (par INV/CA), pas par
les ST.
Impact : Référentiel scénarios spec orphelin ; risque d'oubli si la
gate audite via les ST.
Gravité : Majeur
Type : Incohérence Spec↔Tests
Référence : spec INV-295-23 vs test TC-NEG-03
Description : INV-295-23 contractualise `count == len(result_ids)`. Le
test TC-NEG-03 ne couvre que la branche `count > 0` avec
`result_ids` vide. La branche inverse (`count == 0` mais
`result_ids` non vide) n'est pas testée.
Impact : Couverture asymétrique d'une règle bidirectionnelle.
Gravité : Mineur
Type : Incohérence Spec↔Tests
Référence : spec §5.4 (« ARCHIVED -> STORY_ACTIVE manuel »), §10.2
Q-295-03, tests
Description : Q-295-03 (« démotion manuelle global/domain -> story »)
reste explicitement ouverte dans la spec. Aucun test ne
couvre ce cas, et INV-295-11 (« toute transition non listée
§5.4 est interdite ») rendrait toute démotion impossible
sans amendement. La spec laisse ouvert un point qui est
déjà fermé par invariant.
Impact : Incohérence interne spec ; pas un écart de couverture mais
un cas bord ignoré.
Gravité : Mineur
Type : Incohérence Spec↔Tests
Référence : spec §5.6 N2 étape 7 (purge multi-déclencheurs), tests
TC-NOM-27, TC-NOM-27-bis
Description : Trois déclencheurs concurrents (step 10, hook
`on_story_close`, cron quotidien) peuvent s'exécuter en
parallèle sur `data/clarifications.jsonl` et les fichiers
`PD-XX-clarifications.md`. Aucun lock dédié n'est défini
pour la purge ; aucun test ne couvre la concurrence purge ↔
purge ou purge ↔ injection lecture.
Impact : Race condition possible (double suppression, lecture sur
fichier en cours de purge), non couverte.
Gravité : Majeur
Type : Incohérence Spec↔Tests
Référence : spec §5.9 N5 étape 5, INV-295-18, test TC-NOM-17
Description : B5 acquiert SH sur les deux locks puis lance `search-*` en
parallèle. Si `search-learnings` réacquiert SH (ce qui est
attendu pour un usage isolé du script), `fcntl.flock` n'est
pas réentrant de manière portable (POSIX/Linux) — le
comportement dépend du même process/thread. Aucune
hypothèse explicite, aucun test sur la réentrance.
Impact : Hypothèse implicite non vérifiée ; risque de blocage ou de
dégradation silencieuse selon implémentation.
Gravité : Majeur
Type : Incohérence Spec↔Tests
Référence : spec §5.7 N3 étape 8 (gel baseline CS-1), tests TC-NOM-24
Description : L'étape 8 place le gel baseline dans le flux nominal de
scoring (B3 périodique). TC-NOM-24 traite la baseline comme
un one-shot (date merge B5). Aucun test ne valide ni ne
réfute la présence de l'étape 8 dans le flux périodique.
Impact : Spec et tests divergent sur la nature one-shot vs récurrente
du gel baseline.
Gravité : Mineur
Axe 5 — Hypothèses dangereuses¶
Type : Hypothèse dangereuse
Référence : spec INV-295-18, §5.12
Description : `fcntl.flock` est posé comme primitive de verrouillage sans
hypothèse explicite sur le type de filesystem (NFS, SMB, FS
local). Sur NFS antérieur à v4 et certains FS distants,
`flock` a un comportement indéfini ou silencieusement
dégradé.
Impact : Garanties de concurrence (CA-295-16, INV-295-18) non
tenues sur déploiements partagés.
Gravité : Majeur
Type : Hypothèse dangereuse
Référence : spec §5.7 N3, §5.8 N4, §5.9 N5
Description : Les écritures sur `learnings-scores.jsonl`, `learnings.jsonl`
et `learnings-injections.jsonl` sont protégées par lock mais
aucune règle d'atomicité (write-temp + rename, fsync) n'est
contractualisée. Un crash en cours d'écriture peut produire
une ligne JSONL tronquée que le validateur §5.1 traitera
comme « ligne exclue + log », masquant silencieusement la
perte.
Impact : Perte de score / promotion / trace non détectable. Audit
probatoire compromis.
Gravité : Majeur
Type : Hypothèse dangereuse
Référence : spec §5.1.6 (`timestamp_bucket` arrondi 5 min UTC,
idempotence)
Description : L'idempotence repose sur l'horloge UTC locale du process.
§5.12 prévoit `MEMORY_DEGRADED` en cas de dérive, mais
`timestamp_bucket` est calculé indépendamment de cet état :
une dérive de quelques secondes autour d'une frontière de
bucket place deux requêtes identiques dans deux buckets
distincts, contournant l'idempotence sans alerte.
Impact : Idempotence non garantie aux frontières de bucket.
Gravité : Mineur
Type : Hypothèse dangereuse
Référence : spec INV-295-05 (« verbatim strict »), INV-295-21 (« pas
d'anonymisation »)
Description : Hypothèse implicite : les réponses PO Q1..Q4 ne contiennent
pas de PII ni de secrets. Aucune barrière n'empêche
l'écriture d'un email, d'un nom client, d'un token, qui
seront ensuite indexés sémantiquement et conservés 18 mois.
Impact : Risque RGPD et fuite secret via index FAISS. Voir Axe 6.
Gravité : Majeur
Type : Hypothèse dangereuse
Référence : spec §5.8 N4 étape 1, INV-295-16
Description : « Détecter si migration scope est nécessaire » par scan de
`learnings.jsonl` à chaque exécution B4, sans marqueur
persistant. Si une nouvelle ligne sans `scope` est insérée
hors workflow (script ad-hoc, restore manuel), elle
re-déclenche la précondition « commit préalable » à un
moment imprévu.
Impact : ERR-295-08 imprévisible ; B4 peut bloquer en production
suite à une opération externe.
Gravité : Mineur
Type : Hypothèse dangereuse
Référence : spec §5.1.6 (rotation HMAC)
Description : Aucune périodicité maximale de rotation de clé n'est
contractualisée. La clé peut rester active indéfiniment.
Aucun mécanisme de révocation rapide en cas de compromis.
Impact : Posture cryptographique faible vs. exigence probatoire.
Gravité : Mineur
Type : Hypothèse dangereuse
Référence : spec §5.6 N2 étape 7, INV-295-21
Description : Hypothèse implicite : la suppression du fichier
`PD-XX-clarifications.md` purge effectivement les données.
Or l'index FAISS clarifications, les embeddings NPY, et les
traces `learnings-injections.jsonl` (qui contiennent
`query` jusqu'à 500 chars potentiellement copiés depuis la
clarification) ne sont pas listés comme cibles de purge.
Impact : Rétention résiduelle au-delà des 18 mois ; non-conformité
à INV-295-21.
Gravité : Majeur
Axe 5bis — Cohérence des diagrammes¶
Type : Incohérence Spec↔Tests (diagramme)
Référence : spec §5bis (diagramme d'état) vs §5.4
Description : Les transitions interdites (`STORY->GLOBAL`,
`DOMAIN->STORY`, `DOMAIN->ARCHIVED`, `GLOBAL->DOMAIN`,
`GLOBAL->STORY`, `GLOBAL->ARCHIVED`) sont listées en note
latérale mais ne sont pas matérialisées graphiquement
(arcs barrés, transitions explicites avec garde
`[interdit]`). Un lecteur du seul diagramme verra une FSM
compatible avec ces transitions.
Impact : INV-295-11 (« toute transition non listée §5.4 est
interdite ») n'est lisible que dans le texte ; le
diagramme ne contractualise pas l'interdiction.
Gravité : Mineur
Type : Incohérence Spec↔Tests (diagramme)
Référence : spec §5bis (diagramme de séquence)
Description : Le diagramme de séquence couvre uniquement le flux N5
(B5 step 0). Les flux N3 (scoring, double lock SH+EX) et
N4 (promotion/éviction, double lock EX+EX, écriture
archive, événement signé) — qui portent les invariants
les plus exposés à la concurrence (INV-295-18 / INV-295-20)
— n'ont pas de diagramme. Cf. axe 5bis du prompt :
« si le flux implique >= 2 services/opérations crypto,
signaler l'absence comme écart Mineur ».
Impact : Lecture incomplète des flux critiques.
Gravité : Mineur
Type : Incohérence Spec↔Tests (diagramme)
Référence : spec §5bis (diagramme de séquence) vs §5.1.6
Description : Le diagramme indique « append events signés + key_version »
sans préciser le contenu canonique signé ni l'algorithme
(HMAC-SHA256). Le format de payload n'est pas représenté
entre `SRCH` et `TRACE`. Cf. axe 5bis : « signaler les
appels sans format d'entrée/sortie spécifié ».
Impact : Cohérent avec l'ambiguïté `payload_canonique` (axe 1).
Gravité : Mineur
Axe 6 — Risques sécurité / conformité¶
Type : Risque sécu/conformité
Référence : spec INV-295-05, INV-295-21, §5.6 N2
Description : Verbatim strict des réponses PO + rétention 18 mois sans
anonymisation + indexation sémantique : si Q1..Q4
contiennent des données personnelles ou des secrets
(token, identifiant client, donnée santé), ces données
seront persistées dans `clarifications.jsonl`,
`clarifications.md`, l'index FAISS, et potentiellement
copiées dans `learnings-injections.jsonl` via le champ
`query`. Aucun filtre, aucun avertissement PO, aucune
cible de purge multi-artefacts.
Impact : Non-conformité RGPD potentielle ; fuite de secret possible
via recherche sémantique. Trou d'auditabilité du droit à
l'effacement.
Gravité : Bloquant
Type : Risque sécu/conformité
Référence : spec ERR-295-12
Description : « Échec d'écriture trace => injection maintenue,
observabilité dégradée » crée légitimement un trou d'audit.
Un attaquant capable d'induire un échec d'écriture ciblé
(filesystem plein, permissions) peut faire passer des
injections sans laisser de trace, tout en respectant les
autres invariants.
Impact : Contournement de l'auditabilité contractuelle (INV-295-13).
Gravité : Bloquant
Type : Risque sécu/conformité
Référence : spec §5.1.6 (rotation HMAC manuelle)
Description : Rotation 100% manuelle, sans périodicité maximale, sans
alerte d'âge de clé, sans procédure de révocation
d'urgence. La clé est lue par chaque exécuteur autorisé
(H-295-07) — surface d'exposition non bornée.
Impact : Compromission de clé non détectable ; révocation ad-hoc.
Gravité : Majeur
Type : Risque sécu/conformité
Référence : spec INV-295-20, §5.1.6
Description : L'audit repose sur HMAC symétrique. Tout détenteur de la
clé Vault peut forger un événement signé indistinguable
d'un événement légitime. Aucune séparation
signataire/vérificateur, aucune chaîne de hachage entre
événements (pas de log append-only cryptographique).
Impact : Audit « probatoire » au sens contractuel mais non
opposable à un détenteur de clé. À calibrer vs. exigence
probatoire annoncée.
Gravité : Majeur
Type : Risque sécu/conformité
Référence : spec §5.12 (rate-limit), ERR-295-11
Description : Rate-limit `3 req / 5 min glissantes / story`. Un appelant
interne peut contourner le quota en variant `story_id`
dans la clé idempotence sans aucun coût. Aucun quota
global ni par utilisateur défini.
Impact : Protection anti-abus inefficace si la story devient un
paramètre contrôlable.
Gravité : Mineur
Type : Risque sécu/conformité
Référence : spec §5.1.2 (`source_path` veille)
Description : `source_path` est validé « chemin relatif veille `.md`,
sans traversal ». Le test TC-NEG-01 couvre `../../x.md`,
mais aucun observable n'est défini pour les chemins
encodés (`%2e%2e`), les liens symboliques, ou les chemins
absolus déguisés. La règle de validation n'est pas
spécifiée (regex, normalisation).
Impact : Risque de path traversal résiduel selon implémentation.
Gravité : Mineur
Type : Risque sécu/conformité
Référence : spec §5.1.6 (`query` dans trace)
Description : Le champ `query` (jusqu'à 500 chars) est persisté en clair
dans `learnings-injections.jsonl` avec rétention non
bornée (aucun invariant de purge). Si la query contient
des fragments de clarification PO sensible, ces fragments
survivent à toute purge clarifications.
Impact : Rétention dérivée non contrôlée ; cf. risque RGPD ci-dessus.
Gravité : Majeur
Synthèse des écarts¶
| Gravité | Nombre |
|---|---|
| Bloquant | 5 |
| Majeur | 17 |
| Mineur | 17 |
| Total | 39 |
Écarts bloquants (rappel) :
payload_canoniquenon défini (axe 1)- Contradiction INV-295-13 ↔ ERR-295-12 sur l'auditabilité (axe 2)
- Vérification HMAC non reproductible (axe 3)
- Verbatim PO sans contrôle PII / RGPD (axe 6)
- Trou d'audit légitimé par ERR-295-12 (axe 6)
Aucune correction proposée. Aucune reformulation. Aucune implémentation.