Aller au contenu

PD-295 — Spécification (cycle 2 v3)

1. Objectif

La User Story PD-295 DOIT transformer la bibliothèque documentaire en mémoire vivante injectée automatiquement au step 0, via 5 briques contractuelles (B1..B5), avec sécurité fail-closed, audit HMAC canonique JCS (RFC 8785), conformité RGPD, isolation runtime des subprocess manipulant du verbatim PO, et signature des fichiers d’état.

2. Périmètre / Hors périmètre

Inclus

  • B1 : indexation sémantique des fiches de veille.
  • B2 : capture clarifications PO en résumé structuré non-PII, validation binaire PO.
  • B3 : scoring reuse_score et tri secondaire.
  • B4 : promotion scope + archivage stale.
  • B5 : injection unifiée learnings + veille + clarifications.
  • Isolation runtime subprocess verbatim.
  • Signature HMAC des fichiers d’état + filtrage lecture fail-closed.
  • Purge RGPD multi-artefacts + purge backups ciblés.
  • Gate 8 méta : existence/exécutabilité scripts CS-1..CS-4.
  • Mesures CS-1..CS-4 post-merge (T+30/T+60/T+90).

Exclu

  • Changement de stack (FAISS/Ollama/Markdown/YAML/JSONL imposés).
  • Reranker neural, BM25 hybride, knowledge graph, classifier d’intention, router automatique.
  • Refonte specs-index/**/*.yaml.
  • Migration DB/vector engine.
  • Persistance verbatim PO.
  • Mode dégradé autorisant injection non tracée.
  • Non-répudiation forte (EdDSA/HSM), report candidat G33.
  • Exécution multi-hôte active/active, report candidat G34.

3. Définitions

Terme Définition
B1..B5 Briques PD-295 (veille, clarifications, scoring, promotion/éviction, injection unifiée).
Clarification PO Résumé structuré non-PII validé, jamais verbatim persistant.
reuse_score_brut nb_injections*0.4 + nb_stories_gate8_go*0.4 + nb_domains*0.2.
reuse_score tanh(reuse_score_brut/10), 4 décimales, plafond effectif 0.9999.
Payload canonique JSON canonicalisé JCS RFC 8785, UTF-8, sans champ de signature.
Trace d’injection Événement signé écrit avant retour B5.
sig_hmac_sha256 Signature de ligne pour fichiers d’état (learnings-injections, learnings-scores, sessions).
Fail-closed Pas d’effet métier si précondition sécurité/audit échoue.
count_configured Cardinalité contractuelle cible (5/3/3).
count_effective min(count_configured, available_after_filter).
under_corpus Booléen vrai si count_effective < count_configured.
fail_closed_depth Compteur thread-local par invocation step 0, initialisé à 0, incrémenté sur fail-closed imbriqué.
CA-295-meta Contrôle Gate 8 : scripts measure-cs1..4.sh présents et exécutables uniquement.

4. Invariants

4.1 Invariants non négociables (runtime/produit)

