shi-saash 0.3.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (83) hide show
  1. shi_saash-0.3.0/MANIFEST.in +3 -0
  2. shi_saash-0.3.0/PKG-INFO +142 -0
  3. shi_saash-0.3.0/README.md +125 -0
  4. shi_saash-0.3.0/pyproject.toml +26 -0
  5. shi_saash-0.3.0/setup.cfg +4 -0
  6. shi_saash-0.3.0/src/shi/__init__.py +45 -0
  7. shi_saash-0.3.0/src/shi/__main__.py +4 -0
  8. shi_saash-0.3.0/src/shi/analysis/__init__.py +0 -0
  9. shi_saash-0.3.0/src/shi/analysis/code_analyzer.py +141 -0
  10. shi_saash-0.3.0/src/shi/api/__init__.py +0 -0
  11. shi_saash-0.3.0/src/shi/api/main.py +176 -0
  12. shi_saash-0.3.0/src/shi/artifacts/.keep +1 -0
  13. shi_saash-0.3.0/src/shi/artifacts/classifier/.keep +1 -0
  14. shi_saash-0.3.0/src/shi/artifacts/classifier/meta.json +1 -0
  15. shi_saash-0.3.0/src/shi/artifacts/classifier/weights.npz +0 -0
  16. shi_saash-0.3.0/src/shi/artifacts/transformer/.keep +1 -0
  17. shi_saash-0.3.0/src/shi/artifacts/transformer/meta.json +1 -0
  18. shi_saash-0.3.0/src/shi/artifacts/transformer/params.npz +0 -0
  19. shi_saash-0.3.0/src/shi/audit/__init__.py +0 -0
  20. shi_saash-0.3.0/src/shi/audit/log.py +60 -0
  21. shi_saash-0.3.0/src/shi/cli.py +186 -0
  22. shi_saash-0.3.0/src/shi/config.py +31 -0
  23. shi_saash-0.3.0/src/shi/datasets/test.jsonl +640 -0
  24. shi_saash-0.3.0/src/shi/datasets/test_aveugle.jsonl +60 -0
  25. shi_saash-0.3.0/src/shi/datasets/test_final.jsonl +60 -0
  26. shi_saash-0.3.0/src/shi/datasets/test_manuel.jsonl +20 -0
  27. shi_saash-0.3.0/src/shi/datasets/train.jsonl +2560 -0
  28. shi_saash-0.3.0/src/shi/datasets/val.jsonl +640 -0
  29. shi_saash-0.3.0/src/shi/demo_repo/.keep +1 -0
  30. shi_saash-0.3.0/src/shi/demo_repo/README.md +6 -0
  31. shi_saash-0.3.0/src/shi/demo_repo/cart.py +12 -0
  32. shi_saash-0.3.0/src/shi/demo_repo/pytest.ini +3 -0
  33. shi_saash-0.3.0/src/shi/demo_repo/tests/.keep +1 -0
  34. shi_saash-0.3.0/src/shi/demo_repo/tests/test_cart.py +19 -0
  35. shi_saash-0.3.0/src/shi/diagnosis/__init__.py +0 -0
  36. shi_saash-0.3.0/src/shi/diagnosis/advice.py +53 -0
  37. shi_saash-0.3.0/src/shi/diagnosis/engine.py +129 -0
  38. shi_saash-0.3.0/src/shi/diagnosis/service.py +43 -0
  39. shi_saash-0.3.0/src/shi/execution/__init__.py +0 -0
  40. shi_saash-0.3.0/src/shi/execution/pipeline.py +240 -0
  41. shi_saash-0.3.0/src/shi/ingestion/__init__.py +0 -0
  42. shi_saash-0.3.0/src/shi/ingestion/masking.py +67 -0
  43. shi_saash-0.3.0/src/shi/ingestion/normalize.py +80 -0
  44. shi_saash-0.3.0/src/shi/models/__init__.py +0 -0
  45. shi_saash-0.3.0/src/shi/models/classifier/__init__.py +11 -0
  46. shi_saash-0.3.0/src/shi/models/classifier/classifier.py +134 -0
  47. shi_saash-0.3.0/src/shi/models/classifier/corpus_extra.py +62 -0
  48. shi_saash-0.3.0/src/shi/models/classifier/corpus_libre.py +167 -0
  49. shi_saash-0.3.0/src/shi/models/classifier/dataset.py +159 -0
  50. shi_saash-0.3.0/src/shi/models/classifier/lexicon.py +58 -0
  51. shi_saash-0.3.0/src/shi/models/classifier/metrics.py +45 -0
  52. shi_saash-0.3.0/src/shi/models/classifier/tokenizer.py +84 -0
  53. shi_saash-0.3.0/src/shi/models/transformer/__init__.py +3 -0
  54. shi_saash-0.3.0/src/shi/models/transformer/mini_transformer.py +197 -0
  55. shi_saash-0.3.0/src/shi/planner/__init__.py +0 -0
  56. shi_saash-0.3.0/src/shi/planner/planner.py +35 -0
  57. shi_saash-0.3.0/src/shi/py.typed +0 -0
  58. shi_saash-0.3.0/src/shi/reports/__init__.py +0 -0
  59. shi_saash-0.3.0/src/shi/reports/report.py +52 -0
  60. shi_saash-0.3.0/src/shi/sandbox/__init__.py +0 -0
  61. shi_saash-0.3.0/src/shi/sandbox/runner.py +171 -0
  62. shi_saash-0.3.0/src/shi/security/__init__.py +0 -0
  63. shi_saash-0.3.0/src/shi/security/guard.py +125 -0
  64. shi_saash-0.3.0/src/shi/server.py +101 -0
  65. shi_saash-0.3.0/src/shi/solutions/__init__.py +0 -0
  66. shi_saash-0.3.0/src/shi/solutions/patches.py +143 -0
  67. shi_saash-0.3.0/src/shi/solutions/proposer.py +77 -0
  68. shi_saash-0.3.0/src/shi/utils.py +27 -0
  69. shi_saash-0.3.0/src/shi/verification/__init__.py +0 -0
  70. shi_saash-0.3.0/src/shi/verification/verify.py +41 -0
  71. shi_saash-0.3.0/src/shi_saash.egg-info/PKG-INFO +142 -0
  72. shi_saash-0.3.0/src/shi_saash.egg-info/SOURCES.txt +81 -0
  73. shi_saash-0.3.0/src/shi_saash.egg-info/dependency_links.txt +1 -0
  74. shi_saash-0.3.0/src/shi_saash.egg-info/entry_points.txt +3 -0
  75. shi_saash-0.3.0/src/shi_saash.egg-info/requires.txt +11 -0
  76. shi_saash-0.3.0/src/shi_saash.egg-info/top_level.txt +1 -0
  77. shi_saash-0.3.0/tests/test_api.py +66 -0
  78. shi_saash-0.3.0/tests/test_diagnose_service.py +103 -0
  79. shi_saash-0.3.0/tests/test_guard_audit.py +80 -0
  80. shi_saash-0.3.0/tests/test_masking_normalize.py +63 -0
  81. shi_saash-0.3.0/tests/test_models.py +106 -0
  82. shi_saash-0.3.0/tests/test_patches_analysis.py +84 -0
  83. shi_saash-0.3.0/tests/test_pipeline.py +166 -0
