"""Update-Verlauf aus dem Meldeprotokoll und festgehaltene Bausteine (Box-Wart 09/2026).""" import json from services import update_verlauf # Echte Formate aus ~/mc2-notify.log der Box (gekürzt), inklusive fremder Meldungen dazwischen. PROTOKOLL = """\ 2026-08-14 18:53:58 OK telegram: Mein Gehirn lädt nicht — ich kann gerade nicht richtig denken. 2026-08-23 04:31:15 QUEUED für Morgen-Digest: KRITISCH: Engine (llama.cpp)-Update auf 10588 UND Rollback fehlgeschlagen — Stack möglicherweise kaputt. 2026-08-23 07:04:17 OK telegram: 📰 DAILY NEWS 23.08.2026 • **Eine Nachricht** (Quelle, 21. August 2026) Text der Nachricht. 2026-09-04 14:07:27 OK telegram: Router (llama-swap) aktualisiert: 252 → 253. Stack-Check grün, alles läuft. 2026-09-04 14:09:30 OK telegram: Commander, die Wochenpflege der Box ist durch — kurz für dich: • Router (llama-swap): 252 → 253 eingespielt, Stack-Check grün. • Engine (llama.cpp): 10733 → 10797 eingespielt, Stack-Check grün. • Hermes-Agent: 1 Commits eingespielt, Gehirn-Check grün. 2026-09-04 14:26:29 OK telegram: Die Box startet jetzt neu — Kernel 7.0.0-31 ist eingespielt. 2026-09-06 04:31:16 QUEUED für Morgen-Digest: Engine (llama.cpp) aktualisiert: 10797 → 10819. Stack-Check grün, alles läuft. 2026-09-06 04:55:53 QUEUED für Morgen-Digest: Hermes-Update fehlgeschlagen (Gehirn-Check rot). Automatisch zurückgerollt auf 63279301b — läuft wieder. Hermes ist jetzt GEPINNT. 2026-09-06 04:55:53 QUEUED für Morgen-Digest: Commander, die Wochenpflege der Box ist durch — kurz für dich: • Router (llama-swap): aktuell (253). • Engine (llama.cpp): 10797 → 10819 eingespielt, Stack-Check grün. • Hermes-Agent: Update FEHLGESCHLAGEN → zurückgerollt + GEPINNT auf 63279301b. 2026-09-10 12:00:00 OK telegram: Die Box startet jetzt neu — von Hand. 2026-09-13 04:31:07 QUEUED für Morgen-Digest: Commander, die Wochenpflege der Box ist durch — kurz für dich: • Router (llama-swap): 253 → 255 eingespielt, Stack-Check grün. • Engine (llama.cpp): 10936 FEHLGESCHLAGEN → zurückgerollt auf 10819 + GEPINNT. • Hermes-Agent: gepinnt, übersprungen. 2026-09-13 04:31:07 QUEUED für Morgen-Digest: Die Box startet jetzt neu — das OS verlangt es nach dem Update (libc6 ). Sie ist ein paar Minuten weg. 2026-09-20 04:30:39 QUEUED für Morgen-Digest: Router (llama-swap) aktualisiert: 255 → 256. Stack-Check grün, alles läuft. 2026-09-20 04:35:33 QUEUED für Morgen-Digest: Commander, die Wochenpflege der Box ist durch — kurz für dich: • Router (llama-swap): 255 → 256 eingespielt, Stack-Check grün. • Engine (llama.cpp): 11026 → 11057 eingespielt, Stack-Check grün. • Hermes-Agent: 1861 Commits eingespielt, Gehirn-Check grün. 2026-09-27 04:31:00 QUEUED für Morgen-Digest: Commander, die Wochenpflege der Box ist durch — kurz für dich: • Router (llama-swap): aktuell (256). • Engine (llama.cpp): aktuell (11057). • Hermes-Agent: aktuell. """ def test_laeufe_neueste_zuerst_und_ohne_fremde_meldungen(): laeufe = update_verlauf.laeufe_aus(PROTOKOLL) # 27.09., 20.09., 13.09., 06.09., 04.09., 23.08. — der Neustart vom 10.09. allein ist kein Lauf. assert [lauf["start"][:10] for lauf in laeufe] == [ "2026-09-27", "2026-09-20", "2026-09-13", "2026-09-06", "2026-09-04", "2026-08-23"] def test_gruener_sonntag_mit_einer_zeile_je_baustein(): lauf = update_verlauf.laeufe_aus(PROTOKOLL)[1] assert lauf["anlass"] == "Updates am Sonntag" assert (lauf["stufe"], lauf["titel"]) == ("gruen", "Eingespielt") assert [(z["baustein"], z["ergebnis"]) for z in lauf["zeilen"]] == [ ("llama-swap", "eingespielt"), ("Motor", "eingespielt"), ("Hermes", "eingespielt")] assert lauf["zeilen"][2]["text"] == "1861 Änderungen eingespielt, Gehirn-Check grün." def test_rueckbau_und_festhalten_werden_gelb_und_lesbar(): lauf = update_verlauf.laeufe_aus(PROTOKOLL)[2] # 13.09. assert (lauf["stufe"], lauf["titel"]) == ("gelb", "Zurückgerollt") zeilen = {z["baustein"]: z for z in lauf["zeilen"]} assert zeilen["Motor"]["ergebnis"] == "zurueckgerollt" assert zeilen["Motor"]["text"] == "10936 fehlgeschlagen → zurückgerollt auf 10819 und festgehalten." assert zeilen["Hermes"]["ergebnis"] == "festgehalten" assert zeilen["Neustart"]["ergebnis"] == "neustart" assert zeilen["Neustart"]["text"] == "Die Box startet jetzt neu — das OS verlangt es nach dem Update (libc6 )." def test_abgebrochener_lauf_ohne_zusammenfassung_ist_rot(): lauf = update_verlauf.laeufe_aus(PROTOKOLL)[-1] # 23.08. assert (lauf["stufe"], lauf["titel"]) == ("rot", "Fehler") assert lauf["zeilen"][0]["baustein"] == "Motor" assert lauf["zeilen"][0]["text"].startswith("Kritisch: Engine (llama.cpp)-Update auf 10588") def test_lauf_von_hand_mit_neustart_danach(): lauf = update_verlauf.laeufe_aus(PROTOKOLL)[4] # 04.09. nachmittags assert lauf["anlass"] == "Update von Hand" assert lauf["titel"] == "Eingespielt" assert lauf["zeilen"][-1]["baustein"] == "Neustart" assert lauf["zeilen"][2]["text"] == "1 Änderung eingespielt, Gehirn-Check grün." def test_alles_aktuell_ist_grau(): lauf = update_verlauf.laeufe_aus(PROTOKOLL)[0] assert (lauf["stufe"], lauf["titel"]) == ("grau", "Alles aktuell") def test_laeufe_liest_die_datei_und_merkt_sich_den_stand(tmp_path, monkeypatch): datei = tmp_path / "mc2-notify.log" datei.write_text(PROTOKOLL, encoding="utf-8") monkeypatch.setattr(update_verlauf, "NOTIFY_LOG", datei) monkeypatch.setattr(update_verlauf, "_cache", {"schluessel": None, "laeufe": []}) assert len(update_verlauf.laeufe(3)) == 3 monkeypatch.setattr(update_verlauf, "NOTIFY_LOG", tmp_path / "gibt-es-nicht.log") assert update_verlauf.laeufe() == [] def test_festgehalten_und_freigeben(tmp_path, monkeypatch): pins = tmp_path / "mc2-pins.json" pins.write_text(json.dumps({ "engine": {"pinned": True, "version": "10819", "datum": "2026-09-13", "grund": "Update auf 10936 zerschoss den Stack-Check; Rollback grün"}, "hermes": {"pinned": True, "version": "63279301b", "datum": "2026-09-06", "grund": "x"}, "swap": {"pinned": False}, }), encoding="utf-8") monkeypatch.setattr(update_verlauf, "PINS_FILE", pins) fest = update_verlauf.festgehalten() assert [(p["baustein"], p["seit"]) for p in fest] == [("engine", "13.09."), ("hermes", "06.09.")] assert fest[0]["name"] == "Motor (llama.cpp)" assert update_verlauf.freigeben("engine") is True assert update_verlauf.freigeben("engine") is False rest = json.loads(pins.read_text(encoding="utf-8")) assert set(rest) == {"hermes", "swap"} def test_waechter_meldet_festgehaltene_bausteine(tmp_path, monkeypatch): from services import waechter pins = tmp_path / "mc2-pins.json" pins.write_text(json.dumps({"engine": {"pinned": True, "version": "10819", "datum": "2026-09-13", "grund": "Update zerschoss den Stack-Check"}}), encoding="utf-8") monkeypatch.setattr(update_verlauf, "PINS_FILE", pins) befunde = waechter.pruefe_festgehalten() assert len(befunde) == 1 b = befunde[0] assert (b.id, b.stufe, b.sofort) == ("pin:engine", "gelb", True) assert "Seit 13.09. auf 10819 festgehalten" in b.text assert b.aktionen[0]["id"] == "freigeben" and b.aktionen[0]["baustein"] == "engine"