Files
management/docs/adr/0022-upstream-anschluss-durch-einmaligen-merge.md
T
Thore Cimbal 6652125ed3 #0099 geschlossen: Upstream-Anschluss vollzogen, v0.6.0 in Produktion
ThreadNet-Web:main auf 8ca03fe, Merge-Commit 88c4e15 mit beiden echten Eltern.
GHSA-wrcp-5v3v-3j6v ist mit dem Versionssprung erledigt.

Drei Anlaeufe: rc.1 erzeugte kein Image (.npmrc fehlte im Docker-Kontext, unter
pnpm 11 fatal), rc.2 ging live und brach die Raumliste, rc.3 bestand die Abnahme.

ADR-0022 um zwei Korrekturen ergaenzt, die erst die Ausfuehrung gezeigt hat:

Die stille Klasse verschwindet nicht ganz, sie dreht sich um. Der Merge meldet,
wenn Upstream eine Datei verschiebt - das hat gehalten. Er meldet nicht, wenn
beide Seiten die Aufloesung ueberleben und nur eine noch Sinn ergibt. Genau das
brach rc.2.

Und: ein gruener Build war nie eine Abnahme. Der web-Job baut nur, webpack
wirft Typen weg. tsc meldete den Fehler durchgehend, gefragt hatte ihn niemand.
Seit 8ca03fe fuehrt docker_web den Job typecheck als needs.

Daraus die stehende Regel fuer kuenftige Merges: nach der Konfliktaufloesung auf
ueberlebende Reste pruefen, nicht nur auf verlorene Zeilen. Wo Zeichenketten
statt Typen im Spiel sind, braucht es einen Abgleich gegen die Registry - fuer
Einstellungen einmalig gefahren, 135 abgefragte gegen 152 registrierte.
2026-08-19 12:00:00 +00:00

5.3 KiB

type, id, status, date, supersedes, superseded_by, related
type id status date supersedes superseded_by related
adr 0022 accepted 2026-08-19 null null
docs/issues/0099-threadnet-web-12-upstream-sicherheitsfixes-lassen-sich-nicht-m.md

ADR-0022: Anschluss an Upstream durch einen einmaligen Merge, nicht durch einen geteilten Graft

Kontext