@@ -0,0 +1,3 @@
1
+ recursive-include src/shi/artifacts *
2
+ recursive-include src/shi/datasets *.jsonl
3
+ recursive-include src/shi/demo_repo *
@@ -0,0 +1,142 @@
1
+ Metadata-Version: 2.4
2
+ Name: shi-saash
3
+ Version: 0.3.0
4
+ Summary: SHI (SaaS H Intelligence) : diagnostic d'incidents logiciels en conseil seulement, avec masquage des secrets, preuves et journal d'audit.
5
+ License: Proprietary
6
+ Requires-Python: >=3.9
7
+ Description-Content-Type: text/markdown
8
+ Requires-Dist: numpy>=1.24
9
+ Provides-Extra: api
10
+ Requires-Dist: fastapi>=0.110; extra == "api"
11
+ Requires-Dist: uvicorn>=0.27; extra == "api"
12
+ Provides-Extra: dev
13
+ Requires-Dist: pytest>=7.0; extra == "dev"
14
+ Requires-Dist: httpx>=0.26; extra == "dev"
15
+ Requires-Dist: fastapi>=0.110; extra == "dev"
16
+ Requires-Dist: uvicorn>=0.27; extra == "dev"
17
+
18
+ # shi-saash — SHI (SaaS H Intelligence), bibliothèque Python
19
+
20
+ ```bash
21
+ pip install shi-saash-0.3.0-py3-none-any.whl # ou, depuis les sources : pip install .
22
+ ```
23
+
24
+ ```python
25
+ import shi
26
+
27
+ r = shi.diagnose("Les clients ne peuvent plus payer", logs="ERROR stripe card_declined")
28
+ r["category"] # 'paiement'
29
+ r["uncertain"] # True si les preuves manquent
30
+ r["hypotheses"] # causes possibles, chacune avec ses preuves (ligne de journal)
31
+ r["advice"] # vérifications sûres, en lecture seule
32
+ r["masked"] # secrets masqués avant l'analyse
33
+
34
+ shi.classify("Le disque est plein").label # 'stockage'
35
+ ```
36
+
37
+ Ligne de commande : `shi classify "texte"`, `shi-server` (service HTTP, variable `SHI_API_KEY`), `python -m shi ...`.
38
+ Le modèle d'exemple est fourni dans le paquet. Pour utiliser votre propre modèle entraîné sans toucher au paquet :
39
+ `SHI_HOME=/mon/dossier shi train-classifier`.
40
+ `pip install "shi-saash[api]"` ajoute FastAPI pour l'API complète (`shi/api`).
41
+
42
+ **Ce que la bibliothèque ne fait pas** : elle n'exécute aucune commande pour `diagnose`/`classify`, et ne remplace pas
43
+ un humain. Voir « Limites connues » plus bas.
44
+
45
+ **Vérification** : la structure du paquet a été vérifiée en construisant une roue, en l'installant dans un
46
+ environnement propre puis en lançant les tests contre le paquet installé. Le fichier `pyproject.toml` n'a pas pu être
47
+ compilé dans l'environnement de génération (outil `setuptools` incompatible) : lancez `pip install .` chez vous et
48
+ signalez toute erreur.
49
+
50
+ ---
51
+
52
+ # SHI — SaaS H Intelligence (v0.1)
53
+
54
+ Implémentation de départ de l'architecture décrite dans le document technique SHI :
55
+ **observer → comprendre → diagnostiquer → proposer → tester → agir → vérifier**.
56
+
57
+ SHI n'est pas un chatbot : c'est une chaîne de composants (modèles d'IA minuscules + logiciels
58
+ classiques déterministes) où **le modèle ne lance jamais de commande**. Tout passe par une garde
59
+ de permissions, un exécuteur isolé et un journal d'audit.
60
+
61
+ ## Démarrage rapide
62
+
63
+ ```bash
64
+ pip install numpy # seul prérequis du cœur (OK sur Pydroid 3)
65
+ pip install pytest fastapi uvicorn httpx # optionnels : tests pytest + API
66
+
67
+ python -m shi.cli build-dataset # génère datasets/{train,val,test}.jsonl
68
+ python -m shi.cli train-classifier # SHI-0.2 : entraîne et évalue
69
+ python -m shi.cli classify "Les pages chargent très lentement"
70
+ python -m shi.cli train-transformer --steps 600 # SHI-0.3 : mini-Transformer
71
+ python -m shi.cli generate "Le serveur"
72
+ python -m shi.cli demo --approve # cycle complet sur le dépôt de démonstration
73
+ python -m shi.cli demo --auto # même cycle en mode automatique
74
+
75
+ python -m unittest discover -s tests -t . # ou : pytest
76
+ ```
77
+
78
+ API (nécessite FastAPI) :
79
+
80
+ ```bash
81
+ export SHI_API_KEY="une-longue-clé-secrète"
82
+ export SHI_ALLOWED_ROOTS="/chemin/vers/mes/dépôts"
83
+ python -m shi.cli serve
84
+ ```
85
+
86
+ ## Service de diagnostic (conseil seulement)
87
+
88
+ `shi-server` (variable `SHI_API_KEY`) démarre un petit serveur HTTP sans dépendance autre que NumPy :
89
+ `POST /diagnose` renvoie catégorie, hypothèses avec preuves et pistes de vérification sûres. Voir `DEPLOIEMENT-RENDER.md`.
90
+ Les 11 tests de `tests/test_diagnose_service.py` passent (serveur réel lancé et interrogé).
91
+
92
+ ## Ce qui est implémenté (phases du plan)
93
+
94
+ | Phase | Module | Contenu |
95
+ |---|---|---|
96
+ | 1-2 | `app/models/classifier` | SHI-0.2 (corpus manuel `corpus_libre.py`, `corpus_extra.py`, lexique `lexicon.py`, essais dans `experiments/`) : tokenizer français, TF-IDF (mots, bigrammes, 4-grammes), régression softmax NumPy + Adam, arrêt anticipé, métriques (précision, rappel, matrice de confusion), sauvegarde |
97
+ | 3 | `app/models/transformer` | SHI-0.3 : mini-Transformer caractère (attention causale, FFN, Adam), rétropropagation écrite à la main et **vérifiée par test de gradient numérique** |
98
+ | 4 | `app/ingestion` | Masquage des secrets/PII (clés, JWT, mots de passe, e-mails, cartes validées Luhn) **avant** analyse ; normalisation des logs en événements |
99
+ | 5 | `app/analysis`, `app/diagnosis`, `app/solutions`, `app/planner` | Faits vs hypothèses avec preuves ; analyse AST ; tracebacks → fichiers ; plusieurs solutions comparées (avantages, risques, score) ; correctifs candidats déterministes avec diff |
100
+ | 6 | `app/sandbox` | Copie temporaire, environnement épuré, limites CPU/mémoire/durée/fichiers, groupe de processus tué au délai, réseau coupé si `unshare` est disponible, liens symboliques ignorés, commande unique validée |
101
+ | 7 | `app/security`, `app/execution` | Mode manuel (diff → tests → approbation), garde de permissions, sauvegarde + retour arrière |
102
+ | 8 | `app/execution` | Mode automatique limité aux actions pré-autorisées ; s'arrête si diagnostic incertain, tests en échec ou action sensible |
103
+ | — | `app/audit`, `app/verification`, `app/reports` | Journal à chaînage de hachages ; verdict réussite / échec / incertain ; rapport Markdown |
104
+ | — | `app/api` | Endpoints du document (+ `/auto`, `/rollback`, `/api/audit/verify`), clé d'API, dépôts limités aux dossiers autorisés |
105
+
106
+ Correctifs automatiques disponibles : argument par défaut mutable, `except:` nu, `== None`,
107
+ appels `requests` sans `timeout`.
108
+
109
+ ## Règles de sécurité appliquées
110
+
111
+ - Lecture seule par défaut ; `APPLY_FIX` doit être accordé au projet. Une approbation humaine ne crée jamais une permission absente.
112
+ - Aucune modification du dépôt réel sans tests isolés réussis ; sauvegarde avant, vérification après, retour arrière automatique si la vérification échoue.
113
+ - Une action destructive ne peut jamais être automatique.
114
+ - Le contenu du dépôt et des logs est **non fiable** : les instructions suspectes (README, commentaires, logs) sont signalées dans le rapport, jamais obéies.
115
+ - Un fichier modifié depuis l'analyse invalide le correctif (empreinte SHA-256).
116
+ - Le rapport ne déclare « résolu » que si les tests prouvent la correction ; un bug non reproduit donne « incertain ».
117
+
118
+ ## Limites connues (à lire)
119
+
120
+ - **Précision du classificateur (v0.2.1)** : entraîné sur environ 420 phrases rédigées à la main, 12 catégories (10 d'incidents + `securite` + `inconnu` pour refuser les phrases hors sujet). Exactitude de **0,88** sur 60 phrases écrites à part et jamais utilisées pour entraîner ni régler (`datasets/test_final.jsonl`, 5 par catégorie). Précautions : un seul auteur et un seul style ; 60 phrases donnent une incertitude d'environ ±8 points ; ce n'est **pas** une mesure sur de vrais incidents. Historique mesuré : gabarits seuls 0,39 sur des phrases libres ; ajouter 264 phrases libres 0,51 ; ajouter 180 phrases + 60 phrases de développement 0,88. **La quantité et la variété des données comptent bien plus que le réglage du modèle** (régularisation, taux d'apprentissage, lexique de mots-clés : aucun gain sur le test final, le lexique `lexicon.py` est donc désactivé par défaut). Pour progresser : de vrais incidents anonymisés, de plusieurs auteurs.
121
+ - **Mini-Transformer** : modèle minuscule (un bloc, caractères). Il génère du texte plausible mais approximatif ; ce n'est pas un réparateur.
122
+ - **Isolation** : ce n'est pas un conteneur. Pour du code réellement hostile ou en production, exécuter le runner dans un conteneur/VM dédié (Docker rootless, gVisor, Firecracker) sans accès à la production. Le niveau d'isolation réellement obtenu (réseau coupé ou non) est écrit dans chaque résultat de test.
123
+ - **Non réalisé** : connecteurs GitHub/logs réels, PostgreSQL, file Redis/Celery, interface Android, déploiement. Le stockage de l'API est en mémoire.
124
+ - **Tests** : la suite complète passe en `unittest` (49 tests exécutés). Les 5 tests d'API sont ignorés si FastAPI/httpx sont absents ; l'API n'a donc pas été exécutée dans l'environnement de génération et doit être testée chez vous (`pip install fastapi httpx`, puis relancer les tests).
125
+ - Les corrections ne sont validées que par les tests du dépôt : des tests faibles donnent une validation faible.
126
+
127
+ ## Organisation
128
+
129
+ ```
130
+ app/ cœur de SHI (voir tableau)
131
+ datasets/ train/val/test.jsonl (générés) + test_manuel.jsonl (phrases libres)
132
+ demo_repo/ dépôt de démonstration avec un vrai bug (argument par défaut mutable)
133
+ artifacts/ modèles entraînés (classifier/, transformer/)
134
+ tests/ suite de tests
135
+ ```
136
+
137
+ ## Étapes suivantes
138
+
139
+ 1. Remplacer le jeu synthétique par de vrais incidents anonymisés ; refaire l'évaluation sur un jeu de test jamais utilisé.
140
+ 2. Connecteur GitHub en lecture (jetons à portée limitée), puis logs/métriques.
141
+ 3. Runner en conteneur ; PostgreSQL ; file de tâches ; revue de sécurité avant toute action en production.
142
+ 4. Application Android : écrans incidents, solutions, diff, approbation (elle appelle l'API ; aucun secret en clair sur le téléphone).
@@ -0,0 +1,125 @@
1
+ # shi-saash — SHI (SaaS H Intelligence), bibliothèque Python
2
+
3
+ ```bash
4
+ pip install shi-saash-0.3.0-py3-none-any.whl # ou, depuis les sources : pip install .
5
+ ```
6
+
7
+ ```python
8
+ import shi
9
+
10
+ r = shi.diagnose("Les clients ne peuvent plus payer", logs="ERROR stripe card_declined")
11
+ r["category"] # 'paiement'
12
+ r["uncertain"] # True si les preuves manquent
13
+ r["hypotheses"] # causes possibles, chacune avec ses preuves (ligne de journal)
14
+ r["advice"] # vérifications sûres, en lecture seule
15
+ r["masked"] # secrets masqués avant l'analyse
16
+
17
+ shi.classify("Le disque est plein").label # 'stockage'
18
+ ```
19
+
20
+ Ligne de commande : `shi classify "texte"`, `shi-server` (service HTTP, variable `SHI_API_KEY`), `python -m shi ...`.
21
+ Le modèle d'exemple est fourni dans le paquet. Pour utiliser votre propre modèle entraîné sans toucher au paquet :
22
+ `SHI_HOME=/mon/dossier shi train-classifier`.
23
+ `pip install "shi-saash[api]"` ajoute FastAPI pour l'API complète (`shi/api`).
24
+
25
+ **Ce que la bibliothèque ne fait pas** : elle n'exécute aucune commande pour `diagnose`/`classify`, et ne remplace pas
26
+ un humain. Voir « Limites connues » plus bas.
27
+
28
+ **Vérification** : la structure du paquet a été vérifiée en construisant une roue, en l'installant dans un
29
+ environnement propre puis en lançant les tests contre le paquet installé. Le fichier `pyproject.toml` n'a pas pu être
30
+ compilé dans l'environnement de génération (outil `setuptools` incompatible) : lancez `pip install .` chez vous et
31
+ signalez toute erreur.
32
+
33
+ ---
34
+
35
+ # SHI — SaaS H Intelligence (v0.1)
36
+
37
+ Implémentation de départ de l'architecture décrite dans le document technique SHI :
38
+ **observer → comprendre → diagnostiquer → proposer → tester → agir → vérifier**.
39
+
40
+ SHI n'est pas un chatbot : c'est une chaîne de composants (modèles d'IA minuscules + logiciels
41
+ classiques déterministes) où **le modèle ne lance jamais de commande**. Tout passe par une garde
42
+ de permissions, un exécuteur isolé et un journal d'audit.
43
+
44
+ ## Démarrage rapide
45
+
46
+ ```bash
47
+ pip install numpy # seul prérequis du cœur (OK sur Pydroid 3)
48
+ pip install pytest fastapi uvicorn httpx # optionnels : tests pytest + API
49
+
50
+ python -m shi.cli build-dataset # génère datasets/{train,val,test}.jsonl
51
+ python -m shi.cli train-classifier # SHI-0.2 : entraîne et évalue
52
+ python -m shi.cli classify "Les pages chargent très lentement"
53
+ python -m shi.cli train-transformer --steps 600 # SHI-0.3 : mini-Transformer
54
+ python -m shi.cli generate "Le serveur"
55
+ python -m shi.cli demo --approve # cycle complet sur le dépôt de démonstration
56
+ python -m shi.cli demo --auto # même cycle en mode automatique
57
+
58
+ python -m unittest discover -s tests -t . # ou : pytest
59
+ ```
60
+
61
+ API (nécessite FastAPI) :
62
+
63
+ ```bash
64
+ export SHI_API_KEY="une-longue-clé-secrète"
65
+ export SHI_ALLOWED_ROOTS="/chemin/vers/mes/dépôts"
66
+ python -m shi.cli serve
67
+ ```
68
+
69
+ ## Service de diagnostic (conseil seulement)
70
+
71
+ `shi-server` (variable `SHI_API_KEY`) démarre un petit serveur HTTP sans dépendance autre que NumPy :
72
+ `POST /diagnose` renvoie catégorie, hypothèses avec preuves et pistes de vérification sûres. Voir `DEPLOIEMENT-RENDER.md`.
73
+ Les 11 tests de `tests/test_diagnose_service.py` passent (serveur réel lancé et interrogé).
74
+
75
+ ## Ce qui est implémenté (phases du plan)
76
+
77
+ | Phase | Module | Contenu |
78
+ |---|---|---|
79
+ | 1-2 | `app/models/classifier` | SHI-0.2 (corpus manuel `corpus_libre.py`, `corpus_extra.py`, lexique `lexicon.py`, essais dans `experiments/`) : tokenizer français, TF-IDF (mots, bigrammes, 4-grammes), régression softmax NumPy + Adam, arrêt anticipé, métriques (précision, rappel, matrice de confusion), sauvegarde |
80
+ | 3 | `app/models/transformer` | SHI-0.3 : mini-Transformer caractère (attention causale, FFN, Adam), rétropropagation écrite à la main et **vérifiée par test de gradient numérique** |
81
+ | 4 | `app/ingestion` | Masquage des secrets/PII (clés, JWT, mots de passe, e-mails, cartes validées Luhn) **avant** analyse ; normalisation des logs en événements |
82
+ | 5 | `app/analysis`, `app/diagnosis`, `app/solutions`, `app/planner` | Faits vs hypothèses avec preuves ; analyse AST ; tracebacks → fichiers ; plusieurs solutions comparées (avantages, risques, score) ; correctifs candidats déterministes avec diff |
83
+ | 6 | `app/sandbox` | Copie temporaire, environnement épuré, limites CPU/mémoire/durée/fichiers, groupe de processus tué au délai, réseau coupé si `unshare` est disponible, liens symboliques ignorés, commande unique validée |
84
+ | 7 | `app/security`, `app/execution` | Mode manuel (diff → tests → approbation), garde de permissions, sauvegarde + retour arrière |
85
+ | 8 | `app/execution` | Mode automatique limité aux actions pré-autorisées ; s'arrête si diagnostic incertain, tests en échec ou action sensible |
86
+ | — | `app/audit`, `app/verification`, `app/reports` | Journal à chaînage de hachages ; verdict réussite / échec / incertain ; rapport Markdown |
87
+ | — | `app/api` | Endpoints du document (+ `/auto`, `/rollback`, `/api/audit/verify`), clé d'API, dépôts limités aux dossiers autorisés |
88
+
89
+ Correctifs automatiques disponibles : argument par défaut mutable, `except:` nu, `== None`,
90
+ appels `requests` sans `timeout`.
91
+
92
+ ## Règles de sécurité appliquées
93
+
94
+ - Lecture seule par défaut ; `APPLY_FIX` doit être accordé au projet. Une approbation humaine ne crée jamais une permission absente.
95
+ - Aucune modification du dépôt réel sans tests isolés réussis ; sauvegarde avant, vérification après, retour arrière automatique si la vérification échoue.
96
+ - Une action destructive ne peut jamais être automatique.
97
+ - Le contenu du dépôt et des logs est **non fiable** : les instructions suspectes (README, commentaires, logs) sont signalées dans le rapport, jamais obéies.
98
+ - Un fichier modifié depuis l'analyse invalide le correctif (empreinte SHA-256).
99
+ - Le rapport ne déclare « résolu » que si les tests prouvent la correction ; un bug non reproduit donne « incertain ».
100
+
101
+ ## Limites connues (à lire)
102
+
103
+ - **Précision du classificateur (v0.2.1)** : entraîné sur environ 420 phrases rédigées à la main, 12 catégories (10 d'incidents + `securite` + `inconnu` pour refuser les phrases hors sujet). Exactitude de **0,88** sur 60 phrases écrites à part et jamais utilisées pour entraîner ni régler (`datasets/test_final.jsonl`, 5 par catégorie). Précautions : un seul auteur et un seul style ; 60 phrases donnent une incertitude d'environ ±8 points ; ce n'est **pas** une mesure sur de vrais incidents. Historique mesuré : gabarits seuls 0,39 sur des phrases libres ; ajouter 264 phrases libres 0,51 ; ajouter 180 phrases + 60 phrases de développement 0,88. **La quantité et la variété des données comptent bien plus que le réglage du modèle** (régularisation, taux d'apprentissage, lexique de mots-clés : aucun gain sur le test final, le lexique `lexicon.py` est donc désactivé par défaut). Pour progresser : de vrais incidents anonymisés, de plusieurs auteurs.
104
+ - **Mini-Transformer** : modèle minuscule (un bloc, caractères). Il génère du texte plausible mais approximatif ; ce n'est pas un réparateur.
105
+ - **Isolation** : ce n'est pas un conteneur. Pour du code réellement hostile ou en production, exécuter le runner dans un conteneur/VM dédié (Docker rootless, gVisor, Firecracker) sans accès à la production. Le niveau d'isolation réellement obtenu (réseau coupé ou non) est écrit dans chaque résultat de test.
106
+ - **Non réalisé** : connecteurs GitHub/logs réels, PostgreSQL, file Redis/Celery, interface Android, déploiement. Le stockage de l'API est en mémoire.
107
+ - **Tests** : la suite complète passe en `unittest` (49 tests exécutés). Les 5 tests d'API sont ignorés si FastAPI/httpx sont absents ; l'API n'a donc pas été exécutée dans l'environnement de génération et doit être testée chez vous (`pip install fastapi httpx`, puis relancer les tests).
108
+ - Les corrections ne sont validées que par les tests du dépôt : des tests faibles donnent une validation faible.
109
+
110
+ ## Organisation
111
+
112
+ ```
113
+ app/ cœur de SHI (voir tableau)
114
+ datasets/ train/val/test.jsonl (générés) + test_manuel.jsonl (phrases libres)
115
+ demo_repo/ dépôt de démonstration avec un vrai bug (argument par défaut mutable)
116
+ artifacts/ modèles entraînés (classifier/, transformer/)
117
+ tests/ suite de tests
118
+ ```
119
+
120
+ ## Étapes suivantes
121
+
122
+ 1. Remplacer le jeu synthétique par de vrais incidents anonymisés ; refaire l'évaluation sur un jeu de test jamais utilisé.
123
+ 2. Connecteur GitHub en lecture (jetons à portée limitée), puis logs/métriques.
124
+ 3. Runner en conteneur ; PostgreSQL ; file de tâches ; revue de sécurité avant toute action en production.
125
+ 4. Application Android : écrans incidents, solutions, diff, approbation (elle appelle l'API ; aucun secret en clair sur le téléphone).
@@ -0,0 +1,26 @@
1
+ [build-system]
2
+ requires = ["setuptools>=64"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "shi-saash"
7
+ version = "0.3.0"
8
+ description = "SHI (SaaS H Intelligence) : diagnostic d'incidents logiciels en conseil seulement, avec masquage des secrets, preuves et journal d'audit."
9
+ readme = "README.md"
10
+ requires-python = ">=3.9"
11
+ license = { text = "Proprietary" }
12
+ dependencies = ["numpy>=1.24"]
13
+
14
+ [project.optional-dependencies]
15
+ api = ["fastapi>=0.110", "uvicorn>=0.27"]
16
+ dev = ["pytest>=7.0", "httpx>=0.26", "fastapi>=0.110", "uvicorn>=0.27"]
17
+
18
+ [project.scripts]
19
+ shi = "shi.cli:main"
20
+ shi-server = "shi.server:main"
21
+
22
+ [tool.setuptools.packages.find]
23
+ where = ["src"]
24
+
25
+ [tool.setuptools.package-data]
26
+ shi = ["py.typed", "artifacts/**/*", "datasets/*.jsonl", "demo_repo/**/*"]
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,45 @@
1
+ """SHI — SaaS H Intelligence : diagnostic d'incidents logiciels, en conseil seulement.
2
+
3
+ Utilisation rapide ::
4
+
5
+ import shi
6
+ r = shi.diagnose("Les clients ne peuvent plus payer", logs="ERROR stripe card_declined")
7
+ print(r["category"], r["uncertain"])
8
+ for h in r["hypotheses"]: print(h["label"], h["confidence"], h["evidence"])
9
+ print(r["advice"])
10
+
11
+ La bibliothèque n'exécute aucune commande et n'écrit rien sur votre système par défaut.
12
+ Les secrets présents dans le texte sont masqués avant l'analyse.
13
+ """
14
+ from __future__ import annotations
15
+
16
+ from typing import Any, Dict, Optional
17
+
18
+ __version__ = "0.3.0"
19
+ __all__ = ["diagnose", "classify", "load_classifier", "SHIClassifier", "__version__"]
20
+
21
+ from shi.models.classifier import SHIClassifier, load_default_classifier # noqa: E402
22
+
23
+ _cache: Dict[str, Optional[SHIClassifier]] = {}
24
+
25
+
26
+ def load_classifier() -> Optional[SHIClassifier]:
27
+ """Charge (une seule fois) le classificateur livré avec le paquet, ou celui de SHI_HOME."""
28
+ if "clf" not in _cache:
29
+ _cache["clf"] = load_default_classifier()
30
+ return _cache["clf"]
31
+
32
+
33
+ def classify(text: str):
34
+ """Catégorie probable d'une description : objet avec label, confidence, probs, uncertain."""
35
+ clf = load_classifier()
36
+ if clf is None:
37
+ raise RuntimeError("Aucun modèle disponible : lancez « python -m shi.cli train-classifier ».")
38
+ return clf.predict(text)
39
+
40
+
41
+ def diagnose(description: str, logs: str = "") -> Dict[str, Any]:
42
+ """Diagnostic en conseil seulement : catégorie, hypothèses avec preuves, vérifications sûres."""
43
+ from shi.diagnosis.service import diagnose_text
44
+
45
+ return diagnose_text(description, logs, load_classifier())
@@ -0,0 +1,4 @@
1
+ from shi.cli import main
2
+
3
+ if __name__ == "__main__":
4
+ main()
File without changes
@@ -0,0 +1,141 @@
1
+ """Analyse de code : symboles, zones citées par les tracebacks, signaux statiques.
2
+
3
+ La syntaxe ne prouve pas la cause d'un incident : ces résultats sont des indices.
4
+ """
5
+ from __future__ import annotations
6
+
7
+ import ast
8
+ import os
9
+ import re
10
+ from dataclasses import dataclass, field
11
+ from pathlib import Path
12
+ from typing import Dict, List, Tuple
13
+
14
+ from shi.config import IGNORE_DIRS
15
+ from shi.security.guard import scan_untrusted
16
+
17
+
18
+ @dataclass
19
+ class Symbol:
20
+ kind: str
21
+ name: str
22
+ lineno: int
23
+
24
+
25
+ @dataclass
26
+ class Finding:
27
+ rule: str
28
+ lineno: int
29
+ message: str
30
+
31
+
32
+ @dataclass
33
+ class Frame:
34
+ file: str
35
+ line: int
36
+ func: str
37
+
38
+
39
+ _FRAME = re.compile(r'File "(?P<f>[^"]+)", line (?P<l>\d+)(?:, in (?P<fn>[\w<>]+))?')
40
+ _REQUESTS = {"get", "post", "put", "delete", "patch", "head", "request"}
41
+
42
+
43
+ def parse_tracebacks(text: str) -> List[Frame]:
44
+ return [Frame(m.group("f"), int(m.group("l")), m.group("fn") or "") for m in _FRAME.finditer(text)]
45
+
46
+
47
+ def _is_mutable_default(d) -> bool:
48
+ if isinstance(d, (ast.List, ast.Dict, ast.Set)):
49
+ return True
50
+ return (isinstance(d, ast.Call) and isinstance(d.func, ast.Name) and d.func.id in {"list", "dict", "set"}
51
+ and not d.args and not d.keywords)
52
+
53
+
54
+ def analyze_source(source: str) -> Tuple[List[Symbol], List[Finding]]:
55
+ try:
56
+ tree = ast.parse(source)
57
+ except SyntaxError as exc:
58
+ return [], [Finding("syntax_error", exc.lineno or 0, "erreur de syntaxe : %s" % exc.msg)]
59
+ symbols: List[Symbol] = []
60
+ findings: List[Finding] = []
61
+ for node in ast.walk(tree):
62
+ if isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef)):
63
+ symbols.append(Symbol("fonction", node.name, node.lineno))
64
+ a = node.args
65
+ defaults = list(a.defaults) + [d for d in a.kw_defaults if d is not None]
66
+ if any(_is_mutable_default(d) for d in defaults):
67
+ findings.append(Finding("mutable_default", node.lineno,
68
+ "argument par défaut mutable dans %s() : l'objet est partagé entre les appels" % node.name))
69
+ elif isinstance(node, ast.ClassDef):
70
+ symbols.append(Symbol("classe", node.name, node.lineno))
71
+ elif isinstance(node, ast.ExceptHandler) and node.type is None:
72
+ findings.append(Finding("bare_except", node.lineno, "« except: » nu : masque toutes les erreurs"))
73
+ elif isinstance(node, ast.Compare):
74
+ if any(isinstance(o, (ast.Eq, ast.NotEq)) for o in node.ops) and any(
75
+ isinstance(c, ast.Constant) and c.value is None for c in node.comparators
76
+ ):
77
+ findings.append(Finding("eq_none", node.lineno, "comparaison « == None » (préférer « is None »)"))
78
+ elif isinstance(node, ast.Call):
79
+ f = node.func
80
+ if (isinstance(f, ast.Attribute) and isinstance(f.value, ast.Name) and f.value.id == "requests"
81
+ and f.attr in _REQUESTS and not any(k.arg in ("timeout", None) for k in node.keywords)):
82
+ findings.append(Finding("requests_no_timeout", node.lineno, "appel requests sans délai d'attente (timeout)"))
83
+ elif isinstance(f, ast.Name) and f.id in ("eval", "exec"):
84
+ findings.append(Finding("eval_exec", node.lineno, "usage de %s() : risque d'exécution de code arbitraire" % f.id))
85
+ return symbols, findings
86
+
87
+
88
+ @dataclass
89
+ class RepoScan:
90
+ files: Dict[str, str] = field(default_factory=dict) # sources .py analysables (hors tests)
91
+ findings: Dict[str, List[Finding]] = field(default_factory=dict)
92
+ symbols: Dict[str, List[Symbol]] = field(default_factory=dict)
93
+ suspicious: List[dict] = field(default_factory=list) # instructions suspectes (informatif)
94
+
95
+
96
+ _TEXT_SUFFIXES = {".py", ".md", ".txt", ".log", ".cfg", ".ini", ".toml", ".yml", ".yaml", ".json"}
97
+
98
+
99
+ def _is_test(rel: str) -> bool:
100
+ name = os.path.basename(rel)
101
+ return rel.startswith("tests/") or "/tests/" in rel or name.startswith("test_") or name.endswith("_test.py") or name == "conftest.py"
102
+
103
+
104
+ def scan_repo(root, max_files: int = 300, max_bytes: int = 200_000) -> RepoScan:
105
+ """Lit le dépôt SANS suivre les liens symboliques ; le contenu est non fiable."""
106
+ root = Path(root).resolve()
107
+ scan = RepoScan()
108
+ count = 0
109
+ for dirpath, dirs, files in os.walk(root, followlinks=False):
110
+ dirs[:] = sorted(d for d in dirs if d not in IGNORE_DIRS and not os.path.islink(os.path.join(dirpath, d)))
111
+ for name in sorted(files):
112
+ full = os.path.join(dirpath, name)
113
+ if os.path.islink(full) or Path(name).suffix not in _TEXT_SUFFIXES:
114
+ continue
115
+ if os.path.getsize(full) > max_bytes:
116
+ continue
117
+ count += 1
118
+ if count > max_files:
119
+ return scan
120
+ rel = os.path.relpath(full, root).replace(os.sep, "/")
121
+ text = Path(full).read_text(encoding="utf-8", errors="replace")
122
+ scan.suspicious += scan_untrusted(text, rel)
123
+ if name.endswith(".py") and not _is_test(rel):
124
+ scan.files[rel] = text
125
+ syms, finds = analyze_source(text)
126
+ scan.symbols[rel] = syms
127
+ if finds:
128
+ scan.findings[rel] = finds
129
+ return scan
130
+
131
+
132
+ def match_frames(frames: List[Frame], known_files) -> List[Tuple[str, int, str]]:
133
+ """Associe les fichiers cités dans un traceback aux fichiers du dépôt (par suffixe)."""
134
+ out = []
135
+ for fr in frames:
136
+ path = fr.file.replace("\\", "/")
137
+ for rel in known_files:
138
+ if path == rel or path.endswith("/" + rel):
139
+ out.append((rel, fr.line, fr.func))
140
+ break
141
+ return out
File without changes