Files
claude-regelwerk/.vault-tools/plan-kopie/mutanten_lauf_ENTWURF.md
T
Pascal MichelandClaude Opus 5 1be4f913aa Automatischer Push: 11 Datei(en) geändert
- .claude/agents/planer.md
- .claude/workflows/plankritik.js
- .vault-tools/plan-kopie/LIESMICH.md
- .vault-tools/plan-kopie/mutanten_lauf.py
- .vault-tools/plan-kopie/mutanten_lauf_ENTWURF.md
- .vault-tools/plan-kopie/mutanten_lauf_kopie.sh
- .vault-tools/plan-kopie/mutanten_lauf_treiber.py
- .vault-tools/workflow-pruefung/faelle_workflows.mjs
- .vault-tools/workflow-pruefung/mutanten_workflows.py
- AI/Regelwerk/codeworkflow.md
- CLAUDE.md

Erzeugt vom PostToolUse-Hook .vault-tools/auto-push/auto_push.py.

Co-Authored-By: Claude Opus 5 <[email protected]>
2026-09-28 04:00:02 +02:00

15 KiB
Raw Blame History

type, status, title, summary, tags, created, updated
type status title summary tags created updated
resource active Entwurf: mutanten_lauf.py — allgemeiner Mutantenläufer aus der Plantabelle Entscheidungsliste, Dateiformat mutanten.toml, Ablauf, Status und Exit-Codes, Abdichtung und Angriffsliste für den allgemeinen Mutantenläufer. Punkt 3 der Arbeitsweise vom 2026-09-26.
vault-tools
codeworkflow
pruefwerkzeuge
2026-09-27 2026-09-27

Entwurf: mutanten_lauf.py

Bisher hatte jeder Plan seinen eigenen Mutantenläufer. mutlauf.py für L1a las die Mutanten per Regex aus dem Plantext, mutanten_py.py hielt sie als Python-Liste. Die Mutanten von B1 stehen in einer dritten Form im Plan, die keiner der beiden liest. Der allgemeine Läufer liest stattdessen eine Datei in einem festen Format, die im Planordner neben der Übersicht liegt: mutanten.toml. Prosa im Plan wird nicht geparst.

Das Werkzeug ist ein Fangnetz, kein Nachweis. Es sagt, ob die genannten Tests unter einer bestimmten Änderung fallen. Ob sie an der gemeinten Stelle fallen, zeigt es nur über die Fehlermeldung, die es mit ausgibt. Das Urteil darüber bleibt beim Menschen.

1 Entscheidungsliste

Frage Herkunft
Steht das Muster eines Mutanten genau einmal als ganze Zeilenfolge in seiner Datei? Projektvorgabe (Pläne L1a und B1: „jedes Muster steht als ganze Zeile genau einmal in seiner Datei“)
Sind alle genannten Tests ohne Mutant grün (Grundlinie)? Projektvorgabe (AI/Regelwerk/pruefwerkzeuge, Abschnitt 3: zuerst die Grundlinie)
Fällt unter dem Mutanten jeder genannte Test? Projektvorgabe (Pläne: „‚Rot bei‘ nennt Tests, die fallen müssen“)
Was heißt „fällt“? Projektvorgabe: Der Test hat in einer Phase (setup, call, teardown) das Ergebnis failed, oder er fehlt, weil das Einsammeln seiner Datei scheitert. skipped und xfail sind nicht rot.
Ist die Datei nach jedem Mutanten wieder byte-gleich? Projektvorgabe: SHA-256 vorher und nachher
Läuft das auf einer Wegwerfkopie? Projektvorgabe: Die Wurzel darf kein .git haben (Ordner oder Datei), und keiner der Pfade aus ohne darf existieren
Welche Tests und Ergebnisse meldet pytest? Quelle: pytest-Hooks pytest_runtest_logreport, pytest_collectreport, pytest_deselected (pytest 9.1.1 im Venv trading, nachgesehen 2026-09-27). Die Knotennamen kommen aus report.nodeid, nicht aus einer JUnit-Klassenbezeichnung.
Hält die Abdichtung Netz und echte Zugänge fern? Annahme, aufgelöst als Projektvorgabe: Sie ist ein Fangnetz mit benannten Lücken (Abschnitt 5), kein Nachweis.