ID Règle
INV-295-01 Clarifications persistées = résumé structuré non-PII validé, jamais verbatim.
INV-295-03 Clarification non validée PO => rejet sans écriture disque.
INV-295-04 Purge supprime clarifications.jsonl, index FAISS, embeddings NPY, cache JSON, traces query sessions liées au story_id.
INV-295-05 Purge rétention strictement à date > retention_until.
INV-295-06 Événements audit B1..B5 signés HMAC-SHA256 avec clé Vault kv/pd-295/memory-audit-hmac.
INV-295-07 Clé HMAC Vault obligatoire hex64 -> 32 bytes, sinon rejet.
INV-295-08 Signature sur payload JCS RFC 8785, champ signature exclu.
INV-295-09 Clé Vault indisponible/invalide au chargement de session => arrêt /gov par SystemExit(1). Tentative d’alerte signée avec dernière clé valide en mémoire; sinon log local UNSIGNED_DEGRADED.
INV-295-10 B5 écrit trace signée avant retour; si échec => bloc vide + ERR-295-TRACE_WRITE_FAILED.
INV-295-11 B5 fixe count_configured à 5/3/3 et calcule count_effective=min(configured, available); si insuffisant, under_corpus=true obligatoire.
INV-295-12 search-clarifications exige --domain et --project.
INV-295-13 reuse_score suit formule figée + 4 décimales + plafond 0.9999.
INV-295-14 Lock flock advisory obligatoire sur learnings.jsonl et learnings-scores.jsonl, timeout 30s, stale strict >60s.
INV-295-15 Toute transition non explicitement autorisée est interdite (ERR-295-STATE_TRANSITION_FORBIDDEN).
INV-295-16 Scripts measure-cs1.sh..measure-cs4.sh existent et sont exécutables (CA-295-meta).
INV-295-17 Récursion fail-closed bornée: fail_closed_depth autorisé jusqu’à 3 inclus; tentative de 4e fail-closed (depth=4) => arrêt process + alerte signée ERR-295-FAIL_CLOSED_RECURSION.
INV-295-18 Transition MEMORY_DEGRADED -> MEMORY_HEALTHY autorisée uniquement après 2 cycles consécutifs conformes post-dégradation.
INV-295-RUNTIME-01 Subprocess B2 verbatim: entrée via stdin uniquement, jamais argv; stdout/stderr redirigés vers buffer mémoire; CLAUDE_DISABLE_SESSION_LOG=1 forcé.
INV-295-RUNTIME-02 Aucun fichier disque n’est autorisé pour transporter du verbatim PO; transit uniquement via buffers mémoire Python et pipes OS (os.pipe). Aucun fallback tmpfs sur macOS.
INV-295-RUNTIME-03 Chaque invocation subprocess verbatim émet événement signé verbatim_subprocess_invoked{story_id,subprocess_name,duration_ms,success} sans verbatim.
INV-295-RUNTIME-04 config/pii_ruleset_v1.yaml DOIT être validé DPO via commit signé portant le tag dpo-approved; hash commit référencé dans le dépôt de conformité.
INV-295-STATE-01 Chaque ligne JSON écrite dans data/learnings-injections.jsonl, data/learnings-scores.jsonl, data/sessions/*.jsonl contient sig_hmac_sha256 (JCS sans sig).
INV-295-STATE-02 Lecture: lignes non signées/invalides ignorées silencieusement; compteur ERR-295-UNSIGNED_ENTRY incrémenté + événement signé.
INV-295-STATE-03 Écriture de ces fichiers via scripts/lib/write-signed-state.py obligatoire; écriture directe non signée => filtrée à la lecture.
INV-295-STATE-04 Migration one-shot scripts/sign-existing-state.py re-signe les entrées pré-B3 (commit 076dc3e) avant activation B3/B4/B5.
INV-295-STATE-05 Écriture append des fichiers d’état signés sous flock write-lock; lecture sans lock. Si ligne >4096 bytes (PIPE_BUF), warning + append sous write-lock strict avec fsync.
INV-295-BACKUP-01 Artefacts purgeables PD-295 exclus des backups automatiques via .nobackup ou tmutil addexclusion; historique existant purgeable via scripts/purge-backups.sh.
INV-295-HOOK-01 Hook on_story_close(story_id, final_state) appelé sur REJECTED, DONE_WITH_ANOMALY, DONE, et déclenche scripts/purge-clarifications.py --story {story_id} de façon idempotente.
INV-295-LS-01 STORY_ACTIVE -> DOMAIN_ACTIVE autorisée si reuse_score>=0.3 et nb_domains>=1.
INV-295-LS-02 STORY_ACTIVE -> GLOBAL_ACTIVE interdite.
INV-295-LS-03 STORY_ACTIVE -> ARCHIVED autorisée si nb_injections==0 et âge >56 jours.
INV-295-LS-04 DOMAIN_ACTIVE -> GLOBAL_ACTIVE autorisée si reuse_score>=0.6 et nb_domains>=2.
INV-295-LS-05 DOMAIN_ACTIVE -> STORY_ACTIVE interdite.
INV-295-LS-06 DOMAIN_ACTIVE -> ARCHIVED autorisée si stale.
INV-295-LS-07 GLOBAL_ACTIVE -> DOMAIN_ACTIVE interdite.
INV-295-LS-08 GLOBAL_ACTIVE -> STORY_ACTIVE interdite.
INV-295-LS-09 GLOBAL_ACTIVE -> ARCHIVED autorisée si stale.
INV-295-LS-10 ARCHIVED -> STORY_ACTIVE autorisée uniquement via restauration manuelle auditée.
INV-295-LS-11 ARCHIVED -> DOMAIN_ACTIVE interdite.
INV-295-LS-12 ARCHIVED -> GLOBAL_ACTIVE interdite.
INV-295-CL-01 VERBATIM_IN_MEMORY -> SUMMARY_PENDING_VALIDATION autorisée.
INV-295-CL-02 SUMMARY_PENDING_VALIDATION -> SUMMARY_VALIDATED si PO=oui.
INV-295-CL-03 SUMMARY_PENDING_VALIDATION -> REJECTED si PO=non.
INV-295-CL-04 SUMMARY_VALIDATED -> SUMMARY_INDEXED après écriture résumé.
INV-295-CL-05 SUMMARY_INDEXED -> EXPIRED si date > retention_until.
INV-295-CL-06 EXPIRED -> PURGED après purge vérifiée.
INV-295-CL-07 REJECTED terminal (-> * interdit).
INV-295-CL-08 PURGED terminal (-> * interdit).

4.2 Invariant de code (non-runnable en boîte noire)

ID Type Règle Vérification
INV-295-02 Invariant de code Le verbatim PO reste en mémoire volatile et est détruit en sortie de phase 1 bis. Revue de code + scripts/check-no-verbatim-persisted.sh.

5. Flux nominaux

5.1 Modèle de données canonique

ID Donnée Format / Règle Invalide
D-295-01 story_id ^PD-[0-9]{1,4}$ ERR-295-INVALID_STORY_ID
D-295-02 domain ^[a-z0-9]+(?:-[a-z0-9]+)*$ ERR-295-INVALID_DOMAIN
D-295-03 project enum {ia-governance,backend,app,site,infra,doc,formal,pixel-governance} ERR-295-INVALID_PROJECT
D-295-04 scope enum {story,domain,global,archived} rejet transition
D-295-05 tag_hash ^[a-f0-9]{12}$ ligne ignorée
D-295-06 learning_id ^PD-[0-9]{1,4}-[0-9]{1,2}-[a-f0-9]{12}$ ligne ignorée
D-295-07 query longueur 1..500 chars UTF-8 ERR-295-INVALID_QUERY_LENGTH
D-295-08 query_hash ^sha256:[a-f0-9]{64}$ ERR-295-INVALID_QUERY_HASH
D-295-09 key_hex ^[A-Fa-f0-9]{64}$ ERR-295-AUDIT_KEY_FORMAT_INVALID
D-295-10 key_version ^[1-9][0-9]*$ rejet trace
D-295-11 signature_hmac_sha256 ^[a-f0-9]{64}$ (événements audit) ERR-295-HMAC_VERIFICATION_FAILED
D-295-12 reuse_score format 0.0000..0.9999; règle écrêtage §5.7 rejet calcul
D-295-13 retention_until YYYY-MM-DD rejet persistance
D-295-14 timestamp_iso8601 YYYY-MM-DDTHH:MM:SS[.mmm]Z rejet trace
D-295-15 verdict enum {signal,bruit,veille} ligne ignorée
D-295-16 impact_pv enum {fort,modere,faible} ligne ignorée
D-295-17 Résumé clarification 4 sections; règles PII (email, phone, IBAN, adresse explicite) via pii_ruleset_v1 ERR-295-PII_DETECTED
D-295-18 source enum {learning,veille,clarification} rejet trace
D-295-19 count entier 0..999 rejet trace
D-295-20 result_ids[] tableau d’identifiants rejet trace
D-295-21 schema_version entier fixé 3 rejet trace
D-295-22 event_type enum incluant learning_injected,veille_injected,clarification_injected,verbatim_subprocess_invoked,audit_key_rotated,clarification_purged,unsigned_entry_detected,injection_failed_fail_closed,audit_key_unavailable_fatal,fail_closed_recursion rejet trace
D-295-23 brick enum {B1,B2,B3,B4,B5} rejet trace
D-295-24 sig_hmac_sha256 ^[a-f0-9]{64}$ (fichiers d’état) ligne ignorée + ERR-295-UNSIGNED_ENTRY
D-295-25 payload_canonique objet JSON JCS rejet trace
D-295-26 count_configured entier fixe selon source: 5/3/3 rejet trace
D-295-27 count_effective entier 0..count_configured rejet trace
D-295-28 under_corpus booléen rejet trace
D-295-29 subprocess_name string 1..64 rejet runtime trace
D-295-30 duration_ms entier 0..600000 rejet runtime trace
D-295-31 success booléen rejet runtime trace
D-295-32 fail_closed_depth entier 0..4; 4 atteignable uniquement à l’instant de l’abort récursif ERR-295-FAIL_CLOSED_RECURSION

5.2 Flux nominal B1 — Index veille

  1. Collecte ProbatioVault-doc/docs/veille/**/*.md.
  2. Production data/veille.jsonl conforme D-295-15/16.
  3. Index FAISS dimension 768.
  4. Recherche avec filtres --impact et --verdict.
  5. Erreurs de format journalisées sans bloquer les lignes valides.
  6. Toute ligne d’état écrite (sessions) est signée sig_hmac_sha256.

