PD-295 — Spécification v3
1. Objectif
La User Story PD-295 DOIT transformer la bibliothèque documentaire existante en mémoire vivante injectée automatiquement au step 0, en ajoutant cinq briques contractuelles :
- B1 : index sémantique des fiches de veille.
- B2 : persistance et index des clarifications PO step 0.
- B3 : scoring de réutilisation des learnings.
- B4 : promotion de portée et éviction stale des learnings.
- B5 : injection unifiée learnings + veille + clarifications.
La story DOIT préserver les mécanismes existants sans régression.
2. Périmètre / Hors périmètre
Inclus
- Ajout des artefacts B1..B5 (JSONL, FAISS, NPY, scripts, commandes).
- Injection step 0 des trois sources avec traces systématiques.
- Ventilation compounding par source.
- Migration fonctionnelle
learnings.jsonl avec scope. - Promotion/éviction selon règles explicites.
- Réindexation globale avec phases veille + clarifications.
- Signature/horodatage des événements via clé HMAC Vault versionnée.
- Script contractuel
scripts/rotate-audit-hmac.sh (rotation manuelle clé audit). - Baseline CS-1 via
scripts/measure-cs1-baseline.py. - KPI CS-2/CS-3/CS-4 via
scripts/measure-cs2.sh, scripts/measure-cs3.sh, scripts/measure-cs4.sh. /morning obligatoire avec top 3 réutilisés. - Rétention clarifications 18 mois avec purge déclenchée par step 10, hook
on_story_close, et cron quotidien.
Exclu
- Migration de stack hors FAISS + Ollama + Markdown/YAML/JSONL.
- Remplacement des skills existants.
- Nouveau moteur de stockage (Elasticsearch, pgvector, SQLite, graph DB).
- Reranker neural, BM25 hybride, classifier d’intention, skill router auto, boucle adaptative.
- Refonte
specs-index/**/*.yaml. - Architecture L1/L2/L3/L4.
- Attribution causale mono-facteur “injection => GO Gate 8” sans protocole statistique dédié.
3. Définitions
| Terme | Définition |
| Mémoire vivante | Injection proactive et traçable de connaissances passées au step 0. |
| Learning | Leçon issue des gates/REX, stockée dans learnings.jsonl. |
| Scope learning | Portée d’un learning : story, domain, global. |
| Clarification PO | Réponse verbatim aux 4 questions step 0. |
| Veille | Fiche documentaire externe stockée dans docs/veille/.... |
| Injection | Ajout de contexte mémoire dans le prompt step 0. |
tag_hash | sha256(",".join(sorted(tags)))[:12]. |
learning_id | Identifiant stable : {story}-{gate}-{tag_hash}. |
nb_domains | len(set(domain des events learning_injected pour un learning_id)) (peut valoir 0). |
reuse_score_brut | 0.4*nb_injections + 0.4*nb_stories_gate8_go_apres_injection + 0.2*nb_domains. |
reuse_score | tanh(reuse_score_brut/10) borné dans [0,1), sérialisé à 6 décimales. |
| Clé idempotence | {story_id, step, operation, timestamp_bucket_5min} (sans query_hash). |
| Conflit idempotence | Même clé idempotence + query_hash différent => ERR-295-10. |
| Fenêtre rate-limit | 5 minutes glissantes (non bucketisées). |
| Source d’injection | learning, veille, clarification. |
key_version | Version Vault de la clé audit utilisée pour HMAC d’un événement. |
| Réconciliation | Contrôle périodique cohérence JSONL/FAISS/traces. |
4. Invariants (non négociables)
| ID | Règle | Justification |
| INV-295-01 | Stack strictement FAISS 1.x + Ollama + Markdown/YAML/JSONL + scripts Python/Shell. | Zéro migration d’outil. |
| INV-295-02 | Les 4 skills de recherche existants et gov-compounder restent non-régressifs. | Compatibilité ascendante. |
| INV-295-03 | B1 produit un corpus veille interrogeable avec filtres impact_pv et verdict. | Activation veille. |
| INV-295-04 | Clarifications step 0 persistées dans tous les modes (local + Ringbearer) avant reprise rédaction besoin. | Pas de perte de connaissance. |
| INV-295-05 | Clarifications persistées en verbatim strict (pas de synthèse). | Fidélité probatoire. |
| INV-295-06 | B3 écrit data/learnings-scores.jsonl en parallèle sans mutation de data/learnings.jsonl. | Immutabilité historique. |
| INV-295-07 | Formule contractuelle reuse_score_brut/reuse_score avec nb_domains défini §3 ; reuse_score borné [0,1). | Reproductibilité. |
| INV-295-08 | Toute entrée learnings.jsonl possède scope in {story,domain,global} après migration initiale. | Filtrage testable. |
| INV-295-09 | Promotion : story->domain si reuse_score>=0.3 && nb_domains>=1 ; domain->global si reuse_score>=0.6 && nb_domains>=2. | Montée de portée. |
| INV-295-10 | Éviction : scope=story avec nb_injections==0 et âge >56 jours vers archive. | Nettoyage stale explicite. |
| INV-295-11 | Toute transition d’état non listée §5.4 est interdite. | FSM fermée. |
| INV-295-12 | B5 injecte 3 sections distinctes avec cardinalités fixes : learnings=5, veille=3, clarifications=3 ; si count=0, section présente avec “aucun résultat”. | Lisibilité/traçabilité. |
| INV-295-13 | L’injection step 0 trace chaque source avec story, step, domain, project, operation. | Mesure fiable. |
| INV-295-14 | search-clarifications exige --domain et --project. | Pertinence clarifications. |
| INV-295-15 | reindex-all inclut veille + clarifications après contracts/specs/plans. | Couverture index. |
| INV-295-16 | Toute migration in-place effective de learnings.jsonl exige un commit git préalable. | Rollback atomique. |
| INV-295-17 | analyze-compounding.py expose les métriques nécessaires à CS-½/¾ ; baseline CS-1 gelée via measure-cs1-baseline.py. | Pilotage empirique. |
| INV-295-18 | Concurrence obligatoire via fcntl.flock sur deux verrous : data/learnings.jsonl.lock et data/learnings-scores.jsonl.lock. B3: SH sur learnings + EX sur scores. B4: EX sur les deux. B5/search: SH sur les deux. Timeout 30s, fail-closed ERR-295-09, ordre d’acquisition fixe (learnings puis scores). | Robustesse multi-exécution. |
| INV-295-19 | Jointure learnings.jsonl ↔ learnings-scores.jsonl par {story,gate,tag_hash} ; score absent => fallback reuse_score=0.0. | Tolérance trous. |
| INV-295-20 | Tous les événements audit B1..B5 et restore-learning.py sont horodatés et signés HMAC avec clé lue depuis Vault (kv/pd-295/memory-audit-hmac), incluent key_version, et sont vérifiables a posteriori y compris version archivée. Vault indisponible au démarrage => fail-closed ERR-295-16. | Auditabilité probatoire. |
| INV-295-21 | Politique clarifications : rétention 18 mois, pas d’anonymisation ; purge via scripts/purge-clarifications.py déclenchée par (1) step 10, (2) hook on_story_close pour REJECTED/DONE_WITH_ANOMALY, (3) cron quotidien. | Gouvernance rétention complète. |
| INV-295-22 | /morning publie exactement 3 learnings top reuse_score; succès observable par code retour 0. Si impossible, échec explicite ERR-295-19. | Exigence opérationnelle testable. |
| INV-295-23 | Dans toute trace d’injection : count == len(result_ids). Sinon événement ignoré + journal audit_inconsistent_trace. | Cohérence trace/résultats. |
5. Flux nominaux
5.1 Modèle de données canonique (source unique)
5.1.1 Champs partagés
| Donnée | Validation | Comportement si invalide |
story_id | ^PD-[0-9]{1,4}$ | Rejet (ERR-295-01) |
domain | ^[a-z0-9]+(?:-[a-z0-9]+)*$ | Rejet (ERR-295-01) |
project | ^(backend|app|site|infra|doc|formal|pixel-governance|ia-governance)$ | Rejet (ERR-295-01) |
date | YYYY-MM-DD | Rejet (ERR-295-02) |
tag_hash | ^[a-f0-9]{12}$ | Ligne exclue + log |
learning_id | ^PD-[0-9]{1,4}-(?:[0-9]|10)-[a-f0-9]{12}$ | Ligne exclue + log |
schema_version | entier >=1 | Rejet entrée |
5.1.2 data/veille.jsonl
| Donnée | Validation | Comportement si invalide |
slug | ^[0-9]{4}-[0-9]{2}-[0-9]{2}-[a-z0-9-]+$ | Ligne exclue + log |
date | YYYY-MM-DD | Ligne exclue + log |
title | non vide | Ligne exclue + log |
tags[] | ^[a-z0-9-]{1,50}$ | Ligne exclue + log |
verdict | signal|bruit|veille | Ligne exclue + log |
impact_pv | fort|modere|faible | Ligne exclue + log |
summary | non vide | Ligne exclue + log |
source_path | chemin relatif veille .md, sans traversal | Ligne exclue + log |
5.1.3 PD-XX-clarifications.md
| Donnée | Validation | Comportement si invalide |
frontmatter.story | story_id valide | Fichier ignoré + log |
frontmatter.step | 0 | Fichier ignoré + log |
frontmatter.date | date ISO | Fichier ignoré + log |
frontmatter.domain | domain valide | Fichier ignoré + log |
frontmatter.project | project valide | Fichier ignoré + log |
Q1..Q4 | verbatim non vide | Fichier ignoré + log |
5.1.4 data/learnings-scores.jsonl
| Donnée | Validation | Comportement si invalide |
learning_id | canonique | Ligne exclue + log |
story | story_id valide | Ligne exclue + log |
gate | 0..10 | Ligne exclue + log |
tag_hash | 12 hex | Ligne exclue + log |
nb_injections | entier >=0 | Ligne exclue + log |
nb_stories_gate8_go_apres_injection | entier >=0 | Ligne exclue + log |
nb_domains | entier >=0 | Ligne exclue + log |
reuse_score_brut | décimal >=0 | Ligne exclue + log |
reuse_score | décimal [0,1) | Ligne exclue + log |
Jointure officielle avec learnings.jsonl : {story, gate, tag_hash}.
Score absent : reuse_score=0.0, reuse_score_brut=0.0.
5.1.5 data/learnings.jsonl (étendu)
| Donnée | Validation | Comportement si invalide |
story | story_id valide | Ligne exclue + log |
gate | 0..10 | Ligne exclue + log |
verdict | GO|RESERVE|NON_CONFORME|ESCALADE | Ligne exclue + log |
tags[] | ^#[a-z0-9-]{1,50}$ | Ligne exclue + log |
learning | non vide | Ligne exclue + log |
scope | story|domain|global | Rejet migration/promotion |
date | date ISO | Ligne exclue + log |
Règle d’intégrité : les tags d’un learning historique sont immuables.
Si dérive détectée (learning_id recalculé différent), ligne exclue du scoring et log learning_id_drift.
5.1.6 data/learnings-injections.jsonl (trace unifiée signée)
Format événement obligatoire :
{
"event": "learning_injected",
"timestamp_iso8601": "2026-04-11T09:42:17Z",
"story_id": "PD-295",
"step": 0,
"source": "learning",
"domain": "auth-identity",
"project": "backend",
"operation": "step0_injection",
"timestamp_bucket": "2026-04-11T09:40:00Z",
"query": "mfa b2b priorites",
"query_hash": "sha256:...",
"count": 5,
"result_ids": ["PD-19-3-a1b2c3d4e5f6"],
"key_version": 12,
"signature_hmac_sha256": "hex...",
"schema_version": 3
}
Règles contractuelles :
- Clé idempotence :
{story_id, step, operation, timestamp_bucket}. timestamp_bucket : arrondi 5 minutes UTC. query.length <= 500 caractères, sinon ERR-295-17 QUERY_TOO_LONG. count == len(result_ids), sinon événement ignoré + log audit_inconsistent_trace. - Signature :
hmac_sha256(payload_canonique, secret_key). - Clé HMAC :
secret_key = vault_read("kv/pd-295/memory-audit-hmac").data.key - clé binaire 32 octets (générée au déploiement via
openssl rand -hex 32) key_version de Vault est sérialisée dans chaque événement - Rotation manuelle :
- opérateur exécute
scripts/rotate-audit-hmac.sh - nouvelle clé écrite dans
kv/pd-295/memory-audit-hmac - ancienne clé archivée dans
kv/pd-295/memory-audit-hmac/archive/{date} - événement
audit_key_rotated signé émis avec nouvelle key_version - Vérification a posteriori :
- vérificateur lit
key_version - version active : lecture chemin principal
- version historique : lecture archive correspondante
- Vault indisponible au démarrage :
- fail-closed
- aucun événement audit émis
ERR-295-16 AUDIT_KEY_UNAVAILABLE - skill
/gov interrompu - Bootstrap initial clé : opération humaine documentée, pas d’auto-bootstrap.
5.2 Bornes numériques obligatoires
| Paramètre | Défaut | Min | Max | Unité | Comportement hors bornes |
embedding_dimension | 768 | 768 | 768 | dimensions | Rejet |
top_k_learnings_step0 | 5 | 5 | 5 | éléments | Rejet |
top_k_veille_step0 | 3 | 3 | 3 | éléments | Rejet |
top_k_clarifications_step0 | 3 | 3 | 3 | éléments | Rejet |
min_score_default | 0.00 | 0.00 | 0.99 | score | Rejet argument |
score_weight_injections | 0.40 | 0.40 | 0.40 | ratio | Rejet config |
score_weight_gate8_go | 0.40 | 0.40 | 0.40 | ratio | Rejet config |
score_weight_domains | 0.20 | 0.20 | 0.20 | ratio | Rejet config |
score_tanh_divisor | 10 | 10 | 10 | brut | Rejet config |
promotion_threshold_domain | 0.30 | 0.30 | 0.30 | score | Rejet config |
promotion_threshold_global | 0.60 | 0.60 | 0.60 | score | Rejet config |
stale_evict_age | 56 | 56 | 56 | jours | Rejet config |
po_question_timeout | 3600 | 60 | 86400 | secondes | ERR-295-18 |
parallel_sources_count | 3 | 3 | 3 | sources | Rejet |
morning_top_reused | 3 | 3 | 3 | éléments | ERR-295-19 |
query_max_chars | 500 | 1 | 500 | caractères | ERR-295-17 |
lock_acquire_timeout | 30 | 5 | 120 | secondes | ERR-295-09 |
lock_stale_file_age | 60 | 60 | 60 | secondes | Rejet config |
idempotence_window | 24 | 1 | 168 | heures | Rejet config |
timestamp_bucket_size | 5 | 5 | 5 | minutes | Rejet config |
rate_limit_step0 | 3 | 1 | 10 | requêtes / 5 min glissantes / story | ERR-295-11 |
reconciliation_interval | 5 | 1 | 60 | minutes | Rejet config |
orphan_threshold | 15 | 5 | 120 | minutes | MEMORY_DEGRADED |
clearing_cycles_required | 2 | 1 | 10 | cycles | Rejet config |
clarifications_retention_months | 18 | 18 | 18 | mois | Rejet config |
5.3 Clarifications JSONL + SLA
5.3.1 Schéma data/clarifications.jsonl
{
"story": "PD-42",
"domain": "auth-identity",
"project": "backend",
"date": "2026-03-15",
"path": "docs/epics/auth-identity/PD-42-mfa/PD-42-clarifications.md",
"questions": [
{"id": "Q1", "theme": "probleme", "answer": "..."},
{"id": "Q2", "theme": "criteres", "answer": "..."},
{"id": "Q3", "theme": "contraintes", "answer": "..."},
{"id": "Q4", "theme": "priorites", "answer": "..."}
],
"retention_until": "2027-09-15"
}
| Champ | Type | Contrainte |
story | string | ^PD-[0-9]{1,4}$ |
domain | string | ^[a-z0-9]+(?:-[a-z0-9]+)*$ |
project | string | enum projet valide |
date | string | ISO YYYY-MM-DD |
path | string | chemin relatif .md existant |
questions | array | taille = 4 |
questions[].id | string | Q1..Q4 |
questions[].theme | string | probleme|criteres|contraintes|priorites |
questions[].answer | string | non vide, verbatim |
retention_until | string | ISO YYYY-MM-DD, exactement date + 18 mois |
Fichier invalide : ligne exclue + log.
5.3.2 SLA temporels
| Paramètre | Valeur défaut | Min | Max | Comportement à expiration |
po_question_timeout | 3600s | 60s | 86400s | ERR-295-18 |
stale_evict_age | 56 jours | 56 | 56 | Archivage |
idempotence_window | 24h | 1h | 168h | Nouvelle tentative |
lock_acquire_timeout | 30s | 5s | 120s | ERR-295-09 |
lock_stale_file_age | 60s | 60s | 60s | cleanup lockfile orphelin |
orphan_threshold | 15 min | 5 | 120 | MEMORY_DEGRADED |
clarifications_retention_months | 18 mois | 18 | 18 | purge automatique multi-déclencheurs |
5.4 Machine à états des learnings (transitions + retours)
États : STORY_ACTIVE, DOMAIN_ACTIVE, GLOBAL_ACTIVE, ARCHIVED.
STORY_ACTIVE : - STORY_ACTIVE -> DOMAIN_ACTIVE : AUTORISÉE (reuse_score>=0.30 && nb_domains>=1). - STORY_ACTIVE -> GLOBAL_ACTIVE : INTERDITE. - STORY_ACTIVE -> ARCHIVED : AUTORISÉE (nb_injections==0 && age>56 jours).
DOMAIN_ACTIVE : - DOMAIN_ACTIVE -> GLOBAL_ACTIVE : AUTORISÉE (reuse_score>=0.60 && nb_domains>=2). - DOMAIN_ACTIVE -> STORY_ACTIVE : INTERDITE (downgrade auto). - DOMAIN_ACTIVE -> ARCHIVED : INTERDITE dans PD-295 (choix de périmètre explicite).
GLOBAL_ACTIVE : - GLOBAL_ACTIVE -> DOMAIN_ACTIVE : INTERDITE. - GLOBAL_ACTIVE -> STORY_ACTIVE : INTERDITE. - GLOBAL_ACTIVE -> ARCHIVED : INTERDITE dans PD-295 (choix de périmètre explicite).
ARCHIVED : - ARCHIVED -> STORY_ACTIVE : AUTORISÉE uniquement manuellement via scripts/restore-learning.py --id <learning_id>. - ARCHIVED -> DOMAIN_ACTIVE : INTERDITE. - ARCHIVED -> GLOBAL_ACTIVE : INTERDITE.
Règles :
- Aucun downgrade automatique.
restore-learning.py émet un événement signé avec la même politique Vault (INV-295-20), incluant key_version.
5.5 Flux nominal N1 — B1 Index veille
- Collecter les fiches veille et produire
data/veille.jsonl. - Indexer embeddings 768 + FAISS.
- Exposer recherche avec
--impact, --verdict. - Tracer les injections veille (
source=veille) avec signature Vault. - Intégrer la phase veille dans
reindex-all.
5.6 Flux nominal N2 — B2 Clarifications step 0
- Poser les 4 questions PO step 0 (tous modes).
- Persister
PD-XX-clarifications.md verbatim avant rédaction besoin. - Collecter en
data/clarifications.jsonl conforme §5.3.1. - Indexer corpus clarifications.
- Rechercher avec
--domain et --project obligatoires. - Tracer
source=clarification signé. - Purger >18 mois via :
- step 10
gov-compounder, - hook
on_story_close (REJECTED, DONE_WITH_ANOMALY), - cron quotidien de sécurité.
5.7 Flux nominal N3 — B3 Scoring de réutilisation
- Acquérir locks (ordre fixe) : SH
learnings.jsonl.lock, puis EX learnings-scores.jsonl.lock. - Lire
learnings.jsonl, learnings-injections.jsonl, metrics.jsonl. - Calculer
reuse_score_brut, puis reuse_score=tanh(reuse_score_brut/10). - Écrire
data/learnings-scores.jsonl (parallèle). - Trier
search-learnings par distance FAISS puis reuse_score secondaire. --min-score optionnel (<=0.99). /morning publie 3 top scores ; succès observable code retour 0, sinon ERR-295-19. - Geler baseline CS-1 via
scripts/measure-cs1-baseline.py à la date merge B5. - Libérer les deux locks.
- Détecter si migration
scope est nécessaire (au moins une ligne sans scope). - Si migration nécessaire, vérifier précondition commit git (
INV-295-16), sinon passer. - Acquérir EX lock sur
learnings.jsonl.lock puis learnings-scores.jsonl.lock. - Migrer
scope: story uniquement pour lignes sans scope. - Promouvoir selon seuils.
- Archiver stale (
scope=story, 0 injection, >56 jours). - Exclure archivés de l’index actif à la réindexation suivante.
- Libérer locks ; journaliser événement signé avec
key_version.
5.9 Flux nominal N5 — B5 Injection unifiée step 0
- Construire requêtes depuis
{story_id,title,domain,project}. - Acquérir SH lock sur
learnings.jsonl.lock puis learnings-scores.jsonl.lock. - Évaluer d’abord le rate-limit (fenêtre glissante) ; dépassement =>
ERR-295-11. - Évaluer ensuite idempotence bucket 5 min.
- Lancer en parallèle :
search-learnings ... --top-k 5 --trace search-veille ... --top-k 3 --trace search-clarifications.py --domain {story.domain} --project {story.project} --top-k 3 --trace - Assembler bloc Markdown à 3 sections distinctes.
- Appliquer cardinalités fixes 5/3/3 ; si
count=0 section présente avec “aucun résultat”. - Tracer chaque source (événements signés,
key_version). - 1 ou 2 sources indisponibles : injection partielle + état dégradé.
- 3 sources indisponibles : blocage step 0 (
ERR-295-06). - Libérer les deux locks.
5.10 Stratégie de migration DDL
Aucune migration DDL SQL applicable.
5.11 Atomicité multi-composant
Aucun flux DB+queue introduit ; non applicable.
5.12 Mécanismes de protection distribuée
| Mécanisme | Spécification contractuelle |
| RWLock | Deux lockfiles : data/learnings.jsonl.lock, data/learnings-scores.jsonl.lock. Ordre obligatoire d’acquisition : learnings puis scores. Timeout 30s -> ERR-295-09. |
| Reprise crash lock | Lockfile orphelin mtime>60s : suppression puis nouvelle tentative. |
| Rate-limit (prioritaire) | 3 req / 5 min glissantes / story évalué avant idempotence ; dépassement -> ERR-295-11. |
| Idempotence | Clé {story_id,step,operation,timestamp_bucket} ; rejeu identique => même résultat logique sans doublon. |
| Conflit payload | Même clé idempotence + query_hash différent => ERR-295-10. |
| Horloge | Contrôle monotonicité UTC locale ; dérive détectée => MEMORY_DEGRADED + alerte, pas de suppression silencieuse de traces. |
| Réconciliation | Intervalle 5 min ; orphelin >15 min => MEMORY_DEGRADED + rattrapage. |
| Clearing | Retour MEMORY_HEALTHY après 2 cycles conformes consécutifs. |
| Audit key rotation | scripts/rotate-audit-hmac.sh rotate + archive + événement audit_key_rotated. |
5.13 Contraintes inter-modules
| Élément | Spécification |
| Points d’entrée protégés | /gov-step-0, /gov-learnings-inject, /gov-compounder, /morning, hook on_story_close. |
| Scripts obligatoires | measure-cs1-baseline.py, measure-cs2.sh, measure-cs3.sh, measure-cs4.sh, purge-clarifications.py, restore-learning.py, rotate-audit-hmac.sh. |
| Données partagées | story_id, domain, project, gate8_go_v1, learning_id, query_hash, key_version. |
| Liaison cross-module | learning_id + story_id + step. |
| Scope d’enregistrement | Commandes ciblées uniquement. |
| Exceptions d’accès | Aucune exception de rôle dans PD-295. |
5bis. Diagrammes
Diagramme d’état
stateDiagram-v2
[*] --> STORY_ACTIVE
note right of STORY_ACTIVE
Garde B4: migration scope autorisée
seulement si commit git préalable
quand au moins une ligne sans scope
end note
STORY_ACTIVE --> DOMAIN_ACTIVE : score>=0.30 && nb_domains>=1
STORY_ACTIVE --> ARCHIVED : nb_injections=0 && age>56j
DOMAIN_ACTIVE --> GLOBAL_ACTIVE : score>=0.60 && nb_domains>=2
ARCHIVED --> STORY_ACTIVE : restore-learning.py (manuel, event signé Vault)
note left of STORY_ACTIVE
Transitions interdites explicitement rejetées:
STORY->GLOBAL, DOMAIN->STORY, DOMAIN->ARCHIVED,
GLOBAL->DOMAIN, GLOBAL->STORY, GLOBAL->ARCHIVED
end note
Diagramme de séquence
sequenceDiagram
participant S0 as gov-step-0
participant INJ as gov-learnings-inject
participant L1 as learnings.jsonl.lock
participant L2 as learnings-scores.jsonl.lock
participant SRCH as search-*
participant TRACE as learnings-injections.jsonl
participant VLT as Vault
S0->>INJ: {story_id,domain,project,title}
INJ->>VLT: read kv/pd-295/memory-audit-hmac
alt vault indisponible
INJ-->>S0: ERR-295-16 (fail-closed)
else vault disponible
INJ->>L1: LOCK_SH (timeout 30s, fail-closed ERR-295-09)
INJ->>L2: LOCK_SH (timeout 30s, fail-closed ERR-295-09)
INJ->>INJ: rate-limit glissant puis idempotence bucket
par learning
INJ->>SRCH: search-learnings --top-k 5 --trace
and veille
INJ->>SRCH: search-veille --top-k 3 --trace
and clarification
INJ->>SRCH: search-clarifications --domain --project --top-k 3 --trace
end
SRCH->>TRACE: append events signés + key_version
INJ-->>S0: bloc mémoire 3 sections (ou "aucun résultat")
INJ->>L2: unlock
INJ->>L1: unlock
end
6. Cas d’erreur
| ID | Condition d’erreur | Réponse attendue |
| ERR-295-01 | story_id/domain/project invalide | Rejet immédiat, aucune écriture |
| ERR-295-02 | Frontmatter/date/champs obligatoires invalides | Fichier/ligne exclu + log |
| ERR-295-03 | Enum invalide (impact_pv, verdict, scope, source) | Rejet argument ou ligne exclue |
| ERR-295-04 | search-clarifications sans --domain ou --project | Rejet commande |
| ERR-295-05 | Index FAISS manquant pour une source | Source dégradée, injection partielle |
| ERR-295-06 | Trois sources indisponibles au step 0 | Blocage step 0 |
| ERR-295-07 | learnings-scores.jsonl absent pendant B4 | Promotion désactivée, scopes inchangés |
| ERR-295-08 | Précondition commit préalable non satisfaite (migration effective requise) | Migration B4 refusée |
| ERR-295-09 | Lock non acquis avant timeout | Rejet sans effet de bord |
| ERR-295-10 | Même clé idempotence + query_hash différent | Rejet conflit idempotence |
| ERR-295-11 | Quota rate-limit step 0 dépassé | Rejet explicite |
| ERR-295-12 | Échec d’écriture trace (Vault OK) | Injection maintenue, observabilité dégradée |
| ERR-295-13 | Signature HMAC absente/invalide ou key_version incohérente | Événement rejeté, alerte audit |
| ERR-295-14 | Échec purge clarifications sur un déclencheur | Warning explicite + relance requise |
| ERR-295-15 | restore-learning.py sur learning_id absent/invalide | Rejet restauration |
| ERR-295-16 | Vault indisponible pour lecture clé audit au démarrage | Fail-closed, arrêt skill /gov |
| ERR-295-17 | query > 500 caractères | Rejet commande/trace |
| ERR-295-18 | Timeout questions PO step 0 | Step 0 interrompu |
| ERR-295-19 | /morning ne peut pas publier exactement 3 entrées | Échec conformité observable (exit != 0) |
7. Critères d’acceptation — sortie Gate 8
| ID | Critère | Observable |
| CA-295-01 | B1 génère corpus veille conforme §5.1.2 | data/veille.jsonl valide |
| CA-295-02 | B1 indexe veille en dimension 768 | index FAISS lisible, dim 768 |
| CA-295-03 | Filtres veille fonctionnent (impact,verdict) | résultats conformes |
| CA-295-04 | B2 persiste les 4 clarifications verbatim en mode local et Ringbearer | PD-XX-clarifications.md complet |
| CA-295-05 | Persistance clarifications avant rédaction besoin | timestamp clarification <= besoin |
| CA-295-06 | search-clarifications refuse sans domain/project | échec explicite |
| CA-295-07 | B3 écrit learnings-scores.jsonl sans muter learnings.jsonl | hash inchangé |
| CA-295-08 | reuse_score suit la formule normalisée tanh | recalcul identique |
| CA-295-09 | B4 ajoute scope: story aux entrées héritées | aucune ligne sans scope |
| CA-295-10 | Promotions respectent seuils 0.30/0.60 + nb_domains | transitions exactes |
| CA-295-11 | Éviction stale suit règle 56 jours + 0 injection | archive correcte |
| CA-295-12 | B5 produit bloc à 3 sections, avec “aucun résultat” si besoin | bloc conforme |
| CA-295-13 | Cardinalités strictes : 5 learnings, 3 veilles, 3 clarifications | cardinalités exactes |
| CA-295-14 | Traces d’injection incluent source, domain, project, operation, key_version, signature | ventilables + vérifiables |
| CA-295-15 | reindex-all inclut veille + clarifications après plans | ordre vérifiable |
| CA-295-16 | Protections concurrence (double lock/idempotence/rate-limit/réconciliation/clearing) effectives | tests passent |
| CA-295-20 | measure-cs1-baseline.py gèle baseline pré-B5 (10 stories) et calcule CS-1 | artefact baseline + calcul reproductible |
| CA-295-21 | /morning publie exactement 3 learnings top score avec observable d’échec | sortie + code retour conformes |
| CA-295-22 | Tous les événements B1..B5 + restore sont signés/horodatés, vérifiés via Vault et key_version | vérification HMAC valide |
| CA-295-23 | Purge clarifications >18 mois fonctionne | suppressions conformes |
| CA-295-24 | restore-learning.py --id <id> restaure scope=story avec audit signé Vault | restauration contrôlée |
| CA-295-25 | B5 propage explicitement --domain {story.domain} --project {story.project} --top-k 3 à search-clarifications.py | commande observée conforme |
| CA-295-26 | Purge clarifications déclenchée par step10, on_story_close, et cron quotidien | 3 chemins observables |
7 bis. Formules des KPI de mesure continue post-merge
CS-1 (itérations Gate 8)
- Baseline gelée : 10 stories pré-B5.
- Fenêtre post : 10 stories post-B5.
- Formule :
CS-1 = taux_GO_v1_10_post_B5 - taux_GO_v1_10_pre_B5. - Script :
scripts/measure-cs1-baseline.py + calcul post.
CS-2 (réutilisation des learnings)
- Fenêtre : T+90j après merge B5.
- Formule :
(nb_learnings_injectés_au_moins_1_fois_dans_la_fenêtre / total_learnings_créés_avant_B5) * 100 - Cible :
>= 30%. - Source :
data/learnings-injections.jsonl + data/learnings.jsonl. - Script :
scripts/measure-cs2.sh --window 90d. - Alerte :
<20% à T+60j => signal /morning.
CS-3 (capitalisation clarifications)
- Fenêtre : 30 jours glissants.
- Formule :
nb_occurrences où search-clarifications.py --domain D remonte >=1 résultat pour une nouvelle story du même domaine - Cible :
>= 1 occurrence/mois. - Source : événements de session
source=clarification. - Script :
scripts/measure-cs3.sh.
CS-4 (activation veille)
- Fenêtre : 5 premières stories post-merge B5.
- Formule :
nb_stories où une fiche veille impact_pv in ('fort','modere') est injectée au step 0 - Cible :
>= 5 sur 5. - Source : événements de session
source=veille. - Script :
scripts/measure-cs4.sh.
| ID | Horizon | Critère | Mesure |
| CA-295-17 | T+90j | CS-2 >=30% | measure-cs2.sh |
| CA-295-18 | Mensuel | CS-3 >=1/mois | measure-cs3.sh |
| CA-295-19 | 5 premières stories post-B5 | CS-4 >=5/5 | measure-cs4.sh |
| CA-295-27 | Dès 10 stories post-B5 (max T+90j) | CS-1 > 0 | baseline CS-1 + recalcul |
8. Scénarios de test (Given / When / Then)
- ST-295-01 Veille nominale.
- ST-295-02 Veille invalide.
- ST-295-03 Clarifications tous modes.
- ST-295-04 Clarifications sans filtres.
- ST-295-05 Scoring tanh.
- ST-295-06 Jointure score manquant.
- ST-295-07 Migration scope.
- ST-295-08 Promotion seuils +
nb_domains. - ST-295-09 Éviction stale.
- ST-295-10 Injection unifiée 3 sections + cardinalités.
- ST-295-11 RWLock double fichier.
- ST-295-12 Rate-limit prioritaire.
- ST-295-13 Idempotence/rejeu.
- ST-295-14 Dégradation partielle source.
- ST-295-15 Indisponibilité totale.
- ST-295-16 Signature +
key_version. - ST-295-17
/morning obligatoire et observable. - ST-295-18 Baseline CS-1 gelée.
- ST-295-19 Purge 18 mois.
- ST-295-20 Restore archive signé.
- ST-295-21 Vérification signature sur clé archivée.
- ST-295-22 Purge via hook
on_story_close. - ST-295-23 Purge via cron quotidien.
- ST-295-24 Rejet
query > 500.
9. Hypothèses explicites
| ID | Hypothèse | Impact si faux |
| H-295-01 | Métadonnées story/domain/project disponibles au step 0. | Injection clarifications impossible. |
| H-295-02 | Dépôts ProbatioVault-* accessibles en lecture pour collecte clarifications. | Corpus incomplet. |
| H-295-03 | metrics.jsonl contient données Gate 8 exploitables. | KPI biaisés. |
| H-295-04 | story/gate/tags restent stables ; édition tags historiques interdite. | Dérive learning_id/score. |
| H-295-05 | Inventaire des consommateurs externes est validé avant migration scope. | Migration bloquée (précondition release). |
| H-295-06 | Horloge UTC locale exploitable pour bucket/idempotence. | Passage MEMORY_DEGRADED + alerte. |
| H-295-07 | Vault (kv/pd-295/memory-audit-hmac) est accessible aux exécuteurs autorisés. | ERR-295-16, arrêt /gov. |
| H-295-08 | Ollama nomic-embed-text disponible localement. | Recherche dégradée. |
| H-295-09 | Bootstrap initial clé audit effectué manuellement avant activation B1..B5. | Audit indisponible au démarrage. |
10. Points à clarifier
10.1 Contraintes techniques (stack obligatoire)
- Projet :
ProbatioVault-ia-governance. - Langages : Python 3 + Shell Bash/Zsh.
- Recherche sémantique : FAISS CPU 1.x + NumPy.
- Embeddings : Ollama
nomic-embed-text (768). - Formats : Markdown, YAML, JSONL, FAISS, NPY.
- Orchestration :
.claude/commands/*. - Épic :
tooling.
10.2 Questions ouvertes
| ID | Point à clarifier | Donnée attendue |
| Q-295-03 | Politique de démotion manuelle global/domain -> story hors restore archive | Processus gouvernance |
| Q-295-07 | Escalade humaine en cas de MEMORY_DEGRADED prolongé | Seuil d’alerte opérationnelle |
Références :
- Epic :
tooling - JIRA :
PD-295 - Repos :
ProbatioVault-ia-governance, ProbatioVault-doc, ProbatioVault-* - Scripts contractuels :
measure-cs1-baseline.py, measure-cs2.sh, measure-cs3.sh, measure-cs4.sh, purge-clarifications.py, restore-learning.py, rotate-audit-hmac.sh
Changelog v2→v3
- G3v2-B1 : remplacement HMAC
file_mtime_ns par clé statique Vault (kv/pd-295/memory-audit-hmac), ajout key_version, rotation manuelle rotate-audit-hmac.sh, vérification archive, fail-closed ERR-295-16. - G3v2-M1 :
nb_domains redéfini sans max(1, ...) ; promotion story->domain réellement bloquée si nb_domains==0. - G3v2-M2 : cardinalité clarifications fixée (
top_k_clarifications_step0=3) et contractuelle dans INV/CA/B5. - G3v2-M3 : formules CS-2/CS-3/CS-4 ajoutées avec fenêtres, sources, scripts et seuils.
- G3v2-M4 : politique lock étendue à B3/B4/B5 sur deux lockfiles avec ordre d’acquisition fixe.
- G3v2-M5 : propagation explicite de
--domain --project --top-k 3 de B5 vers search-clarifications.py (CA-295-25). - G3v2-M6 : purge clarifications découplée du seul step 10 (step10 + hook
on_story_close + cron), CA-295-26. - G3v2-M7 : arbitrage rate-limit/idempotence explicite : rate-limit glissant évalué en premier (
ERR-295-11), puis idempotence bucket. - G3v2-M8 : schéma canonique
data/clarifications.jsonl ajouté en §5.3 avec contraintes types/champs. - G3v2-M9 :
restore-learning.py aligné sur la même politique clé Vault et key_version que B1..B5. - G3v2-M10 : limite
query <= 500 contractualisée (ERR-295-17). - G3v2-M11 : règle
count == len(result_ids) contractualisée (INV-295-23, log audit_inconsistent_trace). - Corrections mineures intégrées : migration scope one-shot clarifiée, observabilité
/morning explicitée, diagrammes alignés sur gardes contractuelles, hypothèse consommateurs externes rendue contrôlée, borne max min_score_default fixée à 0.99, garde de cohérence horloge ajoutée, stabilité learning_id durcie.