2 Dateiformat mutanten.toml, Format 1

TOML, gelesen mit tomllib. Literale Zeichenketten in '''…''' nehmen Backslashes wörtlich; deshalb dieses Format und nicht JSON.

format = 1
python = "/home/claude/venvs/trading/bin/python"
ohne = ["config/secrets.toml", "config/zugang.toml"]

[kuerzel]
DA = "tests/test_bewertung_daten.py"
U = "tests/test_bewertung_urteil.py"

[python_je_datei]
"tests/test_broker_ibkr.py" = "/home/claude/venvs/trading-ibkr/bin/python"

[[mutant]]
id = "D1"
datei = "trading/bewertung/daten.py"
aus = '''
    return ort.weekday() < 5 and SPERRE_VON <= ort.time() < SPERRE_BIS
'''
wird = '''
    return False
'''
rot_bei = ["DA::test_montag_bis_freitag_von_neun_bis_halb_fuenf_new_york_wird_nicht_abgerufen"]
hinweis = "frei, wird nur ausgegeben"

Oberste Ebene, erlaubt sind genau diese Schlüssel:

Schlüssel Pflicht Typ Bedeutung
format ja Ganzzahl, genau 1 Formatfassung
python ja absoluter Pfad Interpreter für pytest; ~ ist ein Befund
ohne nein Liste von relativen Pfaden darf es in der Wurzel nicht geben; .git gilt immer zusätzlich
kuerzel nein Tabelle Name → relativer Pfad einer Testdatei Abkürzung in rot_bei
python_je_datei nein Tabelle relativer Pfad → absoluter Pfad anderer Interpreter für diese Testdatei
mutant ja Liste von Tabellen, nicht leer die Mutanten

Je Mutant, erlaubt sind genau diese Schlüssel:

Schlüssel Pflicht Typ Bedeutung
id ja nicht leere Zeichenkette, eindeutig Name in der Ausgabe und in --nur
datei ja relativer Pfad Datei in der Wurzel; kein .., nicht absolut, keine Verknüpfung, muss existieren
aus ja nicht leere Zeichenkette ganze Zeilen, die ersetzt werden
wird ja Zeichenkette ganze Zeilen, die dafür eingesetzt werden; leer heißt: Zeilen entfernen
rot_bei ja nicht leere Liste von Zeichenketten Tests, die fallen müssen
hinweis nein Zeichenkette wird in Ausgabe und Bericht mitgeführt

Jeder unbekannte Schlüssel ist ein Befund (Positivliste). So fällt rotbei statt rot_bei auf.

