PD-295 — Bibliothèque documentaire → mémoire vivante (5 briques incrémentales, besoin v2)¶
Version 2 — 2026-04-11. Refonte du besoin après une première passe de spécification (3 itérations Gate 3 NON_CONFORME, archivée dans
cycle-1/). Les 3 itérations précédentes ont révélé des zones structurelles sous-explorées (RGPD, chaîne d'audit, canonicalisation HMAC, fail-closed des traces) qui sont maintenant tranchées ici, dans le besoin, avant de relancer le cycle spec. Les écarts v1/v2/v3 capitalisés figurent en §14.
1. Contexte¶
Le système ProbatioVault produit de grandes quantités de connaissances au fil du workflow de gouvernance : besoins, spécifications, REX, fiches de veille, clarifications PO, verdicts de gate. Aujourd'hui ces artefacts sont stockés comme une bibliothèque plate indexée par FAISS : chaque document est rangé quelque part, accessible via les 4 skills de recherche (/learnings, /specs, /plans, /contracts), mais la bibliothèque ne vient jamais au-devant d'une nouvelle story. Toute réutilisation de connaissances passée dépend d'un humain ou d'un agent qui va explicitement fouiller.
Deux sources de valeur se perdent totalement :
- Les conversations de clarification PO au step 0 — utilisées sur le moment puis jetées. En mode Ringbearer (L3), elles transitent par
gov_ask_po→ broker → Signal → humain → broker → agent, sans persistance intermédiaire. - Les 128 fiches de veille produites dans
ProbatioVault-doc/docs/veille/2026-*/*.md— lisibles uniquement manuellement, jamais injectées automatiquement au démarrage d'une story qui en bénéficierait.
Par ailleurs, les learnings existants sont traités uniformément : une leçon qui a été réutilisée 10 fois pèse autant qu'une leçon jamais ressortie, une leçon de février 2026 pèse autant qu'une leçon d'hier.
Sources : confrontation de 4 analyses ChatGPT sur l'architecture RAG face à la stack réelle ProbatioVault + inspiration directe de 3 projets mémoire agent publiés entre le 2026-04-08 et le 2026-04-10 (MemPalace, Moerae, GBrain) + validation par le pattern Karpathy LLM Wiki (2026-04-08) + retours d'expérience des 3 itérations Gate 3 du cycle 1 (archivé) qui ont révélé les problèmes structurels résolus en §3.
2. Besoin¶
En tant que PO/architecte ProbatioVault, je veux que la bibliothèque documentaire existante se transforme en mémoire vivante qui s'injecte automatiquement au démarrage de chaque story, afin de capitaliser sur les réutilisations mesurées, archiver proprement ce qui est obsolète, et éliminer la dépendance à une recherche manuelle explicite.
3. Arbitrages structurels tranchés (issus des 3 itérations du cycle 1)¶
Ces arbitrages sont NON NÉGOCIABLES pour le cycle 2 de spécification. Ils ferment des zones d'ombre qui avaient causé 5 bloquants cumulés sur 3 itérations Gate 3.
3.1 RGPD — clarifications PO stockées en résumé structuré non-PII, pas en verbatim¶
Décision : les clarifications PO ne sont jamais stockées en verbatim. Le step 0 ingère les 4 réponses PO, puis un sous-step dédié produit un résumé structuré par catégorie (problème, critères, contraintes, priorités) qui décrit les décisions sans retenir les détails identifiants.
Règles :
- Le résumé est produit par Claude avec une température basse et un prompt contraint : "extraire l'intention décisionnelle, éliminer noms propres, emails, téléphones, adresses, IBAN, identifiants externes".
- Le résumé est validé par le PO (confirmation binaire) avant écriture.
- Le verbatim transite uniquement en mémoire du processus step 0 et n'est jamais persisté sur disque.
- Le résumé validé est stocké dans
PD-XX-clarifications.mdet indexé. - Les clarifications rejetées au moment de la validation PO sont jetées (pas de trace).
- Rétention : 18 mois glissants, purge quotidienne par
scripts/purge-clarifications.pyappelé par cron et pargov-compounderen step 10 (défense en profondeur). - La purge couvre tous les artefacts dérivés :
data/clarifications.jsonl,data/clarifications-index.faiss,data/clarifications-embeddings.npy,data/clarifications-cache.json, et les tracesquerydansdata/sessions/*.jsonlcontenant lestory_idpurgé.
Droit à l'oubli (RGPD art. 17) : exécutable via scripts/purge-clarifications.py --story PD-XX --force. Le script vérifie la suppression effective dans les 5 artefacts ci-dessus et émet un événement clarification_purged tracé.
Justification : le résumé structuré supprime le risque PII à la source, simplifie la purge multi-artefacts, et conserve ~80 % de la valeur de réutilisation (l'intention décisionnelle) au lieu de 100 % (verbatim + PII). Le choix est motivé par la simplicité d'être bulletproof par construction, plutôt que complexe et fragile.
3.2 Chaîne d'audit — signature HMAC canonique via clé Vault, fail-closed strict¶
Décision : tous les événements d'observabilité B1..B5 sont signés HMAC-SHA256 avec une clé statique chargée depuis HashiCorp Vault, et le payload signé est canonicalisé selon JCS (JSON Canonicalization Scheme, RFC 8785).
Règles :
- Clé HMAC : générée manuellement via
openssl rand -hex 32, stockée danskv/pd-295/memory-audit-hmacau format hex-64 caractères. Décodage en bytes bruts (32 octets) à la lecture. - Rotation : manuelle via
scripts/rotate-audit-hmac.shqui génère une nouvelle clé, archive l'ancienne danskv/pd-295/memory-audit-hmac/archive/{version}, et émet un événementaudit_key_rotatedsigné avec la nouvelle clé. - Vérification a posteriori : chaque événement signé porte un
key_version(id de version Vault). Le vérificateur charge la clé correspondante depuis Vault (active ou archivée). - Payload canonique : sérialisation JCS RFC 8785 — clés triées lexicographiquement, sans espaces, encodage UTF-8, nombres en notation minimale. Le champ
signature_hmac_sha256est exclu du payload avant signature (signature d'un objet qui se contient lui-même est impossible). - Vecteurs de test pré-calculés : la spec fournit 3 triplets
(payload, key, expected_signature)en annexe pour rendre TC-NOM-26 déterministe. - Si Vault est indisponible au démarrage → fail-closed, aucun événement audit émis, erreur
ERR-295-AUDIT_KEY_UNAVAILABLElevée, le skill/govs'arrête.
Justification : JCS RFC 8785 est le standard IETF pour la canonicalisation JSON déterministe, utilisé par Google, Mozilla, les Verifiable Credentials W3C. Vecteurs de test pré-calculés = reproductibilité indépendante de l'implémentation. Clé Vault statique = vraie propriété de secret (vs file_mtime_ns rejeté en cycle 1).
3.3 Fail-closed strict sur les traces d'injection¶
Décision : aucune injection de mémoire (B5) ne peut se produire sans qu'une trace signée soit écrite avec succès dans data/sessions/*.jsonl. Il n'y a pas de mode dégradé qui autoriserait une injection sans trace.
Règles :
- B5 tente d'écrire la trace avant de retourner le bloc d'injection au caller.
- Si l'écriture de trace échoue (disque plein, permissions, lock distribué timeout) → B5 lève
ERR-295-TRACE_WRITE_FAILEDet retourne un bloc d'injection vide. Le step 0 consomme le bloc vide (injection = "aucune mémoire disponible pour cette story") et continue normalement. - Le caller (step 0 de
/gov) enregistre un événementinjection_failed_fail_closeddans le session log courant (non bloquant pour le step 0). - Cette règle ferme le trou d'audit exploitable identifié en Gate 3 v3 où un attaquant aurait pu induire un échec d'écriture pour neutraliser l'audit.
Justification : Art. IV CONSTITUTIONAL (non-régression) impose fail-closed. Art. I (auditabilité) impose que la trace soit une condition nécessaire à l'effet. Ces deux articles sont incompatibles avec un "mode dégradé trace" comme celui proposé en cycle 1.
3.4 reuse_score normalisé via tanh + borne stricte¶
Décision : reuse_score = tanh(reuse_score_brut / 10) avec reuse_score_brut = nb_injections*0.4 + nb_stories_gate8_go*0.4 + nb_domains*0.2 où nb_domains = len(set(domains)) (sans plancher max(1, ...)). Le score est dans [0, 1) — borne supérieure ouverte car tanh n'atteint jamais 1.0, mais la sérialisation JSON à 6 décimales atteint 1.000000 pour des entrées ≥ ~35. Pour éviter l'ambiguïté, la spec contractualise que la sérialisation est à 4 décimales (pas 6) et que la valeur maximum effective est 0.9999.
Justification : 4 décimales suffisent largement pour le tri secondaire. La borne ouverte est préservée. Le problème d'arrondi à 1.000000 vu en cycle 1 est éliminé.
3.5 Scope B5 — cardinalité des 3 sections¶
Décision : B5 injecte exactement 5 learnings + 3 fiches de veille + 3 clarifications (résumées). Les 3 cardinalités sont fixées dans le besoin, la spec les reprend en invariant dur (INV-295-12), et les tests les vérifient par comptage.
3.6 Politique de lock — rwlock sur tous les flux qui touchent learnings.jsonl¶
Décision : lock partagé lecteurs/écrivains (fcntl.flock advisory sur data/learnings.jsonl.lock, timeout 30s, fail-closed). Couvre B3 (scoring → write-lock sur learnings-scores.jsonl, read-lock sur learnings.jsonl), B4 (promotion/éviction → write-lock sur les deux), B5 (injection → read-lock sur les deux). Reprise crash : suppression automatique du lockfile si mtime > 60s.
3.7 Distinction CA "Gate 8" vs CA "mesure continue post-merge"¶
Décision : les 4 indicateurs mesurables (CS-1 à CS-4) ne sont pas vérifiables au moment du Gate 8 (ils nécessitent 30 à 90 jours de production). Ils ne peuvent donc pas être des CA de clôture de Gate 8. Ils deviennent des CA de mesure continue post-merge, formalisés en §7 bis de la spec, avec leurs scripts dédiés (scripts/measure-cs1.sh à scripts/measure-cs4.sh) et leurs fenêtres de mesure (T+30j, T+60j, T+90j).
Le Gate 8 vérifie uniquement que les scripts de mesure existent et sont exécutables (mesure "méta"), pas leurs valeurs. Un CA supplémentaire (CA-295-meta) contractualise cela.
3.8 Purge des clarifications robuste à l'échec de cycle¶
Décision : la purge scripts/purge-clarifications.py est appelée depuis trois points :
gov-compounderen step 10 (story DONE).- Hook
on_story_closequand une story passe en REJECTED ou DONE_WITH_ANOMALY. - Cron quotidien (
crontab+launchd.plistselon OS) de sécurité défense en profondeur.
3.10 Runtime isolation — subprocess et fichiers d'état non signés (ajouté cycle 2 v1)¶
Contexte : la Gate 3 du cycle 2 v1 a révélé deux classes d'attaques que le cycle 1 n'avait pas vues parce qu'elles touchent l'exécution du workflow, pas son modèle de données. Ces deux problèmes sont capitalisés ici dans le besoin pour cascader proprement vers la spec cycle 2 v2, suivant la même logique que les arbitrages §3.1 à §3.8 issus du cycle 1.
3.10.1 Isolation des subprocess qui consomment du verbatim PO¶
Décision : les subprocess qui reçoivent en entrée le verbatim des 4 réponses PO (y compris pour produire le résumé structuré §3.1) opèrent en isolation totale sur les canaux de persistance.
Règles contractuelles :
- Redirection stdout/stderr : les subprocess qui manipulent du verbatim redirigent leurs sorties standard vers un buffer en mémoire uniquement (jamais vers un fichier, jamais vers
terminal.log). Le résumé structuré est récupéré via ce buffer, puis le verbatim est écrasé en mémoire avant retour de la fonction. - Désactivation des traces subprocess : les appels
claude -pou équivalents utilisés par B2 passent la variable d'environnementCLAUDE_DISABLE_SESSION_LOG=1(à créer dans le wrapper) et désactivent l'écriture dansdata/sessions/*.jsonlpour le subprocess concerné. Aucun champ du verbatim ne doit apparaître dans un session log. - Pas d'argument CLI verbatim : le verbatim est toujours passé via
stdin, jamais via arguments de ligne de commande (arguments persistés dansps,/proc, shell history, cmux logs). - Pas d'écriture de fichier temporaire : le wrapper subprocess ne crée aucun fichier temporaire contenant du verbatim. Si un fichier temporaire est nécessaire techniquement, il est en
tmpfs(RAM) et supprimé avant retour. - Variable d'environnement d'audit : un événement signé
verbatim_subprocess_invokedest émis à chaque appel (sans le verbatim lui-même) avec{story_id, subprocess_name, duration_ms, success}pour tracer les invocations sans les compromettre.
Justification : le §3.1 du besoin v2 disait "le verbatim n'est jamais persisté sur disque", mais ne contractualisait pas les canaux dérobés (logs subprocess, session logs, arguments CLI, fichiers temporaires). Le §3.10.1 ferme ces canaux explicitement.
3.10.2 Signature HMAC des fichiers d'état au moment de l'écriture¶
Décision : tous les fichiers d'état lus par le workflow pour prendre des décisions de scoring, promotion, ou injection sont signés ligne par ligne au moment de l'écriture, et filtrés à la lecture.
Fichiers concernés :
data/learnings-injections.jsonl(prompt injection viareuse_scoregonflé)data/learnings-scores.jsonl(idem, cible directe du scoring B3)data/sessions/*.jsonl(audit trail, cible de falsification)
Règles contractuelles :
- Signature à l'écriture : chaque ligne JSON écrite porte un champ
sig_hmac_sha256calculé sur le payload canonique JCS de la ligne (sans le champsiglui-même), avec la clé Vaultkv/pd-295/memory-audit-hmac(déjà spécifiée §3.2). - Filtrage à la lecture :
scripts/compute-learning-scores.py,scripts/promote-learnings.py, etgov-learnings-injectignorent silencieusement toute ligne dont la signature ne vérifie pas. Un compteurERR-295-UNSIGNED_ENTRYest incrémenté et émis en événement signé. Les lignes sans signature du tout sont également ignorées. - Migration existant : les entrées pré-B3 déjà présentes dans
data/learnings-injections.jsonl(commit 076dc3e) sont re-signées au moment du merge B3 viascripts/sign-existing-state.pyqui lit, signe, et réécrit les fichiers (commit git atomique préalable). - Protection en écriture : les scripts qui écrivent dans ces fichiers hors workflow interne (administration manuelle, debug) doivent passer par
scripts/write-signed-state.py <file> <payload>qui signe avant d'ajouter. Unecho ... >> filedirect produit une ligne non signée qui sera filtrée à la lecture (fail-closed par construction). - Rotation de clé : si la clé Vault est rotée (
scripts/rotate-audit-hmac.sh), les lignes existantes sont re-signées avec la nouvelle clé. L'archive de l'ancienne clé (§3.2) reste disponible pour vérifier les lignes historiques hors workflow courant.
Justification : sans signature des fichiers d'état, un attaquant qui a un accès filesystem (utilisateur compromis, contenu malicieux d'un repo dépendance, processus tierce tournant sur la même machine) peut empoisonner le contexte step 0 de toutes les futures stories en gonflant artificiellement le reuse_score d'un learning malveillant ou en réécrivant les sessions. La signature HMAC + filtrage fail-closed rend l'attaque détectable et inefficace.
Limites explicites : la non-répudiation est faible (HMAC symétrique, un administrateur avec accès Vault peut forger une signature valide). Une non-répudiation forte exigerait une signature asymétrique (EdDSA) avec clé privée HSM. C'est hors scope de PD-295 et reporté comme candidat G33 « non-répudiation forte de la chaîne d'audit mémoire ».
3.12 Arbitrages cycle 3 — compounding des 6 itérations (ajouté cycle 3)¶
Contexte : le cycle 2 a atteint le plafond Art. I en 3 itérations sans converger au score arithmétique (meilleur point c2v2 à 6.375/10). La convergence était structurelle (0 bloquants réels, traceability 8.5) mais freinée par clarity (4.25) dû à 5 clarifications locales qui n'avaient pas été tranchées au niveau besoin. Ce §3.12 tranche ces points au niveau besoin pour le cycle 3, suivant la même logique que §3.10 avait tranché les problèmes runtime du cycle 2 v1 pour arrêter la boucle plateau.
Principe : les clarifications ne sont pas des redesigns, ce sont des précisions non négociables que la spec doit refléter littéralement.
3.12.1 Tmpfs inexistant sur macOS — buffer mémoire Python exclusif¶
Décision : aucun fichier disque n'est autorisé pour transporter du verbatim PO, quel qu'il soit (tmpfs, /tmp, /private/var/run, etc.). Le verbatim transite exclusivement via buffers Python en mémoire (io.BytesIO, io.StringIO) ou via file descriptors os.pipe() passés à subprocess en communication inter-processus. INV-295-RUNTIME-02 doit contractualiser cette règle sans mention de tmpfs, parce que macOS n'offre pas de tmpfs natif accessible à une application.
3.12.2 Architecture subprocess B2 sanitizer — wrapper Python + sous-subprocess claude -p¶
Décision : l'architecture de capture-résumé des clarifications PO est un schéma à deux niveaux :
- Process parent :
.claude/commands/gov-step-0.mdcollecte le verbatim en mémoire (io.StringIO). - Subprocess wrapper :
scripts/b2-sanitizer.py(wrapper Python court). Lit le verbatim via stdin (pipe OS depuis le parent), appelleclaude -pen sous-subprocess avec les contraintes §3.10.1 (stdin=verbatim, stdout=pipe retour, env={CLAUDE_DISABLE_SESSION_LOG=1,TERM=dumb}), récupère le résumé structuré, le retourne sur stdout. Code retour = 0 si succès. - Retour au parent : le résumé est lu via pipe, le buffer verbatim est écrasé en mémoire,
PD-XX-clarifications.mdest écrit avec le résumé uniquement.
Justification : le wrapper Python ajoute une isolation procès intermédiaire qui garantit que même si claude -p décide de persister quelque chose localement, le wrapper contrôle ses file descriptors et peut les isoler. Un appel direct claude -p depuis le parent sans wrapper fragiliserait cette isolation.
3.12.3 Hook on_story_close ancré dans le workflow¶
Décision : le hook on_story_close(story_id, final_state) est partie intégrante du workflow /gov. Il est implémenté dans scripts/lib/story-hooks.sh et appelé depuis .claude/commands/gov.md à chaque transition Jira finale (états : REJECTED, DONE_WITH_ANOMALY, DONE). Action obligatoire : appeler scripts/purge-clarifications.py --story {story_id} pour garantir la purge même si la story ne passe pas par step 10. Idempotent (plusieurs appels → effet identique). La spec doit ancrer ce hook en §Hooks de cycle de vie, pas seulement le tester.
3.12.4 Scope du compteur fail_closed_depth — thread-local par invocation step 0¶
Décision : fail_closed_depth est thread-local, initialisé à 0 au début de step 0, incrémenté à chaque fail-closed imbriqué dans le thread courant, réinitialisé à 0 à la fin de step 0 (succès ou échec). Aucune persistance disque, aucun partage inter-invocations. Implémentation de référence : threading.local() en Python. Valeur max autorisée = 3, tentative de 4e fail-closed imbriqué → abort processus avec ERR-295-FAIL_CLOSED_RECURSION.
3.12.5 Chargement de la clé Vault — une seule fois par session¶
Décision : la clé Vault kv/pd-295/memory-audit-hmac est chargée une seule fois au démarrage de /gov (ou de n'importe quel subprocess du workflow). Si Vault est indisponible au chargement → ERR-295-AUDIT_KEY_UNAVAILABLE → arrêt /gov complet (pas de fallback, cascade fail-closed §3.3). Si Vault redevient disponible en cours de session → ignoré (la session courante garde sa clé chargée). Re-évaluation uniquement au prochain démarrage de session. Justification : garantir qu'une même session n'émet jamais d'événements avec des clés différentes (sinon la vérification HMAC a posteriori exigerait de connaître l'ordre temporel précis, ce qui casse l'auditabilité en rejeu).
3.12.6 Vecteurs HMAC v3 pré-calculés — schéma générique complet¶
Décision : les 4 vecteurs HMAC de référence (V1 nominal, V4 sous-corpus, V6 rotation clé, V8 purge droit à l'oubli) sont pré-calculés dans la spec en §vecteurs, avec le triplet complet {count_configured, count_effective, under_corpus} et le schéma générique v3 complet (schema_version, timestamp_iso8601, story_id, event_type, brick, key_version, payload_canonique). La signature HMAC SHA-256 est donnée en hexadécimal, reproductible en offline par un lecteur de la spec qui dispose uniquement de la clé de test (annexée en exemple, distincte de la clé de production).
3.12.7 Trou de purge RGPD — PD-XX-clarifications.md inclus¶
Décision : l'énumération des artefacts à purger lors du droit à l'oubli (scripts/purge-clarifications.py --story PD-XX --force) inclut obligatoirement PD-XX-clarifications.md dans docs/epics/**/PD-XX-*/. C'était l'oubli SR-04 détecté en cycle 2 v3 review. Liste complète (6 artefacts, pas 5) : data/clarifications.jsonl, data/clarifications-index.faiss, data/clarifications-embeddings.npy, data/clarifications-cache.json, data/sessions/*.jsonl (filtrage par story_id), et docs/epics/**/PD-XX-clarifications.md.
3.12.8 Résolution des 2 contradictions internes cycle 2 v3¶
Décision 1 — Lock sur learnings.jsonl : le lock est rwlock (fcntl.flock advisory, LOCK_SH pour lecture, LOCK_EX pour écriture). Pas de contradiction entre §5.12 et §5.13 — la spec cycle 3 doit utiliser le même terme rwlock dans les deux sections.
Décision 2 — Clé Vault immuable en session vs clé indisponible : pas de contradiction. Au démarrage de session : si clé indisponible → arrêt immédiat (avant toute opération). Pendant la session : la clé est immuable (§3.12.5). Donc un cas "clé devenue indisponible au milieu de la session" n'existe pas — si le process parent peut la lire au démarrage, il la conserve en mémoire jusqu'à la fin. La spec cycle 3 doit retirer toute mention d'un scénario "clé devient indisponible pendant la session".
3.12.9 Contrainte de volume sur la spec — ≤ 550 lignes¶
Décision : la spécification cycle 3 v1 ne doit pas dépasser 550 lignes de markdown (c2v2 = 527, c2v3 = 603 qui a causé la régression arithmétique). Les clarifications §3.12.1-§3.12.8 sont intégrées inline dans les invariants existants INV-295-RUNTIME-, INV-295-STATE-, §5.12 lock, §vecteurs HMAC. Interdit : créer de nouvelles sections §5.2.4, §5.3.3, §5.6.10, §5.8, §5.9 comme l'a fait c2v3. Ces clarifications tiennent chacune en 3-5 lignes inline.
Justification : le cycle 2 v3 a démontré qu'ajouter de nouvelles sections crée de nouveaux points d'entrée pour la review adversariale, ce qui fait monter le nombre de majeurs même si la qualité réelle augmente. La discipline de volume est une mitigation contre ce biais du scoring arithmétique sur le volume.
3.11 Bornes non chiffrées du cycle 1 — fixées dans le besoin¶
Pour éviter les écarts "bornes numériques manquantes" du cycle 1 :
| Paramètre | Valeur |
|---|---|
top_k_learnings_step0 | 5 |
top_k_veille_step0 | 3 |
top_k_clarifications_step0 | 3 |
query.length_max (chars) | 500 |
min_score_default (filtre) | 0.0 (désactivé par défaut) |
retention_clarifications | 18 mois (540 jours) |
eviction_learnings_stale | 8 semaines sans injection (56 jours) |
rate_limit_window_sliding | 5 minutes glissantes |
rate_limit_max_requests | 3 requêtes |
idempotency_bucket | 5 minutes bucketisées |
lock_timeout | 30 secondes |
lock_stale_threshold | 60 secondes |
clock_drift_max | 500 ms |
vault_key_path | kv/pd-295/memory-audit-hmac |
promotion_threshold_domain | reuse_score >= 0.3 ET nb_domains >= 1 |
promotion_threshold_global | reuse_score >= 0.6 ET nb_domains >= 2 |
4. Principes directeurs¶
- Zéro migration d'outil. FAISS + Ollama + Markdown + YAML restent la stack unique.
- Rien ne remplace l'existant. Les 5 briques s'ajoutent à côté. Les 4 skills de recherche,
learnings.jsonl,specs-index/,/coherence,gov-compounderPhase 5 continuent de fonctionner tels quels. - Refactor incrémental en 5 briques indépendantes ou faiblement couplées, chacune commitable seule, chacune avec ses propres tests de validation.
- Pas de dérive d'architecture abstraite. Les 4 analyses ChatGPT proposaient des architectures de plus en plus élaborées (RAG+reranker → moteur documentaire → MARS → moteur cognitif industriel). On retient ~15 % utile, ~85 % hors-scope reporté en candidats séparés G26-G32.
- Fail-closed partout où la sécurité, l'audit ou la conformité sont en jeu. Pas de "mode dégradé" qui contourne une règle de sécurité.
5. Périmètre fonctionnel — 5 briques¶
5.1 [B1] Index FAISS des fiches veille¶
Objectif : rendre les ~128 fiches de ProbatioVault-doc/docs/veille/**/*.md interrogeables par recherche sémantique, avec filtrage par verdict et impact_pv.
Livrables :
scripts/collect-veille.py: parse frontmatter YAML + titre H1 + résumé, écritdata/veille.jsonl. Clone decollect-specs.py.scripts/index-veille.py: embeddings via Ollamanomic-embed-text(768 dim), écritdata/veille-index.faiss+data/veille-embeddings.npy+data/veille-cache.json. Clone deindex-learnings.py.scripts/search-veille.py: clone desearch-learnings.pyavec flags--impact fort|modere|faible,--verdict signal|bruit|veille,--trace STORY_ID..claude/commands/veille-search.md: skill minimal qui invoquesearch-veille.py. À ne pas confondre avec/veille(archivage).- Modification de
scripts/reindex-all.py: ajouter phase veille après contracts/specs/plans.
Prérequis : aucun. Indépendant.
5.2 [B2] Résumé structuré et index FAISS des clarifications step 0¶
Objectif : capter l'intention décisionnelle des 4 questions PO step 0 au lieu de la jeter. La rendre interrogeable sémantiquement pour les futures stories du même domaine, sans jamais persister de verbatim (§3.1).
Livrables :
- Modification de
.claude/commands/gov-step-0.md: nouvelle Phase 1 bis qui : - Collecte les 4 réponses PO en mémoire du processus uniquement.
- Appelle Claude (température 0.2) pour produire un résumé structuré non-PII par catégorie.
- Demande validation binaire au PO.
- Si validé → écrit
PD-XX-clarifications.md(résumé uniquement, jamais verbatim). - Si rejeté → le verbatim et le résumé sont jetés.
scripts/collect-clarifications.py: parcourt/Users/loic/Developpement/ProbatioVault/*/docs/epics/**/PD-*-clarifications.md, parse frontmatter, écritdata/clarifications.jsonl.scripts/index-clarifications.py: clone deindex-learnings.py.scripts/search-clarifications.py: clone desearch-learnings.pyavec filtres--domainet--projectobligatoires.scripts/purge-clarifications.py: purge multi-artefacts avec vérification effective, support--story PD-XX --forcepour droit à l'oubli..claude/commands/clarifications.md: skill minimal.- Modification de
scripts/reindex-all.py: ajouter phase clarifications.
Schéma PD-XX-clarifications.md (résumé uniquement, jamais verbatim) :
---
story: PD-XX
step: 0
date: 2026-04-11
domain: auth-identity
project: backend
retention_until: 2027-10-11
---
# Clarifications PO — PD-XX (résumé structuré)
## Problème / besoin utilisateur
{résumé 2-4 lignes, aucune PII}
## Critères de succès
{résumé 2-4 lignes, aucune PII}
## Contraintes connues
{résumé 2-4 lignes, aucune PII}
## Priorités (must-have / nice-to-have)
{résumé 2-4 lignes, aucune PII}
Prérequis : aucun. Indépendant.
5.3 [B3] Scoring de réutilisation sur learnings.jsonl¶
Objectif : ajouter un champ reuse_score calculé à partir de data/learnings-injections.jsonl (créé par #28 le 2026-04-11). Le score reflète combien de fois un learning a été réinjecté en step 0 ET combien de fois la story receveuse a passé Gate 8 GO.
Formule (figée §3.4) : reuse_score = tanh((nb_injections*0.4 + nb_stories_gate8_go*0.4 + nb_domains*0.2) / 10) avec nb_domains = len(set(domains)), sérialisé à 4 décimales, plafond effectif 0.9999.
Livrables :
scripts/compute-learning-scores.py: litlearnings.jsonl+learnings-injections.jsonl+metrics.jsonl, écritdata/learnings-scores.jsonlfichier parallèle.- Modification de
scripts/search-learnings.py: tri secondaire parreuse_scoreaprès la distance FAISS. Flag--min-score N. - Intégration dans
/morning: afficher les 3 learnings avec le plus grandreuse_score. Observable de test : ligne de sortie contient exactement 3 entrées formatées- [story] [tags] score=X.XXXX.
Prérequis : #28 instrumentation (faite le 2026-04-11, commit 076dc3e).
5.4 [B4] Promotion automatique + éviction des learnings stale¶
Objectif : transformer learnings.jsonl d'un flux append-only plat en un système à 3 niveaux de portée : story, domain, global.
Règles de promotion (figées §3.9) :
reuse_score >= 0.3ETnb_domains >= 1→scope: domainreuse_score >= 0.6ETnb_domains >= 2→scope: global
Règles d'éviction (figées §3.9) :
nb_injections == 0ETdate > 8 semaines→data/learnings-archive.jsonl- L'index FAISS n'indexe plus les learnings archivés après la prochaine réindexation
- L'archive reste accessible via grep pour debug historique
- Restauration manuelle :
scripts/restore-learning.py --id <id>déplace delearnings-archive.jsonlverslearnings.jsonlavec audit signé (clé Vault §3.2).
Filtrage à l'injection step 0 : story dans domaine auth-identity → injecter les scope: story du même domaine + tous les scope: domain du domaine + tous les scope: global.
Livrables :
- Migration initiale de
learnings.jsonl: ajouterscope: storyà toutes les entrées existantes. scripts/promote-learnings.py: applique les règles de promotion.scripts/archive-stale-learnings.py: applique les règles d'éviction.scripts/restore-learning.py: restauration manuelle depuis archive.- Modification de
gov-learnings-inject.md+search-learnings.py: filtrage parscope. - Intégration dans
gov-compounder: appel des 2 scripts après réindexation step 10.
Précaution obligatoire : git add data/learnings.jsonl && git commit avant migration, rollback atomique.
Prérequis : [B3].
5.5 [B5] Injection unifiée au step 0¶
Objectif : modifier gov-learnings-inject.md pour qu'au step 0 d'une nouvelle story, il injecte 3 sources : learnings pertinents + fiches de veille pertinentes + clarifications passées du même domaine (résumé structuré).
Règles :
- B5 propage
--domain {story.domain} --project {story.project}àsearch-clarifications.py(§3.5). - B5 prend un read-lock sur
learnings.jsonletlearnings-scores.jsonlpendant l'exécution (§3.6). - B5 écrit la trace signée avant de retourner le bloc d'injection (§3.3). Si l'écriture échoue → bloc d'injection vide +
ERR-295-TRACE_WRITE_FAILED. - Cardinalité fixe : exactement 5 learnings + 3 veilles + 3 clarifications (§3.5).
Livrables :
- Modification de
.claude/commands/gov-learnings-inject.md: nouvelle Étape 1 « Injection unifiée mémoire ». - Format d'injection : bloc Markdown structuré avec 3 sections distinctes.
- Extension de
scripts/analyze-compounding.py: ventilation par source.
Prérequis : [B1], [B2], [B4].
6. Ordre d'implémentation et dépendances¶
B1 (veille) ──┐
B2 (clarifications) ──┤──> B5 (injection unifiée)
B3 (scoring) ───> B4 (promotion + éviction) ──┘
Ordre : B1 → B2 → B3 → B4 → B5.
7. Critères de succès — 4 indicateurs mesurables (mesure continue post-merge, §3.7)¶
| # | Indicateur | Script | Fenêtre | Cible |
|---|---|---|---|---|
| CS-1 | Taux de Gate 8 GO v1 augmente | scripts/measure-cs1.sh | T+30j, T+60j, T+90j post-B5 | ΔGO v1 > 0 |
| CS-2 | Réutilisation des learnings | scripts/measure-cs2.sh --window 90d | T+90j | ≥ 30 % |
| CS-3 | Capitalisation clarifications | scripts/measure-cs3.sh --window 30d | 30j glissants | ≥ 1 occurrence/mois |
| CS-4 | Activation veille | scripts/measure-cs4.sh --last 5 | 5 premières stories post-B5 | ≥ 5/5 |
CA-295-meta (Gate 8) : les 4 scripts existent et sont exécutables (test -x scripts/measure-csN.sh retourne 0 pour N=1..4). La valeur elle-même n'est pas vérifiée au Gate 8, elle est mesurée en continu post-merge.
8. Hors-scope explicite¶
- Pas de reranker neural (G27)
- Pas de BM25 hybrid (G28)
- Pas de metadata filter avancé au-delà de
--impact,--domain,--scope(G26) - Pas de knowledge graph (G30)
- Pas de skill router automatique (G32)
- Pas de résumés hiérarchiques pré-générés (G31)
- Pas de refonte de
specs-index/**/*.yaml - Pas de migration FAISS vers autre chose
- Pas de classifier d'intention
- Pas de boucle adaptative de retrieval
- Pas de nouveau moteur de stockage
- Pas de refonte en couches L1/L2/L3/L4
- Pas de verbatim des clarifications PO (§3.1)
- Pas de mode dégradé sur les traces d'injection (§3.3)
- Pas de signature HMAC non-canonique (§3.2)
9. Contraintes¶
9.1 Techniques¶
- Stack immuable : FAISS + Ollama + Markdown + YAML.
- Pas de pipelines externes (vérifié cycle 1).
- Rollback git obligatoire avant toute migration en place.
- Mode Ringbearer : B2 doit persister le résumé structuré (pas le verbatim) avant que
gov_ask_pone rende la validation. - Vault disponible pour
kv/pd-295/memory-audit-hmacdès le merge de B1.
9.2 Process¶
- Plan unique en 5 briques : PD-295 couvre les 5 briques en un seul plan, un seul Gate 8.
- Commits attendus : 1 commit par brique, 5 commits au total.
- Effort estimé : 4 jours de travail effectif, étalés sur 2-3 semaines pour laisser #28 accumuler ~5 stories d'instrumentation.
9.3 Dépendances amont validées¶
-
28 instrumentation
learnings-injections.jsonl— fait 2026-04-11 (commit 076dc3e)¶ -
27
extract-terminal-errors.py— fait 2026-04-11 (commit 039b503)¶ - L3 mode Ringbearer via
gov-interact.sh— fait 2026-04-11 (commit be1c610) - HashiCorp Vault accessible en
kv/pd-295/*— opérateur doit provisionner le chemin avant le merge B5
10. Priorités¶
| Priorité | Briques | Justification |
|---|---|---|
| Must-have | B1, B2 | Indépendantes de #28. ROI immédiat. |
| Should-have | B3, B4 | Dépendent de l'accumulation d'instrumentation #28. |
| Must-have orchestration | B5 | Cible finale. Sans B5, les autres briques restent passives. |
11. Ce qu'on ne fait pas (rappel)¶
- On ne remplace pas l'existant.
- On ne refactore pas
specs-index/. - On ne change pas d'outil de stockage.
- On n'introduit pas de couches hiérarchisées.
- On ne pose pas de classifier d'intention.
- On ne stocke jamais de verbatim PO.
- On n'accepte jamais de trace d'injection qui échoue silencieusement.
12. Références — projets d'inspiration¶
| Projet | Mécanismes repris | Fiche veille locale |
|---|---|---|
| Moerae (principale) | Segments append-only, lifecycle, promotion auto, éviction stale. B3+B4 transposent directement sur FAISS. | 2026-04-10-moerae-memoire-agents-rust-hnsw.md |
| MemPalace (secondaire) | Mémoire conversationnelle persistante (idée). Non repris : stockage intégral sans compression ni filtre PII. B2 prend l'idée de capturer, rejette le stockage intégral. | 2026-04-08-mempalace-memoire-claude-palais-spatial.md |
| Karpathy LLM Wiki (validation) | Pattern markdown-based memory. Confirme la direction générale. | 2026-04-08-karpathy-llm-wiki-second-brain-markdown.md |
| GBrain (tertiaire) | Pattern compiled truth + timeline. Non repris (volume insuffisant). | 2026-04-10-gbrain-thin-cli-sqlite-knowledge-base.md |
13. Livrables attendus¶
Identiques au besoin v1 (§12 archivé) + additions issues de §3 :
scripts/rotate-audit-hmac.sh(rotation clé Vault)scripts/purge-clarifications.py(purge multi-artefacts avec --story --force)scripts/measure-cs{1,2,3,4}.sh(mesure continue post-merge)scripts/restore-learning.py(restauration depuis archive)- Vecteurs de test HMAC pré-calculés en annexe de la spec
14. Learnings capitalisés du cycle 1 (archivé)¶
Le cycle 1 (3 itérations Gate 3, toutes NON_CONFORME) a produit les learnings structurels suivants qui sont tous adressés par le présent besoin v2 :
14.1 Learnings techniques¶
- Signature HMAC :
file_mtime_nsn'est pas un secret → clé Vault statique §3.2. - Canonicalisation : sans définition du payload canonique, la vérification HMAC n'est pas reproductible → JCS RFC 8785 + vecteurs de test pré-calculés §3.2.
- Contradiction INV/ERR trace : un mode dégradé sur trace crée un trou d'audit → fail-closed strict §3.3.
- RGPD clarifications : verbatim + indexation vectorielle + purge incomplète = droit à l'oubli techniquement impossible → résumé structuré non-PII §3.1.
- Règles vacuous :
max(1, nb_domains) >= 1toujours vrai → définition brute §3.4. - Bornes numériques : 15+ paramètres numériques non chiffrés dans le cycle 1 → tableau complet §3.9.
- KPI à Gate 8 : les 4 CS nécessitent 30-90j post-merge → §7 bis mesure continue §3.7.
- Rétention 18 mois vs purge : le bord
retention_until == todayn'était pas tranché → purge àdate > retention_untilstrictement.
14.2 Learnings process¶
- Spec iterative sans besoin enrichi = boucle plateau : cycle 1 a itéré 3 fois sur une spec sans remettre le besoin en question. La bonne approche est d'arrêter l'itération spec dès qu'un bloquant révèle un gap structurel, et de remonter au besoin. C'est ce qui est fait ici.
- Review qui creuse révèle des problèmes pré-existants : les bloquants v3 RGPD / contradiction INV-ERR / trou audit existaient déjà en v1 et v2 mais n'avaient pas été détectés. Une review Gate 3 qui "trouve plus" n'est pas une régression, c'est un signal de maturation. La bonne réponse n'est pas "corrige", c'est "intègre et repars".
- Plafond Art. I 3 itérations n'est pas une limite, c'est un signal : quand on l'atteint, c'est que le problème n'est pas dans la spec mais dans le besoin ou dans le design amont.
15. Arbitrages déjà tranchés par le PO¶
- Scope PD-295 = 5 briques en un seul plan (2026-04-11). Pas de descope de B2.
- Aucun risque de casser un pipeline externe (2026-04-11).
- Précaution [B4] : commit git atomique préalable avant migration
learnings.jsonl. - RGPD B2 = résumé structuré non-PII (2026-04-11 cycle 2). Pas de verbatim.
- HMAC = clé Vault + JCS RFC 8785 (2026-04-11 cycle 2). Pas de dérivation de mtime.
- Fail-closed strict sur trace (2026-04-11 cycle 2). Pas de mode dégradé.
- Cycle 2 relance spec + tests (2026-04-11). Les 3 itérations du cycle 1 sont archivées dans
cycle-1/.