5.2.4 Chargement de la clé Vault (session)

  • La clé HMAC est chargée une seule fois au démarrage de la session /gov.
  • Si Vault est indisponible au chargement ou renvoie un format invalide, /gov s’arrête immédiatement (SystemExit(1)), avec code opérateur ERR-295-AUDIT_KEY_UNAVAILABLE ou ERR-295-AUDIT_KEY_FORMAT_INVALID.
  • Si Vault redevient disponible pendant la session, aucune relecture n’est faite; la clé de session reste immuable jusqu’au prochain démarrage.
  • Cette règle garantit qu’une session donnée n’utilise qu’un seul contexte clé/version.

5.3 Flux nominal B2 — Clarifications structurées non-PII + isolation runtime

  1. Step 0 collecte 4 réponses PO en mémoire volatile.
  2. Appel scripts/b2-sanitizer.py avec verbatim sur stdin uniquement.
  3. stdout/stderr subprocess redirigés vers buffer mémoire (pipe + buffer Python), jamais fichier.
  4. Environnement subprocess impose CLAUDE_DISABLE_SESSION_LOG=1 et TERM=dumb.
  5. Aucun fichier temporaire disque.
  6. Résumé structuré non-PII produit et validé PO (binaire).
  7. Si validé: écriture PD-XX-clarifications.md + indexation; sinon REJECTED sans persistance.
  8. Événement signé verbatim_subprocess_invoked émis sans verbatim.
  9. Effacement des buffers verbatim en sortie.

