Source - Conversation - Two Round-Trip Defects Found by an Ingest Session 2026-08-31
0.70
0.70
sourced
Anforderung, dass zwei Befehle auf derselben Datei in jeder Reihenfolge zusammenpassen und jeder erzeugte Zustand einen Gegenbefehl hat - 2026-08-31 in wikitool zweimal verletzt
Command Round-Trip Integrity
Typ: Pattern
Definition
Command Round-Trip Integrity ist die Anforderung, dass zwei Befehle, die dieselbe Datei
schreiben, in jeder Reihenfolge zusammenpassen und dass ein Befehl, der einen Zustand erzeugt,
einen Gegenbefehl hat, der ihn vollständig zurücknimmt. Verletzt ist sie in zwei Formen: die
Reihenfolge entscheidet über den Inhalt - der zweite Aufruf zerstört, was der erste
geschrieben hat -, oder ein Befehl erzeugt einen Zustand, den kein anderer Befehl mehr
erreicht.
Beide Formen sind auf Kommandoebene unsichtbar. Jeder einzelne Aufruf gelingt, meldet Erfolg
und tut für sich genommen das Richtige; der Schaden entsteht erst aus der Kombination. In einem
Stack, dessen Regeln jede Handeditierung ausschließen, ist die zweite Form die schwerere: eine
Seite, die kein Befehl mehr reparieren kann, ist eine Sackgasse.
Kernpunkte
Der Anlassfall, Form 1 (Reihenfolge):split_cite_block() in wikitool nahm alles von
der Überschrift ## Fußnoten bis zum Dateiende als Fußnotenblock und behielt daraus nur die
Zitatdefinitionszeilen. Weil xref add seine Abschnitte ans Dateiende hängt, entschied allein die
Reihenfolge von xref add und cite add, ob eine Seite ihre Querverweise behielt. Betroffen
waren cite add, cite sync und rename; 8 Seiten mit 74 Zeilen standen in der gefährdeten
Position1 .
Der Anlassfall, Form 2 (kein Gegenbefehl):xref add schrieb auf einer Source-Seite ein
related:, das types/source.md nicht deklariert, und strip_frontmatter_ref() räumte nur
deklarierte Felder. xref remove konnte den Rest also nicht entfernen - ein Kommando erzeugte
einen Zustand, den ein anderes nicht rückgängig machen konnte1 .
Die Reparatur ordnet die Ausgabe, statt die Aufrufer zu disziplinieren. Der Fußnotenblock
endet seit 1.5.1 an der nächsten Überschrift und wird immer zuletzt gerendert. Damit muss
xref add sein Anhängen am Dateiende nicht ändern: der Widerspruch ist aufgelöst, nicht
umgangen. Eine Regel „erst xref, dann cite" wäre eine Regel gewesen, an die sich jeder
künftige Aufrufer hätte erinnern müssen1 .
Daraus folgt Selbstheilung. Weil der Block immer zuletzt ausgegeben wird, bringt die erste
Zitatoperation eine bereits verrutschte Seite von selbst wieder in Ordnung. Der Fix repariert
nicht nur künftige Aufrufe, sondern den bestehenden Korpus im laufenden Betrieb - siehe
Self-Healing1 .
Der Beleg ist Byte-Gleichheit, nicht ein grüner Test. Nach xref remove und
anschließendem xref link-source kam die referenzierende Concept-Seite byteidentisch aus dem
Zyklus zurück. Erst das zeigt, dass die beiden Kommandos Inversen sind; ein Test, der nur
prüft, dass hinterher wieder eine Referenz dasteht, würde eine umformatierte Seite
durchlassen1 .
Vor dem Schreiben beide Seiten prüfen.xref add validiert seit 1.6.0 beide Seiten,
bevor es eine schreibt, damit eine Ablehnung keine halbe Verknüpfung hinterlässt. Eine
abgebrochene bidirektionale Operation ist selbst ein Zustand ohne Gegenbefehl1 .
Die Feldwahl folgt der Collection, nicht einer Tabelle.xref link-source legt ein Ziel
aus kb/entities/ in entities: und eines aus kb/concepts/ in concepts: ab. Das
Verzeichnis ist der Feldname, also braucht eine neue Collection keine Codeänderung, sondern
einen Typ, der das passende Feld deklariert. Eine Typ-zu-Feld-Zuordnung wurde verworfen, weil
sie eine zweite Kopie dessen wäre, was die Type-Specs schon sagen1 .
Abgrenzung zu Detect-Repair Asymmetry: dort meldet ein Check einen Defekt, für den es
keinen Reparaturbefehl gibt. Hier meldet niemand etwas - jeder beteiligte Aufruf endet mit
Erfolg, und der Defekt zeigt sich erst an dem, was hinterher in der Datei fehlt.
Beispiele
wikitool - cite add/xref add (Gitea-Issue #17, geschlossen mit 1.5.1) und
xref add/xref remove auf einer Source-Seite (Issue #18, geschlossen mit 1.6.0)
Self-Healing - die Eigenschaft, die aus der gewählten Reparatur folgt
Beim Entwurf eines Befehls, der eine Datei schreibt, die schon ein anderer Befehl schreibt:
beide Reihenfolgen durchspielen, nicht nur die geplante.
Bei jedem Befehl, der einen Zustand erzeugt: benennen, welcher Befehl ihn wieder entfernt,
und den Zyklus einmal vollständig durchlaufen - der Vergleich ist Byte-Gleichheit.
Bei einer Ablehnung, die auf ein anderes Kommando verweist: sie ist eine Behauptung über
dessen Fähigkeiten und gehört mit dem Test ausgeliefert, der sie belegt (siehe
Denylist over Allowlist).
Wann NICHT zu verwenden
Für Befehle, die bewusst nicht umkehrbar sind, weil die Umkehrung eine andere Operation ist:
publish schreibt Historie, und die Rücknahme eines Commits ist ein eigener Vorgang, keine
fehlende Inverse.
Als Argument gegen anhängende Schreibvorgänge überhaupt. Das Problem war nicht das Anhängen
am Dateiende, sondern ein Leser, der alles dahinter als seinen Bereich betrachtete.