Zeilen aus aus und wird: Ein einzelnes \n am Ende wird entfernt (die schließenden ''' auf eigener Zeile). Eine leere Zeichenkette ergibt null Zeilen. Sonst wird an \n getrennt. Ein \r bleibt Teil der Zeile.

Einträge in rot_bei: K::name mit K aus kuerzel wird zu <pfad>::name. Sonst muss der Eintrag mit einem relativen Pfad auf .py beginnen, gefolgt von ::. Alles andere ist ein Befund (unbekanntes Kürzel). Parametrisierte Tests stehen mit ihrer vollen Kennung, etwa U::test_knapp_hinter_jeder_grenze_faellt_genau_dieses_kriterium[max_drawdown-0.1501-max_drawdown].

3 Ablauf

Aufruf in WSL, mit einem Python ab 3.11 (nur Standardbibliothek):

python mutanten_lauf.py --wurzel <kopie> --mutanten <mutanten.toml> [--nur ID,ID,…] [--nur-muster] [--bericht <datei.json>] [--zeitlimit <sekunden>]

--zeitlimit gilt je pytest-Aufruf, Vorgabe 900. --nur-muster fährt nur die Schritte 1 und 2.

  1. Vorbedingungen und Format. Alle Befunde sammeln, nicht beim ersten abbrechen: Wurzel fehlt oder ist kein Ordner; .git in der Wurzel; ein Pfad aus ohne existiert; TOML nicht lesbar; jede Formatregel aus Abschnitt 2; ein Interpreter fehlt oder ist nicht ausführbar; --nur nennt eine unbekannte ID. Gibt es einen Befund, endet der Lauf hier mit Exit 2.
  2. Muster. Für jeden ausgewählten Mutanten: Datei als Bytes lesen, streng als UTF-8 dekodieren (sonst Befund), an \n trennen, die Zeilenfolge aus aus an jeder Startposition suchen (überlappende Treffer zählen mit). Genau ein Treffer ist gut. Sonst Status MUSTER mit der Trefferzahl; enthält die Datei ein \r, steht der Hinweis „Datei hat CR“ dabei. aus gleich wird ist ein Formatbefund aus Schritt 1 („wirkungslos“).
  3. Grundlinie. Alle genannten Tests der ausgewählten Mutanten ohne Mutant fahren, gruppiert nach Interpreter. Jeder muss GRUEN sein. Fehlt einer, ist er abgewählt (etwa durch den Marker integration), übersprungen oder rot, ist das ein Befund mit der Meldung, und der Lauf endet mit Exit 2, bevor ein Mutant läuft.
  4. Mutanten in der Reihenfolge der Datei, MUSTER-Fälle ausgelassen. Je Mutant:
    • SHA-256 der Originalbytes merken, die Zeilen ersetzen, mit \n zusammenfügen, als UTF-8-Bytes schreiben.
    • Die genannten Tests fahren, gruppiert nach Interpreter.
    • Im finally die Originalbytes zurückschreiben und den SHA-256 neu lesen. Weicht er ab, endet der ganze Lauf sofort mit dem Befund „Rücksetzen gescheitert“ und Exit 2.
  5. Zusammenfassung und Exit-Code.

pytest-Aufruf: <python> -m pytest -q -p no:cacheprovider -p _mutanten_lauf_plugin <knoten…> mit cwd = Wurzel. Das Plugin ist eine kleine Datei, die das Skript in einen eigenen Temp-Ordner schreibt; der Ordner kommt vorn in PYTHONPATH. Das Plugin schreibt je Bericht eine JSON-Zeile in die Datei aus der Umgebungsvariable MUTANTEN_LAUF_ERGEBNIS:

  • pytest_runtest_logreport: {"art": "test", "knoten": nodeid, "phase": when, "ergebnis": outcome, "xfail": hat wasxfail, "meldung": erste Zeile}
  • pytest_collectreport nur bei failed: {"art": "sammeln", "knoten": nodeid, "meldung": erste Zeile}
  • pytest_deselected: je Element {"art": "abgewaehlt", "knoten": nodeid}

Die Meldung ist longrepr.reprcrash.message, wenn es das gibt, sonst die erste nicht leere Zeile von str(longrepr), gekürzt auf 300 Zeichen. Das Plugin schreibt jede Zeile sofort und mit flush.

Auswertung je Test: eine Phase failed → ROT (bei xfail nicht); sonst skipped in einer Phase oder xfail → UEBERSPRUNGEN; sonst call mit passed → GRUEN; abgewählt → ABGEWAEHLT; gar kein Bericht → FEHLT. Unter einem Mutanten zählt FEHLT als ROT mit der Meldung „Sammelfehler: …“, wenn es für seine Testdatei (oder einen übergeordneten Knoten) einen sammeln-Bericht gibt. In der Grundlinie ist jedes FEHLT ein Befund.

Zeitlimit: Prozess mit start_new_session=True starten; bei Überschreitung die ganze Prozessgruppe mit SIGKILL beenden. Der Mutant bekommt ZEIT, das Rücksetzen läuft trotzdem.

4 Status, Ausgabe, Exit-Codes

Status je Mutant Bedeutung
OK jeder genannte Test ROT
LUECKE mindestens ein genannter Test GRUEN oder UEBERSPRUNGEN
UNKLAR ein Test FEHLT ohne Sammelfehler, oder pytest endete ohne auswertbare Ergebnisdatei
ZEIT Zeitlimit überschritten
MUSTER Muster nicht genau einmal gefunden, nichts gefahren

Ausgabe auf stdout, UTF-8, je Mutant eine Zeile: D1 OK DA::test_x=ROT(<meldung, 120 Zeichen>) …. Tests erscheinen in der Kurzform aus rot_bei. Ein vorhandener hinweis folgt in der nächsten Zeile, eingerückt. Oben stehen Wurzel, Mutantendatei, Anzahl gesamt und ausgewählt und je Interpreter die Python-Version. Unten eine gerechnete Zusammenfassung: ergebnis: <n> Mutanten, <a> OK, <b> LUECKE, <c> UNKLAR, <d> ZEIT, <e> MUSTER.

--bericht schreibt dasselbe als JSON: {"wurzel", "mutanten_datei", "befunde": [...], "grundlinie": {knoten: {ergebnis, meldung}}, "mutanten": [{"id", "datei", "status", "hinweis", "treffer", "tests": [{"eintrag", "knoten", "ergebnis", "meldung"}]}], "zusammenfassung": {...}}. Der Bericht wird auch bei einem Abbruch geschrieben, mit allem, was bis dahin feststand.

Exit Bedeutung
0 Grundlinie grün, jeder ausgewählte Mutant OK (mit --nur-muster: jedes Muster genau einmal)
1 mindestens ein Mutant nicht OK
2 Vorbedingung, Format, Grundlinie oder Rücksetzen gescheitert
3 unerwarteter Abbruch; die bis dahin gesammelten Befunde und Ergebnisse werden trotzdem ausgegeben

Der Hauptablauf steht in try. Ein Traceback ist ein Befund und wird als solcher gemeldet (AI/Regelwerk/pruefwerkzeuge, Abschnitt 4).

5 Abdichtung

Jeder pytest-Aufruf bekommt eine Kopie der Umgebung mit diesen Änderungen:

  • HOME zeigt auf einen frischen, leeren Temp-Ordner. Zugänge, die unter ~/.config liegen (Scharfschalter, IBKR-Live-Zugang, Scalable), sind damit nicht erreichbar.
  • http_proxy, https_proxy, all_proxy und dieselben in Großschrift zeigen auf http://127.0.0.1:9; no_proxy und NO_PROXY werden entfernt. Bibliotheken, die Proxy-Variablen lesen, laufen damit gegen einen geschlossenen Port.
  • PYTHONDONTWRITEBYTECODE=1, COLUMNS=300, MUTANTEN_LAUF_ERGEBNIS=<temp-datei>, PYTHONPATH mit dem Plugin-Ordner vorn.

Was das nicht abdichtet: einen Unterprozess mit absolutem Pfad (so kam der Vorfall vom 2026-09-26 zustande), einen Socket ohne Proxy, einen Client mit trust_env=False oder mit eigenem Transport, Dateien mit absolutem Pfad außerhalb von HOME. Die eigentliche Sperre bleibt die Netzsperre der Testdateien selbst und eine Kopie ohne Geheimnisdateien (ohne).

Alle Temp-Ordner liegen außerhalb der Wurzel und werden am Ende entfernt, auch nach einem Abbruch.

6 Angriffsliste für den Treiber mutanten_lauf_treiber.py

Der Treiber baut in einem Temp-Ordner ein kleines Projekt (ein Modul, eine Testdatei, pyproject.toml mit [tool.pytest.ini_options]), schreibt je Fall eine mutanten.toml, startet den Läufer als Unterprozess und prüft Exit-Code und ein Meldungsstück. Zuerst die Grundlinie: Der saubere Fall muss Exit 0 und nur OK liefern, sonst misst der Rest nichts. Nach jedem Fall prüft der Treiber, dass jede Datei des kleinen Projekts byte-gleich mit dem Ausgangsstand ist.

Nr Fall erwartet
1 sauber, zwei Mutanten, beide getötet Exit 0, zweimal OK
2 ein Test bleibt grün, als erster Eintrag in rot_bei Exit 1, LUECKE
3 dasselbe als letzter Eintrag Exit 1, LUECKE
4 der lückenhafte Mutant an erster und an letzter Position der Datei je Exit 1, LUECKE beim richtigen Mutanten
5 aus kommt nicht vor Exit 1, MUSTER mit Zahl 0
6 aus kommt zweimal vor Exit 1, MUSTER mit Zahl 2
7 aus ist nur ein Teil einer Zeile (andere Einrückung) Exit 1, MUSTER mit Zahl 0
8 Zieldatei mit CRLF Exit 1, MUSTER mit Hinweis auf CR
9 aus über zwei Zeilen, getötet Exit 0, OK
10 wird = "" entfernt eine Zeile, getötet Exit 0, OK
11 Mutant erzeugt einen Syntaxfehler, das Einsammeln scheitert Exit 0, OK mit „Sammelfehler“
12 Mutant erzeugt eine Endlosschleife, --zeitlimit 5 Exit 1, ZEIT, Datei danach byte-gleich
13 ein Test ist in der Grundlinie rot Exit 2
14 ein Test ist in der Grundlinie übersprungen Exit 2
15 ein Test ist per Marker abgewählt Exit 2, Meldung nennt „abgewählt“ oder ABGEWAEHLT
16 ein Testname existiert nicht Exit 2
17 .git als Ordner in der Wurzel; dasselbe als Datei je Exit 2
18 ein Pfad aus ohne existiert Exit 2
19 unbekannter Schlüssel im Mutanten (rotbei) und auf oberster Ebene je Exit 2
20 doppelte id Exit 2
21 rot_bei leer; rot_bei als Zeichenkette statt Liste je Exit 2
22 datei mit ..; absolut; fehlt je Exit 2
23 unbekanntes Kürzel in rot_bei Exit 2
24 aus gleich wird Exit 2
25 aus leer; format = 2; format fehlt; python relativ; python existiert nicht je Exit 2
26 TOML-Syntaxfehler Exit 2
27 keine Mutanten Exit 2
28 --nur mit unbekannter ID Exit 2
29 --nur wählt einen von zwei Mutanten Exit 0, nur dieser läuft
30 --nur-muster bei sauberem Fall; bei Fall 6 Exit 0 ohne pytest-Lauf; Exit 1
31 Zieldatei ist kein UTF-8 Exit 1 oder 2, Meldung nennt die Kodierung
32 Abdichtung: ein Test prüft, dass HOME nicht das echte ist und https_proxy gesetzt ist Grundlinie grün, Exit 0
33 --bericht schreibt gültiges JSON mit den Status aus Fall 2 Datei lesbar, Status stimmt
34 python_je_datei setzt für eine Testdatei einen zweiten Interpreter (derselbe Pfad über einen anderen Namen, etwa eine Verknüpfung im Temp) Exit 0

Der Treiber gibt je Fall eine Zeile aus (Fall 5 ok oder Fall 5 FEHLER: erwartet …, bekommen …) und am Ende die gerechnete Zahl der Fehler. Exit 0 nur, wenn alle Fälle wie erwartet ausgehen und jedes Mal byte-gleich zurückgesetzt wurde.

7 Abbruchbedingung

Eine Runde mit dem Prüfer endet, wenn die Befunde nur noch Formulierungen betreffen. Der erste echte Einsatz ist B1 (74 Mutanten auf einer Kopie von b1-mat-kopie).