5.3.3 Architecture subprocess B2 sanitizer

gov-step-0 (process parent)
  1) Collecte verbatim en mémoire (io.StringIO)
  2) Prépare pipe stdin avec verbatim
  3) Lance subprocess "b2-sanitizer.py"

b2-sanitizer.py (process enfant)
  1) Lit verbatim depuis stdin
  2) Lance sous-subprocess "claude -p" avec:
     - stdin = verbatim
     - stdout = pipe vers b2-sanitizer
     - env = CLAUDE_DISABLE_SESSION_LOG=1, TERM=dumb
     - argv = ["-p","--append-system-prompt","SANITIZE: extraire intention, supprimer PII"]
  3) Lit le résumé via pipe
  4) Écrit le résumé sur stdout (pipe retour)

gov-step-0 (retour)
  1) Lit le résumé via pipe
  2) Écrase le buffer verbatim en mémoire
  3) Écrit uniquement le résumé structuré sur disque

5.4 Flux nominal B3 — Scoring de réutilisation + état signé

  1. Lecture learnings.jsonl, learnings-injections.jsonl, metrics.jsonl.
  2. Vérification signatures sur lignes learnings-injections; lignes non signées/invalides ignorées.
  3. Compteur ERR-295-UNSIGNED_ENTRY incrémenté pour chaque ligne ignorée.
  4. Calcul reuse_score_brut, puis reuse_score.
  5. Écriture data/learnings-scores.jsonl via scripts/lib/write-signed-state.py.
  6. Tri secondaire search-learnings sur reuse_score.
  7. /morning affiche 3 entrées top score format score=X.XXXX.
  8. Migration initiale: scripts/sign-existing-state.py re-signe les entrées pré-B3.

5.5 Flux nominal B4 — Promotion et éviction

  1. Migration initiale scope:story si absent.
  2. Promotion story->domain et domain->global selon seuils.
  3. Éviction stale (nb_injections==0, âge >56j) vers archive.
  4. Lecture des fichiers d’état signés; lignes non signées filtrées.
  5. Restauration uniquement via scripts/restore-learning.py (audit signé).
  6. Exclusion des archived de l’index actif.

5.6 Flux nominal B5 — Injection unifiée + fail-closed borné

  1. Acquisition locks lecture (INV-295-14).
  2. Recherches:
  3. learnings (top_k=5)
  4. veille (top_k=3)
  5. clarifications (top_k=3, --domain et --project obligatoires)
  6. Pour chaque source:
  7. count_configured fixé (5/3/3),
  8. count_effective=min(configured, available_after_filter),
  9. under_corpus=true si insuffisant.
  10. Construction bloc injection 3 sections.
  11. Construction événement audit conforme schéma générique §5.14.
  12. Signature HMAC JCS et écriture trace signée en session via helper signé.
  13. Si écriture réussie: retour bloc injection.
  14. Si échec écriture trace: ERR-295-TRACE_WRITE_FAILED, fail_closed_depth +=1, retour EMPTY_BLOCK tant que fail_closed_depth <=3.
  15. Si tentative de 4e fail-closed (fail_closed_depth==4): événement fail_closed_recursion signé (ou UNSIGNED_DEGRADED si clé absente), puis arrêt process SystemExit(1) avec ERR-295-FAIL_CLOSED_RECURSION.
  16. Si clé audit indisponible/invalide au moment critique: tentative d’événement audit_key_unavailable_fatal signé avec dernière clé valide en mémoire; sinon log local UNSIGNED_DEGRADED; arrêt process immédiat.

5.6.10 Scope du compteur fail_closed_depth

  • Scope: par invocation de step 0, thread-local.
  • Initialisation: 0 au début de l’invocation.
  • Incrément: à chaque fail-closed imbriqué dans le thread courant.
  • Réinitialisation: 0 en fin d’invocation (succès ou échec).
  • Pas de persistance disque, pas de partage inter-invocations.
  • Implémentation de référence: threading.local().

5.7 Bornes numériques

Paramètre Valeur
embedding_dimension 768 fixe
summary_temperature 0.2 fixe
top_k_learnings_step0 5 fixe
top_k_veille_step0 3 fixe
top_k_clarifications_step0 3 fixe
query.length_max 500
retention_clarifications 540 jours
eviction_learnings_stale 56 jours
rate_limit_window_sliding 5 min
rate_limit_max_requests 3
idempotency_bucket 5 min
lock_timeout 30 s
lock_stale_threshold 60 s (strict >60)
clock_drift_max 500 ms
promotion_threshold_domain reuse_score >=0.3 et nb_domains>=1
promotion_threshold_global reuse_score >=0.6 et nb_domains>=2
score_round_decimals 4
reuse_score_effective_max 0.9999
reuse_score_clip_threshold_pre_round >= 0.99995 => écrêtage 0.9999
fail_closed_depth_max_accepted 3
fail_closed_depth_abort 4

