- services/lucy_autonomie.py: Stufe je Aktionsart (nur lesen, erst fragen, selbst handeln), Start „mehr Freiheit“;
Meldung danach (POST /api/lucy/gehandelt)
- services/rueckfragen.py: nummerierte Rückfragen, nur Knöpfe der Oberfläche (ERLAUBT), „ja“ führt genau die
gespeicherte Aktion aus, 24 h gültig, hinfällig wenn der Hinweis weg ist, Nachprüfung danach
- notify.sh -k: Antworttasten (Antworttastatur über den Direktweg, ohne Eingriff in Hermes)
- services/ermittlung.py: Wächter meldet rote Hinweise an Lucy; Knopf wird Rückfrage, Lucy sieht über den
api_server nach und schickt Ursache und Weg als eine Nachricht
- services/wochenbericht.py + mc2-wochenbericht.timer (So 07:30): feste Sätze, Absatz vom Modell, offene
App-Updates als Rückfragen
- MCP: Werkzeuge fragen die Freiheiten (auf_bitte), neu: freiheiten, rueckfragen, rueckfrage_beantworten,
sicherheitsupdates_einspielen, snapshot_zurueckspielen, snapshots, geraet_logs, dienst_logs
- Ausführer-Aktion journal (nur lesend), POST /api/homelab/ziele/{id}/sicherheitsupdates
- Läufe des Homelabs mit flock (Oberfläche und Steward schreiben beide)
- Skill homelab-orchestrator, SOUL-Abschnitt als Kopie in deploy/nur-box
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
81 lines
4.1 KiB
Python
81 lines
4.1 KiB
Python
"""Lucys Freiheiten je Aktionsart (Ausbauplan Welle 3, Punkt 14, 25.09.2026). User-Entscheid „Mehr Freiheit“: Sie startet
|
|
Dienste selbst neu, spielt Sicherheitsupdates ein und geht bei Rot auf den Snapshot zurück — mit Meldung danach; andere
|
|
Updates, Aufräumen, Modell-Tests und den Host-Neustart gibt es nur nach dem Ja des Commanders."""
|
|
|
|
import json
|
|
|
|
import pytest
|
|
from fastapi import FastAPI
|
|
from fastapi.testclient import TestClient
|
|
from kern import einstellungen as einstellungen_mod
|
|
from services import lucy_autonomie, rueckfragen
|
|
|
|
|
|
@pytest.fixture
|
|
def daten(tmp_path, monkeypatch):
|
|
monkeypatch.setenv("MC_DATEN_DIR", str(tmp_path))
|
|
einstellungen_mod.einstellungen.cache_clear()
|
|
yield tmp_path
|
|
einstellungen_mod.einstellungen.cache_clear()
|
|
|
|
|
|
def test_start_ist_mehr_freiheit(daten):
|
|
stufen = lucy_autonomie.stufen()
|
|
assert {a for a, s in stufen.items() if s == "selbst"} == {"ermitteln", "neustart", "sicherheit", "rueckweg", "suchen"}
|
|
assert {a for a, s in stufen.items() if s == "fragen"} == {"updates", "aufraeumen", "radar", "host_neustart"}
|
|
assert lucy_autonomie.stufe("gibt-es-nicht") == "fragen" # im Zweifel nie ungefragt
|
|
|
|
|
|
def test_umstellen_und_merken(daten):
|
|
ergebnis = lucy_autonomie.setzen({"stufen": {"host_neustart": "lesen", "updates": "selbst"}})
|
|
assert ergebnis == {"ok": True, "text": "Gespeichert — Proxmox-Host neu starten: nur lesen; Andere Updates "
|
|
"einspielen: selbst handeln."}
|
|
assert lucy_autonomie.stufe("host_neustart") == "lesen" and lucy_autonomie.stufe("updates") == "selbst"
|
|
assert json.loads((daten / "lucy-autonomie.json").read_text(encoding="utf-8")) == {
|
|
"stufen": {"host_neustart": "lesen", "updates": "selbst"}}
|
|
stand = lucy_autonomie.stand()
|
|
assert stand["arten"][0]["id"] == "ermitteln" and set(stand["stufen"]) == {"lesen", "fragen", "selbst"}
|
|
host = next(a for a in stand["arten"] if a["id"] == "host_neustart")
|
|
assert host["stufe"] == "lesen" and host["standard"] == "fragen"
|
|
|
|
|
|
@pytest.mark.parametrize("eingabe, fehler", [
|
|
({}, "Es fehlt"), ({"stufen": {}}, "Es fehlt"), ("quatsch", "Es fehlt"),
|
|
({"stufen": {"loeschen": "selbst"}}, "gibt es nicht"), ({"stufen": {"updates": "immer"}}, "keine Stufe"),
|
|
])
|
|
def test_nur_bekanntes(daten, eingabe, fehler):
|
|
ergebnis = lucy_autonomie.setzen(eingabe)
|
|
assert not ergebnis["ok"] and fehler in ergebnis["detail"]
|
|
assert not (daten / "lucy-autonomie.json").exists()
|
|
|
|
|
|
def test_kaputte_datei_gilt_als_standard(daten):
|
|
(daten / "lucy-autonomie.json").write_text('{"stufen": {"updates": "immer", "neustart": "lesen"}}', encoding="utf-8")
|
|
assert lucy_autonomie.stufe("updates") == "fragen" and lucy_autonomie.stufe("neustart") == "lesen"
|
|
(daten / "lucy-autonomie.json").write_text("kein json", encoding="utf-8")
|
|
assert lucy_autonomie.stufe("neustart") == "selbst"
|
|
|
|
|
|
def test_meldung_danach(daten, monkeypatch):
|
|
gemeldet = []
|
|
monkeypatch.setattr(rueckfragen, "melden",
|
|
lambda betreff, text, knoepfe=None, dringend=False: gemeldet.append((betreff, text)))
|
|
ergebnis = lucy_autonomie.gehandelt({"was": "Hermes-Gateway neu gestartet.", "grund": "hing seit zehn Minuten",
|
|
"ergebnis": "hermes-gateway wird neu gestartet."})
|
|
assert ergebnis["ok"]
|
|
assert gemeldet == [("[Lucy selbst]", ("Hermes-Gateway neu gestartet. Warum: hing seit zehn Minuten\n"
|
|
"hermes-gateway wird neu gestartet."))]
|
|
assert not lucy_autonomie.gehandelt({"grund": "ohne was"})["ok"]
|
|
|
|
|
|
def test_schnittstelle(daten):
|
|
from routers import lucy
|
|
app = FastAPI()
|
|
app.include_router(lucy.router)
|
|
c = TestClient(app)
|
|
assert c.get("/api/lucy/autonomie").json()["arten"][1]["id"] == "neustart"
|
|
assert c.post("/api/lucy/autonomie", json={"stufen": {"radar": "selbst"}}).status_code == 200
|
|
assert lucy_autonomie.stufe("radar") == "selbst"
|
|
r = c.post("/api/lucy/autonomie", json={"stufen": {"radar": "nie"}})
|
|
assert r.status_code == 409 and "keine Stufe" in r.json()["detail"]
|