Aller au contenu

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) :

  1. payload_canonique non défini (axe 1)
  2. Contradiction INV-295-13 ↔ ERR-295-12 sur l'auditabilité (axe 2)
  3. Vérification HMAC non reproductible (axe 3)
  4. Verbatim PO sans contrôle PII / RGPD (axe 6)
  5. Trou d'audit légitimé par ERR-295-12 (axe 6)

Aucune correction proposée. Aucune reformulation. Aucune implémentation.