Règle stricte reuse_score: - Calcul pré-arrondi: raw=tanh(reuse_score_brut/10). - Si raw >= 0.99995, valeur sérialisée forcée à 0.9999. - Sinon arrondi décimal 4 chiffres. - 1.0000 est interdit.

5.8 Hooks de cycle de vie des stories

  • Hook: on_story_close(story_id, final_state).
  • Déclenchement: transitions vers REJECTED, DONE_WITH_ANOMALY, DONE.
  • Implémentation de référence: scripts/lib/story-hooks.sh, appelé depuis .claude/commands/gov.md lors de la transition Jira finale.
  • Action contractuelle: appel scripts/purge-clarifications.py --story {story_id}.
  • Idempotence: plusieurs appels successifs doivent produire le même état final (sans double-effet indésirable).

5.9 Format canonique du bloc d’injection ({{LEARNINGS}})

Le bloc injecté DOIT respecter la forme suivante (lignes et sections obligatoires):

## Mémoire pertinente pour cette story

### Learnings (count_configured=5, count_effective={N}, under_corpus={bool})
- [{story}] [{tags}] {learning_text} (scope: {scope}, score: {reuse_score:.4f})

### Veille (count_configured=3, count_effective={N}, under_corpus={bool})
- [{date}] [{impact_pv}] {title}

### Clarifications passées (count_configured=3, count_effective={N}, under_corpus={bool}, même domaine)
- [{story}, {date}] Priorités : {summary_priorities_one_line}

5.10 SLA temporels

Paramètre Valeur Expiration
retention_until clarifications 540 jours SUMMARY_INDEXED -> EXPIRED -> PURGED
stale learning 56 jours *_ACTIVE -> ARCHIVED
rate-limit 5 min glissantes décompte fenêtre
idempotence bucket 5 min recalcul autorisé hors bucket
lock timeout 30s ERR-295-LOCK_TIMEOUT
stale lock mtime > 60s suppression stale + retry unique
purge cron 24h purge sécurité

5.11 Machines à états

5.11.1 learning.scope

État Transitions autorisées Interdites
STORY_ACTIVE ->DOMAIN_ACTIVE, ->ARCHIVED ->GLOBAL_ACTIVE
DOMAIN_ACTIVE ->GLOBAL_ACTIVE, ->ARCHIVED ->STORY_ACTIVE
GLOBAL_ACTIVE ->ARCHIVED ->DOMAIN_ACTIVE, ->STORY_ACTIVE
ARCHIVED ->STORY_ACTIVE (manuel audité) ->DOMAIN_ACTIVE, ->GLOBAL_ACTIVE

5.11.2 clarification.lifecycle_state

État Transitions autorisées Interdites
VERBATIM_IN_MEMORY ->SUMMARY_PENDING_VALIDATION autres
SUMMARY_PENDING_VALIDATION ->SUMMARY_VALIDATED, ->REJECTED autres
SUMMARY_VALIDATED ->SUMMARY_INDEXED autres
SUMMARY_INDEXED ->EXPIRED autres
EXPIRED ->PURGED autres
REJECTED aucune ->*
PURGED aucune ->*

5.12 Migration DDL

Aucune migration DDL SQL.

5.13 Atomicité DB/queue

Non applicable (pas de flux transaction DB + queue dans PD-295).

5.14 Schéma générique d’événement audit + vecteurs HMAC

Schéma contractuel signé (JCS strict, champ signature exclu avant HMAC):

{
  "schema_version": 3,
  "timestamp_iso8601": "2026-04-11T15:42:33.123Z",
  "story_id": "PD-295",
  "event_type": "learning_injected|veille_injected|clarification_injected|verbatim_subprocess_invoked|audit_key_rotated|clarification_purged|unsigned_entry_detected|injection_failed_fail_closed|audit_key_unavailable_fatal|fail_closed_recursion",
  "brick": "B1|B2|B3|B4|B5",
  "key_version": 1,
  "payload_canonique": {},
  "sig_hmac_sha256": "..."
}

Vecteurs contractuels pré-calculés (utilisés par TC-NOM-06):

