feat(agent_v1): helpers logging PII-safe (push-log-DGX, brique 4)

Module agent_v1/core/log_safe.py — 3 helpers purs pour assainir les logs
client à la source : _title_hash (SHA1[:8], corrélation sans révéler),
_sanitize_metadata (drop title/active_window/window_title), _path_ext
(extension seule). 6 tests unitaires verts. Module inerte (non encore wired) ;
le branchement dans le code runtime suit en étape supervisée.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Dom
2026-06-27 11:24:54 +02:00
parent 2597ca9110
commit 4a38000e74
2 changed files with 121 additions and 0 deletions

View File

@@ -0,0 +1,48 @@
"""Helpers de logging PII-safe pour le client Léa (agent_v1).
Convention : ne jamais logger le contenu brut d'une variable utilisateur
(texte tapé, titre de fenêtre, nom de workflow, réponse VLM, chemin fichier).
Le remplacer par :
- une longueur ou un hash court (corrélation de diagnostic sans révéler) ;
- un dict de métadonnées filtré (sans titre / fenêtre active).
À importer dans tout module d'agent_v1 qui logge une donnée potentiellement
sensible. Branche feat/push-log-dgx — DETTE-020 (assainissement à la source).
"""
from __future__ import annotations
import hashlib
import os
def _title_hash(title: str) -> str:
"""Hash SHA1 tronqué (8 hex) d'un titre.
Corrélation stable (même titre → même hash → « même popup re-détectée »)
sans exposer le contenu. `errors="replace"` pour ne jamais lever sur un
encodage exotique (titres Windows multi-langues).
"""
return hashlib.sha1((title or "").encode("utf-8", errors="replace")).hexdigest()[:8]
# Clés de métadonnées susceptibles de contenir du contenu utilisateur (PII).
_PII_METADATA_KEYS = ("title", "active_window", "window_title")
def _sanitize_metadata(metadata: dict) -> dict:
"""Copie d'un dict de métadonnées sans les clés porteuses de PII.
Garde les champs techniques (resolution, dpi, theme, langue…), retire
titre / fenêtre active. Ne mute pas le dict d'origine.
"""
return {k: v for k, v in metadata.items() if k not in _PII_METADATA_KEYS}
def _path_ext(path: str) -> str:
"""Extension seule d'un chemin (ex. « .png »), sans nom ni dossier.
Un chemin peut nommer un patient ; l'extension suffit au diagnostic.
Chaîne vide si pas de chemin ou pas d'extension.
"""
return os.path.splitext(path)[1] if path else ""