fix(dashboard,worker): vérité produit P0 — dashboard+worker+VWB export
Some checks failed
tests / Lint (ruff + black) (push) Failing after 1m46s
tests / Tests unitaires (sans GPU) (push) Failing after 2m0s
tests / Tests sécurité (critique) (push) Has been skipped

War-room clôture DGX 2026-06-18 (recadrage Dom : graphe/apprentissage/mémoire/dashboard = surface produit P0).
Le dashboard et le statut worker affichaient des états faux ; corrige pour refléter la vérité du produit.

- dashboard FAISS: distingue index brut / metadata HMAC invalide / runtime / absent (plus de faux "inactif")
- dashboard process-mining: 503 explicite missing_dependency (plus de message trompeur)
- dashboard /api/workflows + system/status: lecture DB VWB v3 canonique (total réel = 24, plus de 0)
- worker /processing/status: véridique (lit _worker_health.json) + statut "idle/armé (lazy)" distinct de "dégradé (échec)"
- VWB export: N steps -> N actions/edges (dernière action n'est plus perdue)
- tests: dashboard routes, worker status truthfulness, export VWB

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Dom
2026-06-18 17:50:12 +02:00
parent 6d5ef51c60
commit ec1fb81054
8 changed files with 626 additions and 56 deletions

View File

@@ -114,6 +114,37 @@ class TestDashboardRoutes:
'timeout': 30,
}]
def test_streaming_status_snapshot_aggregates_existing_endpoints(self, monkeypatch):
"""Le proxy legacy /status agrège les endpoints streaming reels."""
calls = []
def fake_fetch(endpoint, query_string=''):
calls.append((endpoint, query_string))
if endpoint == 'stats':
return {'active_sessions': 1, 'total_events': 7}
if endpoint == 'sessions':
return {'sessions': [{'session_id': 's1'}]}
if endpoint == 'processing/status':
return {'status': 'degraded', 'components_ready': False}
if endpoint == 'replays':
return {'replays': [{'replay_id': 'r1', 'active': True}]}
raise AssertionError(endpoint)
monkeypatch.setattr(dashboard_app, '_fetch_streaming_json', fake_fetch)
snapshot = dashboard_app._streaming_status_snapshot()
assert snapshot['active_sessions'] == 1
assert snapshot['sessions'] == [{'session_id': 's1'}]
assert snapshot['processing']['status'] == 'degraded'
assert snapshot['replay']['replay_id'] == 'r1'
assert calls == [
('stats', ''),
('sessions', ''),
('processing/status', ''),
('replays', ''),
]
def test_dashboard_submit_competence_verdict(self, client, monkeypatch):
"""Le dashboard journalise un verdict sans write-back YAML."""
import core.competences.verdicts as verdicts_module
@@ -212,6 +243,58 @@ class TestDashboardRoutes:
data = resp.get_json()
assert 'workflows' in data
def test_workflows_list_reads_vwb_db(self, client, monkeypatch, tmp_path):
"""Régression red-gate : /api/workflows reflète la base VWB v3, pas 0.
Avant correctif l'endpoint globait un store JSON vide et renvoyait
toujours total:0. On construit une DB VWB minimale (schéma canonique
workflows + steps) et on vérifie que l'endpoint expose le compte réel.
"""
import sqlite3
from pathlib import Path
db_path = tmp_path / "instance" / "workflows.db"
db_path.parent.mkdir(parents=True, exist_ok=True)
conn = sqlite3.connect(str(db_path))
conn.execute(
"CREATE TABLE workflows (id VARCHAR(64) PRIMARY KEY, name VARCHAR(255), "
"description TEXT, created_at DATETIME, updated_at DATETIME, "
"is_active BOOLEAN, source VARCHAR(64), review_status VARCHAR(32))"
)
conn.execute(
"CREATE TABLE steps (id VARCHAR(64) PRIMARY KEY, workflow_id VARCHAR(64), "
"action_type VARCHAR(64))"
)
conn.execute(
"INSERT INTO workflows VALUES (?,?,?,?,?,?,?,?)",
("wf_aiva", "Urgence_aiva_demo", "demo", "2026-06-01", "2026-06-18",
1, "manual", ""),
)
conn.execute(
"INSERT INTO workflows VALUES (?,?,?,?,?,?,?,?)",
("wf_learned", "Learned_flow", "", "2026-06-02", "2026-06-17",
1, "learned_import", "pending"),
)
# 3 steps pour wf_aiva → nodes_count attendu = 3
for i in range(3):
conn.execute(
"INSERT INTO steps VALUES (?,?,?)", (f"s{i}", "wf_aiva", "click")
)
conn.commit()
conn.close()
monkeypatch.setattr(dashboard_app, "VWB_DB_PATH", Path(db_path))
resp = client.get('/api/workflows')
assert resp.status_code == 200
data = resp.get_json()
assert data['total'] == 2, f"attendu 2 workflows, obtenu {data['total']}"
names = {w['name'] for w in data['workflows']}
assert 'Urgence_aiva_demo' in names
aiva = next(w for w in data['workflows'] if w['name'] == 'Urgence_aiva_demo')
assert aiva['nodes_count'] == 3
assert aiva['source'] == 'manual'
def test_sessions_list(self, client):
"""L'API sessions retourne la liste."""
resp = client.get('/api/agent/sessions')