V1 (nominal B5 5/3/3 plein)
key_hex=000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f
payload_canonique_jcs={"brick":"B5","event_type":"learning_injected","key_version":1,"payload_canonique":{"sections":{"clarification":{"count_configured":3,"count_effective":3,"result_ids":["PD-090","PD-091","PD-092"],"under_corpus":false},"learning":{"count_configured":5,"count_effective":5,"result_ids":["PD-101","PD-102","PD-103","PD-104","PD-105"],"under_corpus":false},"veille":{"count_configured":3,"count_effective":3,"result_ids":["VE-201","VE-202","VE-203"],"under_corpus":false}}},"schema_version":3,"story_id":"PD-295","timestamp_iso8601":"2026-04-11T15:42:33.123Z"}
expected_sig=f92f494419827c18d40d69ca1d606e015ba72618056a9c8863b96f60e1a5240b

V4 (sous-corpus learnings 1/5)
key_hex=aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
payload_canonique_jcs={"brick":"B5","event_type":"learning_injected","key_version":1,"payload_canonique":{"count_configured":5,"count_effective":1,"result_ids":["PD-042"],"under_corpus":true},"schema_version":3,"story_id":"PD-295","timestamp_iso8601":"2026-04-11T15:42:34.123Z"}
expected_sig=84aa1fc05652688b4b426cb7b34f3563372d7225a002ba0ab673b4ed609e3a5a

V6 (rotation de clé 1 -> 2)
key_hex=aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
payload_canonique_jcs={"brick":"B3","event_type":"audit_key_rotated","key_version":2,"payload_canonique":{"archive_path":"kv/pd-295/memory-audit-hmac/archive/1","new_key_version":2,"previous_key_version":1},"schema_version":3,"story_id":"PD-295","timestamp_iso8601":"2026-04-11T15:42:35.123Z"}
expected_sig=377b66b089c4e337405c2d48ab93b0950eded52e758ee7a2b5acf25c6d2f60b8

V8 (purge clarification --force, droit à l’oubli)
key_hex=aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
payload_canonique_jcs={"brick":"B2","event_type":"clarification_purged","key_version":1,"payload_canonique":{"count_configured":3,"count_effective":0,"force":true,"result_ids":[],"story_id":"PD-295","under_corpus":true},"schema_version":3,"story_id":"PD-295","timestamp_iso8601":"2026-04-11T15:42:36.123Z"}
expected_sig=29262974fd91d2e0812bc52b0f83012ace1a19ca53b7bd69620a3a05699de17a

5.15 Signature des fichiers d’état

  • Writer unique: scripts/lib/write-signed-state.py.
  • Reader filtering: compute-learning-scores.py, promote-learnings.py, gov-learnings-inject.
  • Migration one-shot: scripts/sign-existing-state.py.
  • Rotation clé: scripts/rotate-audit-hmac.sh + re-signature existant + archive clé précédente.
  • Écriture manuelle autorisée via scripts/write-signed-state.py <file> <payload_json> uniquement.

5.16 Protection distribuée et déploiement

Sujet Contrat
Lock flock advisory local sur fichiers ciblés.
Fichiers d’état signés write-lock append obligatoire, lecture lock-free.
Déploiement Mono-hôte obligatoire. Multi-hôte hors scope PD-295 (G34).
Idempotence clé {story_id,step,operation,timestamp_bucket_5min}.
Rate-limit 3 req / 5 min / story_id.
Réconciliation lock stale strict >60s, suppression + retry unique.
Clearing MEMORY_DEGRADED puis MEMORY_HEALTHY après 2 cycles conformes.
Non-répudiation HMAC symétrique: intégrité/authentification sur machine non compromise; non-répudiation forte hors scope (G33).

5.17 Guards obligatoires (préconditions fail-closed)

Guard ID Précondition Erreur
G-295-01 story_id conforme D-295-01 ERR-295-INVALID_STORY_ID
G-295-02 domain conforme D-295-02 ERR-295-INVALID_DOMAIN
G-295-03 project conforme D-295-03 ERR-295-INVALID_PROJECT
G-295-04 query et query_hash conformes ERR-295-INVALID_QUERY_LENGTH/QUERY_HASH
G-295-05 search-clarifications reçoit --domain + --project ERR-295-INVALID_DOMAIN/PROJECT
G-295-06 rate-limit admissible ERR-295-RATE_LIMIT_EXCEEDED
G-295-07 idempotence non conflictuelle ERR-295-IDEMPOTENCY_CONFLICT
G-295-08 lock acquis ERR-295-LOCK_TIMEOUT
G-295-09 clé Vault disponible et valide ERR-295-AUDIT_KEY_UNAVAILABLE/FORMAT_INVALID
G-295-10 trace signée écrite avec helper signé ERR-295-TRACE_WRITE_FAILED ou ERR-295-FAIL_CLOSED_RECURSION

5bis. Diagrammes

Diagramme d’état learning.scope

stateDiagram-v2
    [*] --> STORY_ACTIVE
    STORY_ACTIVE --> DOMAIN_ACTIVE: seuil domain
    STORY_ACTIVE --> ARCHIVED: stale
    DOMAIN_ACTIVE --> GLOBAL_ACTIVE: seuil global
    DOMAIN_ACTIVE --> ARCHIVED: stale
    GLOBAL_ACTIVE --> ARCHIVED: stale
    ARCHIVED --> STORY_ACTIVE: restore manuel audité

