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.
- shi_saash-0.3.0/MANIFEST.in +3 -0
- shi_saash-0.3.0/PKG-INFO +142 -0
- shi_saash-0.3.0/README.md +125 -0
- shi_saash-0.3.0/pyproject.toml +26 -0
- shi_saash-0.3.0/setup.cfg +4 -0
- shi_saash-0.3.0/src/shi/__init__.py +45 -0
- shi_saash-0.3.0/src/shi/__main__.py +4 -0
- shi_saash-0.3.0/src/shi/analysis/__init__.py +0 -0
- shi_saash-0.3.0/src/shi/analysis/code_analyzer.py +141 -0
- shi_saash-0.3.0/src/shi/api/__init__.py +0 -0
- shi_saash-0.3.0/src/shi/api/main.py +176 -0
- shi_saash-0.3.0/src/shi/artifacts/.keep +1 -0
- shi_saash-0.3.0/src/shi/artifacts/classifier/.keep +1 -0
- shi_saash-0.3.0/src/shi/artifacts/classifier/meta.json +1 -0
- shi_saash-0.3.0/src/shi/artifacts/classifier/weights.npz +0 -0
- shi_saash-0.3.0/src/shi/artifacts/transformer/.keep +1 -0
- shi_saash-0.3.0/src/shi/artifacts/transformer/meta.json +1 -0
- shi_saash-0.3.0/src/shi/artifacts/transformer/params.npz +0 -0
- shi_saash-0.3.0/src/shi/audit/__init__.py +0 -0
- shi_saash-0.3.0/src/shi/audit/log.py +60 -0
- shi_saash-0.3.0/src/shi/cli.py +186 -0
- shi_saash-0.3.0/src/shi/config.py +31 -0
- shi_saash-0.3.0/src/shi/datasets/test.jsonl +640 -0
- shi_saash-0.3.0/src/shi/datasets/test_aveugle.jsonl +60 -0
- shi_saash-0.3.0/src/shi/datasets/test_final.jsonl +60 -0
- shi_saash-0.3.0/src/shi/datasets/test_manuel.jsonl +20 -0
- shi_saash-0.3.0/src/shi/datasets/train.jsonl +2560 -0
- shi_saash-0.3.0/src/shi/datasets/val.jsonl +640 -0
- shi_saash-0.3.0/src/shi/demo_repo/.keep +1 -0
- shi_saash-0.3.0/src/shi/demo_repo/README.md +6 -0
- shi_saash-0.3.0/src/shi/demo_repo/cart.py +12 -0
- shi_saash-0.3.0/src/shi/demo_repo/pytest.ini +3 -0
- shi_saash-0.3.0/src/shi/demo_repo/tests/.keep +1 -0
- shi_saash-0.3.0/src/shi/demo_repo/tests/test_cart.py +19 -0
- shi_saash-0.3.0/src/shi/diagnosis/__init__.py +0 -0
- shi_saash-0.3.0/src/shi/diagnosis/advice.py +53 -0
- shi_saash-0.3.0/src/shi/diagnosis/engine.py +129 -0
- shi_saash-0.3.0/src/shi/diagnosis/service.py +43 -0
- shi_saash-0.3.0/src/shi/execution/__init__.py +0 -0
- shi_saash-0.3.0/src/shi/execution/pipeline.py +240 -0
- shi_saash-0.3.0/src/shi/ingestion/__init__.py +0 -0
- shi_saash-0.3.0/src/shi/ingestion/masking.py +67 -0
- shi_saash-0.3.0/src/shi/ingestion/normalize.py +80 -0
- shi_saash-0.3.0/src/shi/models/__init__.py +0 -0
- shi_saash-0.3.0/src/shi/models/classifier/__init__.py +11 -0
- shi_saash-0.3.0/src/shi/models/classifier/classifier.py +134 -0
- shi_saash-0.3.0/src/shi/models/classifier/corpus_extra.py +62 -0
- shi_saash-0.3.0/src/shi/models/classifier/corpus_libre.py +167 -0
- shi_saash-0.3.0/src/shi/models/classifier/dataset.py +159 -0
- shi_saash-0.3.0/src/shi/models/classifier/lexicon.py +58 -0
- shi_saash-0.3.0/src/shi/models/classifier/metrics.py +45 -0
- shi_saash-0.3.0/src/shi/models/classifier/tokenizer.py +84 -0
- shi_saash-0.3.0/src/shi/models/transformer/__init__.py +3 -0
- shi_saash-0.3.0/src/shi/models/transformer/mini_transformer.py +197 -0
- shi_saash-0.3.0/src/shi/planner/__init__.py +0 -0
- shi_saash-0.3.0/src/shi/planner/planner.py +35 -0
- shi_saash-0.3.0/src/shi/py.typed +0 -0
- shi_saash-0.3.0/src/shi/reports/__init__.py +0 -0
- shi_saash-0.3.0/src/shi/reports/report.py +52 -0
- shi_saash-0.3.0/src/shi/sandbox/__init__.py +0 -0
- shi_saash-0.3.0/src/shi/sandbox/runner.py +171 -0
- shi_saash-0.3.0/src/shi/security/__init__.py +0 -0
- shi_saash-0.3.0/src/shi/security/guard.py +125 -0
- shi_saash-0.3.0/src/shi/server.py +101 -0
- shi_saash-0.3.0/src/shi/solutions/__init__.py +0 -0
- shi_saash-0.3.0/src/shi/solutions/patches.py +143 -0
- shi_saash-0.3.0/src/shi/solutions/proposer.py +77 -0
- shi_saash-0.3.0/src/shi/utils.py +27 -0
- shi_saash-0.3.0/src/shi/verification/__init__.py +0 -0
- shi_saash-0.3.0/src/shi/verification/verify.py +41 -0
- shi_saash-0.3.0/src/shi_saash.egg-info/PKG-INFO +142 -0
- shi_saash-0.3.0/src/shi_saash.egg-info/SOURCES.txt +81 -0
- shi_saash-0.3.0/src/shi_saash.egg-info/dependency_links.txt +1 -0
- shi_saash-0.3.0/src/shi_saash.egg-info/entry_points.txt +3 -0
- shi_saash-0.3.0/src/shi_saash.egg-info/requires.txt +11 -0
- shi_saash-0.3.0/src/shi_saash.egg-info/top_level.txt +1 -0
- shi_saash-0.3.0/tests/test_api.py +66 -0
- shi_saash-0.3.0/tests/test_diagnose_service.py +103 -0
- shi_saash-0.3.0/tests/test_guard_audit.py +80 -0
- shi_saash-0.3.0/tests/test_masking_normalize.py +63 -0
- shi_saash-0.3.0/tests/test_models.py +106 -0
- shi_saash-0.3.0/tests/test_patches_analysis.py +84 -0
- shi_saash-0.3.0/tests/test_pipeline.py +166 -0
shi_saash-0.3.0/PKG-INFO
ADDED
|
@@ -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,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())
|
|
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
|