Trading-Plattform-Pruefung: Host nicht erreichbar gibt eine Zeile, --tag frueh geprueft (Paket NT, Task 729)

- abendkontrolle.py: Exit 255 oder Zeitlimit bei Summe oder Bestaetigung beendet die Sicherung mit
  genau einer Zeile in der Notiz von heute und Exit 1; ein Abbruch im Archiv laesst nur die Instanz
  scheitern. --tag ausser der Form endet gleich mit Exit 2, ohne Notiz und ssh.
- abendkontrolle_dockerhost_angriffe.py: sieben neue Faelle, zwei angepasste, Mutanten E729-01 bis
  E729-15 und E729-25 (38 Faelle, 56 Mutanten, keiner ueberlebt).
- abendkontrolle_wsl.py: Kopf nennt die neuen fehler-Gruende.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_0126RDfXiECscXipzf67uypJ
This commit is contained in:
Pascal MichelandClaude Opus 5.5 committed 2026-10-11 23:25:38 +02:00
1 parent 02ff0e878d
commit 88ec4fd40f
3 files changed
+355 -28

No files matched your search

@@ -15,6 +15,9 @@ gegen eine Host-Ablage im Wegwerf-Ordner; ANGRIFF_SICHERUNG stoert sie gezielt.
lauf() setzt SICHERUNG_ERSTER_TAG auf None und SICHERUNG_ZIEL in den Wegwerf-Ordner;
sicherungslauf() lenkt mit --sicherung-ziel um, und das Ziel der Konstante muss dort leer bleiben.
Nichts landet unter D:\\Trading-Sicherung.
Seit Paket NT (Quorum 4): ANGRIFF_HOST_WEG=<n> laesst die Attrappe jeden Aufruf nach den ersten n mit
Exit 255 enden wie ssh ohne Host; ANGRIFF_SICHERUNG=langsam haelt jeden Sicherungsbefehl 5 s auf,
ANGRIFF_SICHERUNG=archiv_unlesbar endet beim Archiv wie abendkontrolle-lesen.sh ohne Leserecht.
Aufruf: python abendkontrolle_dockerhost_angriffe.py [--mutanten]
Exit 0 ohne Abweichung, 2 mit Abweichung. --mutanten faehrt MUTANTEN auf Kopien von
abendkontrolle.py und MUTANTEN_AUFGABE auf Kopien von abendkontrolle_einrichten.ps1: Jeder genannte
@@ -47,6 +50,13 @@ import sys
with open(os.environ["ANGRIFF_PROTOKOLL"], "a", encoding="utf-8") as datei:
datei.write(json.dumps(sys.argv[1:]) + "\n")
# Seit Paket NT: Nach ANGRIFF_HOST_WEG Aufrufen ist der Host weg; ssh selbst endet dann mit 255.
weg = os.environ.get("ANGRIFF_HOST_WEG", "")
if weg:
with open(os.environ["ANGRIFF_PROTOKOLL"], encoding="utf-8") as datei:
if len(datei.read().splitlines()) > int(weg):
sys.stderr.write("ssh: connect to host docker-host port 2498: Connection timed out\n")
sys.exit(255)
befehl = sys.argv[-1]
if befehl.startswith("sicherung"):
# Seit Paket E9: die Lesebefehle der Sicherung, so streng wie abendkontrolle-lesen.sh (Task 724),
@@ -68,6 +78,11 @@ if befehl.startswith("sicherung"):
sys.exit(2)
name = lesen.group(2) if lesen else bestaetigen.group(1)
gestoert = stoerung if not nur or name.endswith("-" + nur) else ""
if gestoert == "langsam":
# Seit Paket NT: laenger als das Zeitlimit, das der Fall in seiner Kopie setzt.
import time
time.sleep(5)
archiv = os.path.join(host, name + ".tar")
summendatei = archiv + ".sha256"
gemeint = archiv if lesen and lesen.group(1) == "sicherung" else summendatei
@@ -84,6 +99,10 @@ if befehl.startswith("sicherung"):
sys.stdout.buffer.write(inhalt)
sys.exit(0)
if lesen:
if gestoert == "archiv_unlesbar":
# Seit Paket NT: wie abendkontrolle-lesen.sh bei einem Archiv ohne Leserecht (Exit 1).
sys.stderr.write("abendkontrolle-lesen: " + name + ".tar liegt auf dem Docker-Host, laesst sich aber nicht lesen\n")
sys.exit(1)
if gestoert == "kaputt":
inhalt = inhalt[:-1] + bytes([inhalt[-1] ^ 1])
if gestoert == "abbruch":
@@ -168,6 +187,7 @@ def lauf(art, *argumente, modul=None):
modul.SICHERUNG_ZIEL = wurzel / "sicherung-ziel"
os.environ.pop("ANGRIFF_HOST", None)
os.environ.pop("ANGRIFF_SICHERUNG", None)
os.environ.pop("ANGRIFF_HOST_WEG", None)
unerwartet = []
if not getattr(modul.fahre_wsl, "attrappe", False):
@@ -410,7 +430,7 @@ def namen(ordner):
return sorted(p.name for p in ordner.iterdir())
def sicherungslauf(wurzel, *argumente, modul=None, stoerung="", erster_tag=date(2026, 10, 5), schalter=True):
def sicherungslauf(wurzel, *argumente, modul=None, stoerung="", erster_tag=date(2026, 10, 5), schalter=True, host_weg=""):
"""Ein Lauf von main() mit Quelle dockerhost und Sicherung, alles unter wurzel.
Vault, Host-Ablage (host_ablage) und Ziel bleiben stehen, damit ein zweiter Lauf daran
@@ -446,6 +466,8 @@ def sicherungslauf(wurzel, *argumente, modul=None, stoerung="", erster_tag=date(
os.environ["ANGRIFF_ART"] = "gut"
os.environ["ANGRIFF_HOST"] = str(wurzel / "host")
os.environ["ANGRIFF_SICHERUNG"] = stoerung
# Seit Paket NT: "" heisst, der Host bleibt erreichbar.
os.environ["ANGRIFF_HOST_WEG"] = host_weg
vorne = ["--vault", str(vault), "--quelle", "dockerhost"]
if schalter:
vorne += ["--sicherung-ziel", str(wurzel / "ziel")]
@@ -529,6 +551,9 @@ def eine_falsche_summe_wird_nie_abgelegt_und_nie_bestaetigt():
@fall
def eine_abgebrochene_uebertragung_hinterlaesst_nichts():
# Seit Paket NT (Plankritik, Befund 1): Exit 255 mitten im Archiv heisst nicht, dass der Host fehlt;
# er hat die Summe gerade beantwortet. Nur die kaputte Instanz scheitert, mit dem Befehl im Grund,
# die andere wird geholt. Keine Nachbardatei bleibt, die kaputte Instanz ist nicht bestaetigt.
for kaputt, heil in (("alpaca", "ibkr"), ("ibkr", "alpaca")):
with tempfile.TemporaryDirectory() as ordner:
wurzel = Path(ordner)
@@ -541,8 +566,13 @@ def eine_abgebrochene_uebertragung_hinterlaesst_nichts():
assert namen(wurzel / "ziel" / TAG) == [f"{heil}.tar", f"{heil}.tar.sha256"], namen(wurzel / "ziel" / TAG)
assert namen(host / "bestaetigt") == [f"{TAG}-{heil}"], namen(host / "bestaetigt")
assert not any(b.startswith(f"sicherung-bestaetigt {TAG}-{kaputt} ") for b in befehle), befehle
assert f"{kaputt} gescheitert (RuntimeError: Archiv nicht geholt (Exit 255;" in zeile, zeile
assert "#needs-review" in zeile, zeile
assert (
f"{kaputt} gescheitert (RuntimeError: Archiv nicht geholt (sicherung {TAG}-{kaputt}: Exit 255; "
"Connection reset by peer))"
) in zeile, zeile
assert f"{heil} abgelegt und bestätigt" in zeile, zeile
assert "**gescheitert** #needs-review" in zeile, zeile
assert "Docker-Host nicht erreichbar" not in zeile, zeile
@fall
@@ -929,16 +959,23 @@ def ohne_schalter_gilt_das_ziel_der_konstante():
@fall
def ein_tag_ausser_der_form_holt_keine_sicherung():
for tag in ("2026-10-5", "2026-13-05", "20261005", "../2026-10-05"):
def ein_tag_ausser_der_form_endet_mit_zwei_ohne_notiz_und_ohne_ssh():
# Seit Paket NT (Quorum 4): main prueft --tag gleich nach dem Zerlegen. Vorher lief die
# Abendkontrolle mit dem falschen Tag los und legte Inbox/<tag>.md an, mit ../ auch ausserhalb von
# Inbox/; seit E9 hielt nur die Sicherung an (Exit 1). Jetzt endet der ganze Lauf vorher mit Exit 2.
for tag in ("2026-10-5", "2026-13-05", "2026-02-30", "20261005", "x026-10-05", "2026-10-0x", "../2026-10-05", ""):
with tempfile.TemporaryDirectory() as ordner:
wurzel = Path(ordner)
host_ablage(wurzel)
code, notiz, befehle, _ = sicherungslauf(wurzel, "--tag", tag, "--jetzt", "2026-10-05T23:30")
assert code == 1, (tag, code)
assert befehle == [], (tag, befehle)
fehler = io.StringIO()
with contextlib.redirect_stderr(fehler):
code, notiz, _, _ = sicherungslauf(wurzel, "--tag", tag, "--jetzt", "2026-10-05T23:30")
assert code == 2, (tag, code)
assert fehler.getvalue() == f"abendkontrolle: --tag {tag!a} ist kein Datum JJJJ-MM-TT\n", (tag, fehler.getvalue())
assert namen(wurzel / "vault") == ["Inbox"], (tag, namen(wurzel / "vault"))
assert namen(wurzel / "vault" / "Inbox") == [], (tag, notiz)
assert not (wurzel / "ssh.jsonl").exists(), tag
assert not (wurzel / "ziel").exists(), tag
assert sicherungszeilen(notiz) == [], (tag, notiz)
@fall
@@ -958,6 +995,176 @@ def der_abendausloeser_steht_in_ortszeit_ohne_versatz():
assert ausloeser[0] < zeilen.index(zeile) < eintragen[0], (ausloeser, zeilen.index(zeile), eintragen)
# --- Seit Paket NT (Quorum 4): Host nicht erreichbar, --tag frueh, unlesbare Dateien --------------
HOST_WEG = "ssh: connect to host docker-host port 2498: Connection timed out"
def ergebnisse_liegen(modul, wurzel):
"""Die Ergebnisse beider Teile liegen fuer 2026-09-20 bis 2026-10-12: Der Lauf faehrt nur die Sicherung."""
ablage = wurzel / "vault" / modul.ABLAGE_TEIL
ablage.mkdir(parents=True, exist_ok=True)
for tag in [f"2026-09-{t}" for t in range(20, 31)] + [f"2026-10-{t:02d}" for t in range(1, 13)]:
(ablage / f"{tag}.json").write_text("{}", encoding="utf-8")
(ablage / f"{tag}-ibkr.json").write_text("{}", encoding="utf-8")
return ablage
@fall
def ein_unlesbares_archiv_auf_dem_host_scheitert_nur_fuer_seine_instanz():
# Paket NT (Quorum 4, G2): abendkontrolle-lesen.sh endet bei einem Archiv ohne Leserecht mit Exit 1
# und eigener Zeile. Das ist kein Ausfall des Hosts: nur diese Instanz scheitert, die andere wird
# geholt. Seit ssh_lauf Exit 255 als HostNichtErreichbar meldet, erreicht ein Abbruch mitten im
# Archiv die Pruefung des Exit-Codes nicht mehr; den Mutanten "Exit des Archivs egal" traegt
# deshalb dieser Fall (vorher eine_abgebrochene_uebertragung_hinterlaesst_nichts).
for kaputt, heil in (("alpaca", "ibkr"), ("ibkr", "alpaca")):
with tempfile.TemporaryDirectory() as ordner:
wurzel = Path(ordner)
host = host_ablage(wurzel)
code, notiz, _, _ = sicherungslauf(
wurzel, "--tag", TAG, "--jetzt", "2026-10-05T23:30", stoerung=f"archiv_unlesbar:{kaputt}"
)
zeile = einzige_zeile(notiz)
assert code == 1, code
assert namen(wurzel / "ziel" / TAG) == [f"{heil}.tar", f"{heil}.tar.sha256"], namen(wurzel / "ziel" / TAG)
assert namen(host / "bestaetigt") == [f"{TAG}-{heil}"], namen(host / "bestaetigt")
assert (
f"{kaputt} gescheitert (RuntimeError: Archiv nicht geholt (Exit 1; abendkontrolle-lesen: "
f"{TAG}-{kaputt}.tar liegt auf dem Docker-Host, laesst sich aber nicht lesen)"
) in zeile, zeile
assert f"{heil} abgelegt und bestätigt" in zeile, zeile
assert "Docker-Host nicht erreichbar" not in zeile, zeile
@fall
def ein_unerreichbarer_host_gibt_eine_zeile_von_heute_und_keine_weitere_notiz():
# Paket NT (Quorum 4, G1): Vorher scheiterte bei einem Host ohne Verbindung jeder Tag des Fensters
# mit eigener Zeile, und schreibe_notiz legte die fehlenden Notizen an, auch fuer Samstag und
# Sonntag. Jetzt endet die Sicherung beim ersten Exit 255 mit genau einer Zeile von heute.
with tempfile.TemporaryDirectory() as ordner:
wurzel = Path(ordner)
modul = lade()
ablage = ergebnisse_liegen(modul, wurzel)
code, notiz, befehle, _ = sicherungslauf(
wurzel, "--jetzt", "2026-10-12T23:30", modul=modul, erster_tag=date(2026, 9, 1), host_weg="0"
)
assert code == 1, (code, notiz)
assert namen(wurzel / "vault" / "Inbox") == ["2026-10-12.md"], namen(wurzel / "vault" / "Inbox")
assert einzige_zeile(notiz) == (
f"- 23:30 Uhr — {KOPF}: **gescheitert** #needs-review. Docker-Host nicht erreichbar "
f"(sicherung-summe 2026-10-02-alpaca: Exit 255; {HOST_WEG}), Sicherung abgebrochen. "
"Der nächste Lauf versucht es erneut."
), notiz
assert befehle == ["sicherung-summe 2026-10-02-alpaca"], befehle
assert not (wurzel / "ziel").exists()
log = (ablage / "lauf.log").read_text(encoding="utf-8")
assert "sicherung 2026-10-02-alpaca: Docker-Host nicht erreichbar (" in log, log
@fall
def ein_host_der_mitten_im_lauf_wegfaellt_laesst_die_zeilen_davor_stehen():
# Paket NT (Quorum 4, G1): Fertige Tage behalten ihre Zeilen in ihren Notizen; was ein angefangener
# Tag schon hat, nennt die eine Zeile von heute. Je Tag mit Sicherung sechs Befehle (Summe, Archiv,
# Bestaetigung je Instanz); nach weg Befehlen ist der Host weg. Am Samstag 2026-10-03 liegt keine
# Sicherung, gefragt wird trotzdem.
for weg, notizen, zuletzt, bis_dahin in (
("12", ["2026-10-01.md", "2026-10-02.md", "2026-10-05.md"], "sicherung-summe 2026-10-03-alpaca", ""),
("9", ["2026-10-01.md", "2026-10-05.md"], "sicherung-summe 2026-10-02-ibkr",
" Bis dahin am 2026-10-02: alpaca abgelegt und bestätigt ("),
):
with tempfile.TemporaryDirectory() as ordner:
wurzel = Path(ordner)
host_ablage(wurzel, tage=("2026-10-01", "2026-10-02", "2026-10-05"))
modul = lade()
ergebnisse_liegen(modul, wurzel)
code, notiz, befehle, _ = sicherungslauf(
wurzel, "--jetzt", "2026-10-05T23:30", modul=modul, erster_tag=date(2026, 10, 1), host_weg=weg
)
zeilen = sicherungszeilen(notiz)
assert code == 1, (weg, code)
assert namen(wurzel / "vault" / "Inbox") == notizen, (weg, namen(wurzel / "vault" / "Inbox"))
assert befehle[-1] == zuletzt and len(befehle) == int(weg) + 1, (weg, befehle)
assert namen(wurzel / "ziel") == ["2026-10-01", "2026-10-02"], (weg, namen(wurzel / "ziel"))
assert len(zeilen) == len(notizen), (weg, zeilen)
for zeile in zeilen[:-1]:
assert "alpaca abgelegt und bestätigt" in zeile and "ibkr abgelegt und bestätigt" in zeile, zeile
assert zeilen[-1].startswith(
f"- 23:30 Uhr — {KOPF}: **gescheitert** #needs-review. Docker-Host nicht erreichbar "
f"({zuletzt}: Exit 255; {HOST_WEG}), Sicherung abgebrochen."
), zeilen[-1]
if bis_dahin:
assert bis_dahin in zeilen[-1], zeilen[-1]
else:
assert "Bis dahin" not in zeilen[-1], zeilen[-1]
assert zeilen[-1].endswith(" Der nächste Lauf versucht es erneut."), zeilen[-1]
@fall
def ein_zeitlimit_beim_host_zaehlt_wie_ein_unerreichbarer_host():
# Paket NT (Quorum 4, G1): Laeuft die Frage nach der Summe in ihr Zeitlimit (SSH_TIMEOUT_S), gilt der
# Host als nicht erreichbar. In der Kopie ist das Zeitlimit 1 s, die Attrappe braucht je
# Sicherungsbefehl 5 s. Das Zeitlimit beim Archiv selbst: naechster Fall.
with tempfile.TemporaryDirectory() as ordner:
wurzel = Path(ordner)
host_ablage(wurzel)
modul = lade()
ergebnisse_liegen(modul, wurzel)
modul.SSH_TIMEOUT_S = 1
code, notiz, befehle, _ = sicherungslauf(wurzel, "--jetzt", "2026-10-05T23:30", modul=modul, stoerung="langsam")
assert code == 1, (code, notiz)
assert namen(wurzel / "vault" / "Inbox") == ["2026-10-05.md"], namen(wurzel / "vault" / "Inbox")
assert befehle == ["sicherung-summe 2026-10-05-alpaca"], befehle
assert einzige_zeile(notiz) == (
f"- 23:30 Uhr — {KOPF}: **gescheitert** #needs-review. Docker-Host nicht erreichbar "
"(sicherung-summe 2026-10-05-alpaca: Zeitlimit 1 s abgelaufen), Sicherung abgebrochen. "
"Der nächste Lauf versucht es erneut."
), notiz
assert not (wurzel / "ziel").exists()
@fall
def ein_zeitlimit_beim_archiv_laesst_nur_diese_instanz_scheitern():
# Plankritik NT, Befund 1: Laeuft nur das Archiv in sein Zeitlimit (SICHERUNG_TIMEOUT_S), ist der Host
# erreichbar; er hat die Summe gerade beantwortet. Die Instanz scheitert mit eigenem Grund, die
# andere wird geholt, der naechste Tag auch. In der Kopie ist das Zeitlimit des Archivs 1 s, die
# Attrappe braucht fuer jeden Befehl von alpaca 5 s; die Summe hat weiter SSH_TIMEOUT_S.
tage = ("2026-10-01", "2026-10-02")
with tempfile.TemporaryDirectory() as ordner:
wurzel = Path(ordner)
host = host_ablage(wurzel, tage=tage)
modul = lade()
ergebnisse_liegen(modul, wurzel)
modul.SICHERUNG_TIMEOUT_S = 1
code, notiz, befehle, _ = sicherungslauf(
wurzel, "--jetzt", "2026-10-02T23:30", modul=modul, erster_tag=date(2026, 10, 1), stoerung="langsam:alpaca"
)
zeilen = sicherungszeilen(notiz)
assert code == 1, (code, notiz)
assert namen(wurzel / "vault" / "Inbox") == ["2026-10-01.md", "2026-10-02.md"], namen(wurzel / "vault" / "Inbox")
assert namen(host / "bestaetigt") == [f"{t}-ibkr" for t in tage], namen(host / "bestaetigt")
assert [b for b in befehle if b.startswith("sicherung ")] == [
f"sicherung {t}-{i}" for t in tage for i in ("alpaca", "ibkr")
], befehle
assert len(zeilen) == 2, zeilen
for tag, zeile in zip(tage, zeilen):
assert namen(wurzel / "ziel" / tag) == ["ibkr.tar", "ibkr.tar.sha256"], (tag, namen(wurzel / "ziel" / tag))
assert (
"**gescheitert** #needs-review. alpaca gescheitert (RuntimeError: Archiv nicht geholt "
f"(sicherung {tag}-alpaca: Zeitlimit 1 s abgelaufen)); ibkr abgelegt und bestätigt ("
) in zeile, zeile
assert "Docker-Host nicht erreichbar" not in zeile, zeile
@fall
def kein_kommentar_beginnt_mit_s3():
# Paket NT (Quorum 4): Die Kommentarzeile ueber SICHERUNG_NACHHOLEN_TAGE begann mit "(S3).", einem
# Verweis ohne Ziel. Erst die neue Zeile zusichern, dann die alte ausschliessen.
zeilen = QUELLE.read_text(encoding="utf-8").splitlines()
neu = "# Die Sicherung entsteht 16:45 New Yorker Zeit, in Berlin meist 22:45, vom 25.10. bis 31.10.2026"
assert zeilen.count(neu) == 1, f"neue Kommentarzeile {zeilen.count(neu)}-mal"
assert [z for z in zeilen if z.startswith("# (S3)")] == [], "Kommentar beginnt mit (S3)."
# Mutanten fuer --mutanten: Name, Text in abendkontrolle.py (genau einmal), Ersatz und die Faelle,
# die davon rot werden muessen. MUTANTEN_AUFGABE (unten) tut dasselbe fuer abendkontrolle_einrichten.ps1.
# Ein Mutant, der das Verhalten nicht aendert, gehoert nicht hierher
@@ -968,7 +1175,7 @@ MUTANTEN = [
("Exit des Archivs egal",
' if lauf.returncode != 0:\n raise RuntimeError(f"Archiv nicht geholt',
' if False:\n raise RuntimeError(f"Archiv nicht geholt',
["eine_abgebrochene_uebertragung_hinterlaesst_nichts"]),
["ein_unlesbares_archiv_auf_dem_host_scheitert_nur_fuer_seine_instanz"]),
("Nachbardatei bleibt", " teil.unlink(missing_ok=True)", " pass",
["eine_falsche_summe_wird_nie_abgelegt_und_nie_bestaetigt", "eine_abgebrochene_uebertragung_hinterlaesst_nichts"]),
("Bestaetigung ungeprueft",
@@ -1032,11 +1239,62 @@ MUTANTEN = [
["ohne_schalter_gilt_das_ziel_der_konstante"]),
("Ziel der Konstante", r'SICHERUNG_ZIEL = Path(r"D:\Trading-Sicherung")',
r'SICHERUNG_ZIEL = Path(r"D:\Trading-Sicherung-alt")', ["ohne_schalter_gilt_das_ziel_der_konstante"]),
("Tag ungeprueft",
' if not TAGFORM.fullmatch(tag):\n raise ValueError(f"--tag {tag!r} ist kein Datum JJJJ-MM-TT")\n'
" date.fromisoformat(tag)",
" pass", ["ein_tag_ausser_der_form_holt_keine_sicherung"]),
("Datum ungeprueft", " date.fromisoformat(tag)", " pass", ["ein_tag_ausser_der_form_holt_keine_sicherung"]),
# Seit Paket NT (Quorum 4): Host nicht erreichbar, --tag frueh geprueft, Kommentar ohne (S3).
("E729-01 Host weg: Exit 255 wie jeder andere", " if lauf.returncode == 255:", " if False:",
["ein_unerreichbarer_host_gibt_eine_zeile_von_heute_und_keine_weitere_notiz",
"eine_abgebrochene_uebertragung_hinterlaesst_nichts"]),
("E729-02 Host weg: Zeitlimit wie jeder Fehler", " except subprocess.TimeoutExpired:",
" except ZeroDivisionError:", ["ein_zeitlimit_beim_host_zaehlt_wie_ein_unerreichbarer_host"]),
("E729-03 Host weg reisst nur die Instanz ab", " except HostNichtErreichbar as ausnahme:",
" except ZeroDivisionError as ausnahme:",
["ein_unerreichbarer_host_gibt_eine_zeile_von_heute_und_keine_weitere_notiz",
"ein_host_der_mitten_im_lauf_wegfaellt_laesst_die_zeilen_davor_stehen"]),
("E729-04 Host weg: Zeile in der Notiz des Tags",
" schreibe_notiz(vault, jetzt.date().isoformat(), zeile)",
" schreibe_notiz(vault, kandidat, zeile)",
["ein_unerreichbarer_host_gibt_eine_zeile_von_heute_und_keine_weitere_notiz"]),
("E729-05 Host weg mit Exit 0",
' logge(ablage, f"sicherung: Notiz nicht geschrieben {zweite!r}")\n return 1',
' logge(ablage, f"sicherung: Notiz nicht geschrieben {zweite!r}")\n return 0',
["ein_unerreichbarer_host_gibt_eine_zeile_von_heute_und_keine_weitere_notiz"]),
("E729-06 Host weg: weiter mit der naechsten Instanz",
' logge(ablage, f"sicherung: Notiz nicht geschrieben {zweite!r}")\n return 1',
' logge(ablage, f"sicherung: Notiz nicht geschrieben {zweite!r}")\n continue',
["ein_unerreichbarer_host_gibt_eine_zeile_von_heute_und_keine_weitere_notiz"]),
("E729-07 Host weg: nichts vom angefangenen Tag", " if stuecke:\n zeile +=",
" if False:\n zeile +=",
["ein_host_der_mitten_im_lauf_wegfaellt_laesst_die_zeilen_davor_stehen"]),
("E729-08 Host weg ohne lauf.log",
' logge(ablage, f"sicherung {kandidat}-{instanz}: Docker-Host nicht erreichbar ({ausnahme}), '
'Sicherung abgebrochen")',
" pass", ["ein_unerreichbarer_host_gibt_eine_zeile_von_heute_und_keine_weitere_notiz"]),
("E729-09 Tag spaet geprueft", " if argumente.tag is not None and not tag_gueltig(argumente.tag):",
" if False:", ["ein_tag_ausser_der_form_endet_mit_zwei_ohne_notiz_und_ohne_ssh"]),
("E729-10 leerer Tag gilt als keiner", " if argumente.tag is not None and not tag_gueltig(argumente.tag):",
" if argumente.tag and not tag_gueltig(argumente.tag):",
["ein_tag_ausser_der_form_endet_mit_zwei_ohne_notiz_und_ohne_ssh"]),
("E729-11 falscher Tag mit Exit 1",
' print(f"abendkontrolle: --tag {argumente.tag!a} ist kein Datum JJJJ-MM-TT", file=sys.stderr)\n'
" return 2",
' print(f"abendkontrolle: --tag {argumente.tag!a} ist kein Datum JJJJ-MM-TT", file=sys.stderr)\n'
" return 1",
["ein_tag_ausser_der_form_endet_mit_zwei_ohne_notiz_und_ohne_ssh"]),
("E729-12 falscher Tag ohne Meldung",
' print(f"abendkontrolle: --tag {argumente.tag!a} ist kein Datum JJJJ-MM-TT", file=sys.stderr)',
" pass", ["ein_tag_ausser_der_form_endet_mit_zwei_ohne_notiz_und_ohne_ssh"]),
("E729-13 Form des Tags ungeprueft", " if not TAGFORM.fullmatch(tag):\n return False",
" if False:\n return False", ["ein_tag_ausser_der_form_endet_mit_zwei_ohne_notiz_und_ohne_ssh"]),
("E729-14 Datum des Tags ungeprueft", " date.fromisoformat(tag)\n except ValueError:",
" pass\n except ValueError:", ["ein_tag_ausser_der_form_endet_mit_zwei_ohne_notiz_und_ohne_ssh"]),
("E729-15 Verweis S3 zurueck",
"# Die Sicherung entsteht 16:45 New Yorker Zeit, in Berlin meist 22:45, vom 25.10. bis 31.10.2026",
"# (S3). Die Sicherung entsteht 16:45 New Yorker Zeit, in Berlin meist 22:45, vom 25.10. bis 31.10.2026",
["kein_kommentar_beginnt_mit_s3"]),
# Seit der Plankritik NT, Befund 1: Ein Abbruch im Archiv laesst nur die Instanz scheitern.
("E729-25 Abbruch im Archiv beendet die Sicherung", " except HostNichtErreichbar as abbruch:",
" except ZeroDivisionError as abbruch:",
["ein_zeitlimit_beim_archiv_laesst_nur_diese_instanz_scheitern",
"eine_abgebrochene_uebertragung_hinterlaesst_nichts"]),
("Name der Summendatei beim Host egal", ' if treffer is None or treffer.group(2) != f"{name}.tar":',
" if treffer is None:", ["eine_summendatei_des_hosts_in_falscher_form_holt_kein_archiv"]),
("Grossbuchstaben in der Summe", r'SUMMENZEILE = re.compile(r"([0-9a-f]{64}) (\S+)\n")',