Diagramme d’état clarification.lifecycle_state

stateDiagram-v2
    [*] --> VERBATIM_IN_MEMORY
    VERBATIM_IN_MEMORY --> SUMMARY_PENDING_VALIDATION
    SUMMARY_PENDING_VALIDATION --> SUMMARY_VALIDATED: PO=oui
    SUMMARY_PENDING_VALIDATION --> REJECTED: PO=non
    SUMMARY_VALIDATED --> SUMMARY_INDEXED
    SUMMARY_INDEXED --> EXPIRED: date > retention_until
    EXPIRED --> PURGED

Diagramme de séquence B5 (arrêt /gov si clé indisponible)

sequenceDiagram
    participant S0 as /gov-step-0
    participant B5 as gov-learnings-inject
    participant V as Vault
    participant L as data/sessions/*.jsonl
    participant O as local ops log

    S0->>B5: inject(story_id,domain,project,query)
    B5->>B5: guard checks + locks + build sections
    B5->>V: read session key
    alt Vault/clé indisponible ou corrompue
        alt last_valid_key disponible en mémoire
            B5->>L: append signed audit_key_unavailable_fatal
        else aucune clé valide
            B5->>O: append marker UNSIGNED_DEGRADED
        end
        B5->>B5: SystemExit(1)
        Note over S0: aucun retour; processus /gov arrêté
    else clé disponible
        B5->>L: append signed event
        alt append OK
            B5-->>S0: injection block
        else append FAIL
            B5->>B5: fail_closed_depth++
            alt fail_closed_depth <= 3
                B5-->>S0: EMPTY_BLOCK + ERR-295-TRACE_WRITE_FAILED
            else fail_closed_depth == 4
                B5->>L: append signed fail_closed_recursion (ou UNSIGNED_DEGRADED)
                B5->>B5: SystemExit(1)
                Note over S0: aucun retour; processus /gov arrêté
            end
        end
    end

6. Cas d’erreur

ID Condition Réponse
ERR-295-INVALID_STORY_ID story_id invalide rejet immédiat
ERR-295-INVALID_DOMAIN domain invalide rejet immédiat
ERR-295-INVALID_PROJECT project invalide rejet immédiat
ERR-295-INVALID_QUERY_LENGTH query vide ou >500 rejet
ERR-295-INVALID_QUERY_HASH hash invalide rejet trace
ERR-295-RATE_LIMIT_EXCEEDED >3 req/5min rejet
ERR-295-IDEMPOTENCY_CONFLICT même clé, payload divergent rejet
ERR-295-LOCK_TIMEOUT lock non acquis 30s fail-closed
ERR-295-LOCK_STALE_RECOVERY_FAILED stale non récupérable fail-closed
ERR-295-AUDIT_KEY_UNAVAILABLE Vault indisponible au chargement session arrêt /gov
ERR-295-AUDIT_KEY_FORMAT_INVALID clé invalide arrêt /gov
ERR-295-HMAC_VERIFICATION_FAILED signature invalide ligne/trace rejetée
ERR-295-TRACE_WRITE_FAILED écriture trace impossible EMPTY_BLOCK
ERR-295-PII_DETECTED PII détectée rejet résumé
ERR-295-STATE_TRANSITION_FORBIDDEN transition interdite état inchangé
ERR-295-PURGE_VERIFICATION_FAILED purge incomplète alerte conformité
ERR-295-MEASURE_SCRIPT_MISSING script CS absent/non-x Gate 8 NON_CONFORME
ERR-295-UNSIGNED_ENTRY ligne état non signée/invalide compteur + événement signé, exécution continue
ERR-295-FAIL_CLOSED_RECURSION tentative 4e fail-closed imbriqué (depth=4) arrêt process + alerte sécurité
ERR-295-FAIL_CLOSED_DEPTH_EXCEEDED alias legacy non émis en v3 compat lecture historique

7. Critères d’acceptation

ID Critère Observable
CA-295-01 B1 index veille + filtres sorties B1 conformes
CA-295-02 B2 persiste uniquement résumé non-PII validé absence verbatim persistant
CA-295-03 Rejet PO n’écrit rien artefacts inchangés
CA-295-04 Purge RGPD 5 artefacts + traces query vérification 100%
CA-295-05 HMAC JCS valide (V1/V4/V6/V8) signatures exactes
CA-295-06 Vault KO ou clé invalide bloque /gov arrêt process explicite
CA-295-07 Pas de bloc non vide sans trace signée écrite trace KO => EMPTY_BLOCK
CA-295-08 count_configured fixe 5/3/3, count_effective borné, under_corpus exact comptage par section
CA-295-09 reuse_score formule + 4 décimales + écrêtage 0.9999 recalcul indépendant
CA-295-10 Promotion/éviction suivent §5.11 transitions conformes
CA-295-11 Transition interdite => erreur + état inchangé avant/après identique
CA-295-12 lock/idempotence/rate-limit/réconciliation/clearing opérationnels tests concurrence OK
CA-295-13 /morning affiche 3 top scores 3 lignes score=X.XXXX
CA-295-14 search-clarifications refuse sans --domain --project rejet explicite
CA-295-15 CA-295-meta (Gate 8): scripts measure-cs1..4.sh existent + test -x OK contrôle méta vert
CA-295-16 CS-1 mesuré T+30/T+60/T+90 (post-merge) rapports horodatés
CA-295-17 CS-2 mesuré T+90, cible >=30% (post-merge) rapport
CA-295-18 CS-3 mesuré 30j glissants (post-merge) rapport
CA-295-19 CS-4 mesuré sur 5 premières stories post-B5 (post-merge) rapport
CA-295-20 Aucun artefact DDL SQL inspection dépôt
CA-295-RUNTIME-01 Aucun marqueur verbatim ne fuite sur disque/session logs via subprocess B2 test UUID + grep
CA-295-STATE-01 Ligne injectée non signée ignorée au scoring/promotion/injection score inchangé + compteur unsigned
CA-295-BACKUP-01 Exclusion backup active sur artefacts PD-295 + script purge historique opérationnel tmutil isexcluded/journal script
CA-295-21 Hook on_story_close déclenché sur REJECTED/DONE_WITH_ANOMALY/DONE et idempotent purge appelée 3 états, sans double-effet
CA-295-22 Clé Vault immuable pendant la session même key_version sur session
CA-295-23 pii_ruleset_v1 validé DPO fichier + commit signé taggé dpo-approved
CA-295-24 Bloc {{LEARNINGS}} respecte le template canonique §5.9 regex ligne par ligne
CA-295-25 MEMORY_DEGRADED -> MEMORY_HEALTHY après 2 cycles conformes scénario E2E validé

8. Scénarios de test contractuels (résumé)

  • ST-295-01..26 conservés.
  • ST-295-27: arrêt process sur clé Vault invalide avec absence de retour au caller.
  • ST-295-28: hook on_story_close 3 états + idempotence.
  • ST-295-29: stabilité clé Vault sur session complète.
  • ST-295-30: attestation DPO pii_ruleset_v1.
  • ST-295-31: validation regex template {{LEARNINGS}}.
  • ST-295-32: transition E2E MEMORY_DEGRADED -> MEMORY_HEALTHY.

9. Hypothèses

ID Hypothèse Impact si faux
H-295-01 Vault kv/pd-295/memory-audit-hmac provisionné /gov bloqué
H-295-02 Schéma learnings-injections.jsonl #28 stable B3 invalide
H-295-03 Repos ProbatioVault lisibles corpus incomplet
H-295-04 Dérive horloge <=500ms idempotence/rate-limit fragiles
H-295-05 Purge traces session par story_id autorisée RGPD incomplet
H-295-06 Déploiement mono-hôte effectif lock non suffisant sinon (G34)

10. Points clarifiés (clos)

ID Décision
Q-295-01 Epic interne figée à tooling.
Q-295-02 Chemin de livraison figé: docs/epics/tooling/PD-295-memoire-vivante-5-briques/.
Q-295-05 pii_ruleset_v1 matérialisé en config/pii_ruleset_v1.yaml + validation DPO commit signé tag dpo-approved.
Q-295-06 Bloc {{LEARNINGS}} canonique figé en §5.9.

Références

  • Epic : tooling
  • JIRA : PD-295
  • Repos : ProbatioVault-ia-governance, ProbatioVault-doc, ProbatioVault-app, ProbatioVault-backend, ProbatioVault-infra, ProbatioVault-site
  • Besoin : docs/epics/tooling/PD-295-memoire-vivante-5-briques/PD-295-besoin.md
  • Archive cycle 1 : docs/epics/tooling/PD-295-memoire-vivante-5-briques/cycle-1/

Changelog cycle2-v2 → cycle2-v3

  • B-01 : borne fail_closed_depth alignée (0..4 ouvert à 4 pour abort)
  • B-02 : vecteurs HMAC V1/V4/V6/V8 recalculés avec schéma v3 {count_configured/effective/under_corpus}
  • B-03 : diagramme B5 aligné sur arrêt /gov (sys.exit)
  • M-01 : tmpfs interdit macOS, buffer mémoire Python exclusif
  • M-02 : architecture subprocess b2-sanitizer contractualisée §5.3.3
  • M-03 : hook on_story_close ancré §5.8
  • M-04 : scope fail_closed_depth thread-local par invocation §5.6.10
  • M-05 : clé Vault chargée une fois par session §5.2.4
  • M-06 : locks fcntl.flock étendus aux fichiers d'état signés
  • Q-295-05 : pii_ruleset_v1 attestation DPO via commit signé
  • Q-295-06 : template YAML bloc d'injection §5.9