ThreadNet-Web enthält keine Upstream-Historie: Am 2026-05-10 kam ein kompletter Element-Web-Baum in einem Commit herein. Ohne gemeinsamen Vorfahren ist git merge upstream/develop unmöglich, und jedes Update bedeutet, zwölf eigene Patches von Hand auf einen neuen Baum aufzutragen. Das ist nicht nur mühsam, sondern gefährlich: Verschiebt Element eine Datei, verschwinden unsere Zeilen ohne Konflikt (#0099).

Am 2026-08-19 wurde der tatsächliche Ursprung gemessen statt geraten: deadd548 vom 2026-05-08 (nicht der Tag v1.12.17, wie zuvor angenommen). Der Beleg ist die Baumdistanz — 43 abweichende Dateien, davon 31 reine Modus-Änderungen und der Rest unser eigenes Feature.

Im Wegwerf-Klon getestet: Mit gesetztem Vorfahren läuft ein Merge von drei Monaten develop durch und erzeugt 32 konfliktbehaftete Dateien, davon nur vier Quellcode — genau unsere Patches.

Optionen

A: Geteilter Replace-Ref. git replace --graft und refs/replace/* mitliefern. Die Historie bleibt formal unverändert, Git interpretiert sie nur anders. Nachteil: Jeder Klon braucht einen zusätzlichen Fetch, und wer ihn vergisst, sieht eine andere Historie als alle anderen — ein stiller Unterschied, der sich erst im Konfliktfall zeigt.

B: Einmaliger echter Merge, danach normale Merges.

⚠️ Praezisierung nach dem Test: --allow-unrelated-histories allein liefert einen ZWEI-Wege-Vergleich und damit 1757 Konflikte — gemessen. Der Graft ist kein Gegenentwurf zu B, sondern sein Werkzeug: lokal setzen, den Merge damit rechnen lassen (32 Konflikte), committen. Der Merge-Commit traegt danach die echten Eltern, der Graft kann weg, und die Abstammung laeuft ueber den Merge-Commit selbst. Die Nahtstelle wird ein sichtbarer Merge-Commit. Kein Sonderwissen, kein Zusatzschritt, kein Klon kann sie versehentlich übersehen.

C: So weiterarbeiten wie bisher — Patches von Hand auftragen. Verworfen: Das ist der Zustand, der die stille Klasse überhaupt erst erzeugt.

Entscheidung

Option B (sorb, 2026-08-19).

Ausschlaggebend ist nicht der Aufwand — beide Wege sind ähnlich billig — sondern die Sichtbarkeit. A funktioniert nur, solange alle daran denken; B trägt sich selbst. Für ein Repo, dessen Kernproblem „Git meldet nichts" ist, wäre ein Mechanismus, der stillschweigend unterschiedlich wirkt, die falsche Wahl.

deadd548 bleibt trotzdem wichtig: Es ist der Stand, gegen den der Merge gefahren wird, und ohne diese Messung wäre der Merge auf eine erfundene Grundlage gelaufen.

Konsequenzen

  • Der erste Merge ist ein einmaliger Kraftakt mit vier Quellcode-Konflikten; danach ist ein Upstream-Update ein gewöhnlicher Merge.
  • Die stille Klasse verschwindet: Verschiebt Upstream eine Datei, die wir angefasst haben, meldet Git künftig einen Konflikt, statt unsere Zeilen wortlos fallen zu lassen.
  • Die Nahtstelle bleibt als Merge-Commit dauerhaft sichtbar — gewollt, nicht geduldet.
  • Abnahme ist kein Build, sondern ein Funktionstest: Die ClamAV-Patches liegen in der Medien-Pipeline. Ohne den Test aus #0099 (verschlüsselte Datei senden, abgelehnte empfangen) ist der Merge nicht abgenommen.
  • Schritt 4 aus #0099 — ein Verfahren zum Auftragen der Patches — wird damit gegenstandslos.

Nachtrag 2026-08-19: ausgeführt, und was die Ausführung korrigiert

Vollzogen. ThreadNet-Web:main steht auf 8ca03fe, der Merge-Commit 88c4e15 trägt beide echten Eltern. Produktion läuft auf v0.6.0. Zwei Annahmen dieser ADR haben sich in der Praxis verschoben:

Die stille Klasse verschwindet nicht ganz — sie dreht sich um. Die Entscheidung begründet sich damit, dass Git künftig meldet, wenn Upstream eine Datei verschiebt, die wir angefasst haben. Das hat gehalten (RoomListItemWrapper-Umbenennung wurde als Konflikt gemeldet). Was ein Drei-Wege-Merge nicht meldet, ist der umgekehrte Fall: Beide Seiten überleben die Auflösung, aber nur eine ergibt noch Sinn. Genau das brach v0.6.0-rc.2 in Produktion — eine getValue-Zeile auf einen von Upstream entfernten Einstellungsschlüssel, die niemand mehr liest und die trotzdem wirft.

Ein grüner Build ist keine Abnahme, und war nie eine. Diese ADR sagt richtig „Abnahme ist kein Build, sondern ein Funktionstest" — gemeint war der ClamAV-Test. Der rc.2-Vorfall zeigt die Lücke davor: Der CI-Job web baut nur, webpack entfernt Typen ohne sie zu prüfen. tsc meldete den Fehler durchgehend, gefragt hatte ihn niemand. Seit 8ca03fe führt docker_web den Job typecheck als needs; kein Image entsteht mehr ohne bestandene Typprüfung.

Daraus die stehende Regel für künftige Upstream-Merges: Nach der Konfliktauflösung gehört eine Prüfung auf überlebende Reste — nicht nur auf verlorene Zeilen. Der billigste Hebel ist die Typprüfung; sie fand beide Reste sofort. Wo Zeichenketten statt Typen im Spiel sind (Einstellungsschlüssel, Feature-Namen, Übersetzungs-IDs), reicht sie nicht, und es braucht einen Abgleich gegen die jeweilige Registry — für Einstellungen wurde er einmalig gefahren: 135 abgefragte gegen 152 registrierte.