phase1b: Backend entruempelt und robuster (35 tote Routen raus, Sperren, ehrliche Update-Pruefung)
Ampel / ampel (push) Successful in 26s

Ballast raus:
- 35 Routen ohne Nutzer entfernt (agent/*, fit, roles, ctx, drafts, groups, routing/policy,
  system/history, system/self-update, maintenance/reboot, zeitmaschine/inhalt, zeitplan,
  voice/health|metrics|trace|voices|reference|tts). Von 95 auf 60.
- Tote Module geloescht: agent-Router, roles, agent_aktivitaet, metrics_history (samt
  10-s-Sampler), voice_metrics, migrate_config, parse_mc2_timeout, scripts/.
- Unbenutzte Funktionen und Konstanten entfernt (Modell-Upgrade-Empfehlung, Draft-/Kontext-
  Setzer, Konsole, PC-Ausfuehrer-Probe, Routing-Policy-Editor ...).

Robuster:
- Jobs in eigener Prozessgruppe (Abbrechen beendet wirklich alles), Zeitlimit je Job-Art,
  start_job_exklusiv: zwei Klicks starten kein doppeltes Update mehr; alte Jobs raeumen sich auf.
- Update-Pruefung meldet Fehler (pruef_fehler, Lampe "Pruefung unklar") statt "aktuell".
- Nach jedem Update sofort neu pruefen (update_stand) statt 10 Minuten alten Stand zeigen.
- llama-swap-Config: Sperre (RLock + flock) fuer UI, Radar, Aufraeumen und Hirn-Umstellung.
- Hermes-Config: bei Lesefehler nichts schreiben, atomar, mit Sicherung.
- Live-Strom und Gateway-Warnung blockieren den Event-Loop nicht mehr (Lucy, OpenChamber).
- Gateway antwortet bei Engine-Ausfall im OpenAI-Fehlerformat (502) statt nacktem 500.
- Abgestuerzte Waechter-Pruefung wird ein gelber Hinweis statt still zu verschwinden.
- Download laedt nur den gewuenschten Quant (vorher bei Fehlen alle Teile aller Varianten),
  Download-Jobs in Gruppe "download"; HF-Suche kodiert den Suchbegriff.
- Herkunftspruefung: schreibende /api-Aufrufe fremder Webseiten werden abgelehnt (keine
  Anmeldung, User-Entscheid); Skripte, Desktop-Lucy und /v1 unveraendert.
- Modellpfade: Eintragen und Loeschen nur innerhalb von MODELS_DIR.
- Dienste-Liste fragt keine abgebauten Dienste mehr ab (PC-Ausfuehrer haette 3 s gekostet).
- SSE-Fehlerzeilen von /api/voice/chat als gueltiges JSON.
- mission-control-2.service: --timeout-graceful-shutdown 3 (Neustart ohne 10-s-Haenger).

Tests: 92 gruen (neu: Herkunft, Quant-Auswahl, abgestuerzte Pruefung).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Hitonabi
2026-09-24 15:08:44 +02:00
co-authored by Claude Opus 5.5
parent c8718fcde0
commit e9f488b56c
42 changed files with 799 additions and 2197 deletions
+23
View File
@@ -0,0 +1,23 @@
"""Herkunftsprüfung für Knöpfe (24.09.2026)."""
from services.herkunft import erlaubt
BOX = "192.168.178.151:9001"
def test_eigene_seite_und_skripte_duerfen():
assert erlaubt("POST", "/api/maintenance/os-update", "http://192.168.178.151:9001", BOX)
assert erlaubt("POST", "/api/alarm", None, BOX) # Lucy-Watchdog, curl
assert erlaubt("POST", "/api/voice/chat", "null", BOX) # Desktop-Lucy (Electron)
assert erlaubt("POST", "/api/voice/stt", "file://", BOX)
assert erlaubt("POST", "/api/system/restart", "http://localhost:5173", BOX) # Vite-Entwicklung
def test_fremde_webseite_darf_keine_knoepfe_druecken():
assert not erlaubt("POST", "/api/maintenance/update-all", "https://boese.example", BOX)
assert not erlaubt("DELETE", "/api/models/x", "http://192.168.178.99:8080", BOX)
def test_lesen_und_v1_bleiben_offen():
assert erlaubt("GET", "/api/start", "https://boese.example", BOX)
assert erlaubt("POST", "/v1/chat/completions", "http://localhost:3000", BOX)
+19
View File
@@ -756,3 +756,22 @@ def test_immer_warme_gruppe_verdraengt_nichts(monkeypatch):
assert cfg["models"]["h"]["ttl"] == 0
llamaswap.set_group("andere", ["h"], swap=True, persist=False)
assert "exclusive" not in cfg["groups"]["andere"]
def test_hf_download_nimmt_nur_den_gewuenschten_quant(monkeypatch):
"""24.09.2026: Fehlt der Quant, lädt der Download NICHTS (vorher: alle Teile aller Varianten)."""
from services import hf
baum = [
{"path": "Q8_0/Modell-Q8_0-00001-of-00002.gguf", "size": 50},
{"path": "Q8_0/Modell-Q8_0-00002-of-00002.gguf", "size": 50},
{"path": "Q4_K_M/Modell-Q4_K_M-00001-of-00002.gguf", "size": 30},
{"path": "Q4_K_M/Modell-Q4_K_M-00002-of-00002.gguf", "size": 30},
{"path": "mmproj-F16.gguf", "size": 5},
]
monkeypatch.setattr(hf, "_tree", lambda repo: baum)
leer = hf.resolve_gguf("x/y", "IQ3_XXS")
assert leer["first"] is None and leer["files"] == []
q4 = hf.resolve_gguf("x/y", "Q4_K_M")
assert q4["files"] == ["Q4_K_M/Modell-Q4_K_M-00001-of-00002.gguf", "Q4_K_M/Modell-Q4_K_M-00002-of-00002.gguf"]
assert q4["total_bytes"] == 65 and q4["mmproj"] == "mmproj-F16.gguf"
+19
View File
@@ -120,3 +120,22 @@ def test_schlafende_dienste_sind_kein_befund(monkeypatch):
assert "dienst:lucy-stimme" in ids
assert waechter.schlaeft(zustaende["voice-service"]) is True
assert waechter.schlaeft({"ActiveState": "failed", "UnitFileState": "disabled"}) is False
def test_abgestuerzte_pruefung_wird_ein_gelber_befund(monkeypatch, tmp_path):
"""24.09.2026: Eine Prüfung, die abstürzt, darf nicht still verschwinden (Cockpit blieb grün)."""
def kaputt():
raise ValueError("jobs.json hat ein neues Format")
kaputt.__name__ = "pruefe_hermes_jobs"
monkeypatch.setattr(waechter, "PRUEFUNGEN", (kaputt,))
monkeypatch.setattr(waechter, "STORE_PATH", tmp_path / "mc2-waechter.json")
monkeypatch.setattr(waechter, "_update_laeuft", lambda: False)
monkeypatch.setattr(waechter, "_telegram", lambda betreff, text: None)
monkeypatch.setattr(waechter, "FAIL_AFTER", 1)
monkeypatch.setattr(waechter, "_stand", {"hinweise": {}, "kandidaten": {}, "verlauf": [], "stand": None,
"update_laeuft": False})
waechter.takt()
hinweise = waechter._stand["hinweise"]
assert "pruefung:pruefe_hermes_jobs" in hinweise
assert "neues Format" in hinweise["pruefung:pruefe_hermes_jobs"]["text"]