Files
management/docs/issues/0099-threadnet-web-12-upstream-sicherheitsfixes-lassen-sich-nicht-m.md
Thore Cimbal 6731d2a70f ADR-0023: fremde Historie erklaert von der Git-Hygiene ausnehmen
Der Upstream-Merge holte 70.265 fremde Commits nach ThreadNet-Web; 39 davon
faerbten die Gruppenpruefung als Echtzeit-Stempel rot. Sie verletzen die
Konvention wirklich, konnten ihr aber nie folgen und werden sich nie aendern -
also ein Dauerrot, und ein Dauerrot meldet nichts mehr (#0104).

Neues Feld fremdhistorie in docs/components/*.md. Wo gesetzt, prueft die
Hygiene nur Commits, die von eigenen Identitaeten COMMITTET wurden. Der
Trennschnitt ist gemessen, nicht geraten: 18 eigene Commits, alle von uns
committet; 39 fremde von GitHub/RiotRobot; keine Ueberschneidung. Der Autor
taugt nicht - eigene Commits koennen fremde Autoren tragen (Cherry-Picks).

Der Feldwert ist die Begruendung, kein Schalter - Muster der Quittungen aus
ADR-0020. Die Ausnahme gilt nur, wo sie deklariert ist, nicht global; der Preis
(ein Commit unter voellig unbekannter Identitaet faellt dort durchs Raster)
steht in den Konsequenzen.

Belegt: nach dem Fix 0 offene Befunde bei 20 Quittungen, und in ThreadNet-Web
werden weiterhin 18 Commits geprueft, alle auf 12:00:00.

Ausserdem zurueckgenommen: mein Nachtrag an ADR-0022. Eine angenommene ADR wird
nicht editiert (Regel in der Vorlage) - die Erkenntnis steht jetzt in #0099.
2026-08-19 12:00:00 +00:00

407 lines
21 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
---
type: issue
id: "0099"
status: done
created: 2026-08-06
milestone: M1
priority: medium
projekt: threadnet-web
gitlab_iid: "12"
related: []
---
# Upstream-Sicherheitsfixes lassen sich nicht mergen — kein gemeinsamer Vorfahre
> Adoptiert aus [threadnet-web#12](https://git.lab/axion1337.chat/ThreadNet-Web/-/issues/12) (2026-08-18, ADR-0019). Kommentare und Verlauf bleiben dort; kanonisch ist ab jetzt diese Datei.
Am 2026-08-06 gemessen (`docs/axion1337-fork.md`, Abschnitt 4): **Dieses Repo hat keine Upstream-Historie.** Element Web 1.12.17 kam am 2026-05-10 als kompletter Baum herein — in `3da3635`, im selben Commit wie das erste eigene Feature.
Damit gibt es **keinen gemeinsamen Vorfahren mit `element-hq/element-web`**. `git merge upstream/develop` ist nicht möglich; erzwungen kollidiert praktisch jede Datei.
## Warum das ein Sicherheitsthema ist, kein Build-Thema
Element veröffentlicht Sicherheitsfixes als neue Version. Bei einem normalen Fork zieht man sie mit einem Merge. Bei uns bedeutet dasselbe: neuen Upstream-Stand beschaffen und **unsere 12 Patches von Hand neu auftragen** — davon sieben in der Medien-Pipeline, wo Element gerade auf MVVM umbaut.
Das ergibt eine unangenehme Kette mit gitops#22 (Advisory-Monitoring): Wir würden von einer Lücke erfahren und wären trotzdem langsam. Die Zeitspanne zwischen „bekannt" und „gepatcht" ist das, was zählt — und sie ist hier strukturell zu lang.
⚠️ Verschärfend: Verschiebt Element beim MVVM-Umbau eine der `viewmodels/`-Dateien, entsteht **kein Konflikt** — unsere Zeilen sind schlicht weg, und Git meldet nichts.
## Was zu tun ist
1. **Zuerst messen, nicht bauen:** Auf welchem Stand ist Upstream inzwischen, und sind seit 1.12.17 Sicherheitsfixes für Element Web erschienen? Das beantwortet, ob das dringend ist oder Vorsorge.
2. `element-hq/element-web` als zweiten Remote aufnehmen und den Tag von 1.12.17 holen. Damit lässt sich ein Update wenigstens **als Diff** betrachten, statt blind zu kopieren.
3. Prüfen, ob sich ein gemeinsamer Vorfahre nachträglich herstellen lässt — ein Graft/Replace des Import-Commits auf den passenden Upstream-Tag. Wenn das trägt, sind künftige Updates wieder ein Merge.
4. Falls nicht: einen Ablauf schreiben, wie unsere 12 Patches auf einen neuen Stand aufgetragen werden — mit dem Funktionstest für ClamAV als Abnahme (verschlüsselte Datei senden, abgelehnte empfangen).
Zusammenhang: gitops#22 (Advisory-Monitoring) ist die Erkennung, dieses Issue die Reaktionsfähigkeit. Das eine nützt wenig ohne das andere.
*Gefunden am 2026-08-06 beim Vermessen der Merge-Reibung (Arbeitspaket 3 aus #7).*
## Schritt 1 beantwortet 2026-08-19 — es ist keine Vorsorge mehr
Das Issue verlangt ausdrücklich „zuerst messen, nicht bauen". Gemessen:
**Wir sind neun Veröffentlichungen zurück.** Import war Element Web **1.12.17**
(2026-05-10); Upstream steht bei **v1.12.26** (2026-08-18).
**Und es gibt eine offene Sicherheitsmeldung, die uns betrifft:**
| | |
|---|---|
| Advisory | `GHSA-wrcp-5v3v-3j6v` |
| veröffentlicht | **2026-07-20** (vier Wochen offen) |
| Einstufung | medium |
| Inhalt | Ein bösartiger Homeserver kann über die Startseite HTML in Element Web einschleusen |
| betroffen | **< v1.12.22** — wir laufen 1.12.17 |
Die übrigen fünf Advisories im Projekt betreffen ältere Stände (≤ 1.11.x) und damit
nicht uns.
### Einordnung — real, aber schmal
Der Angriff setzt voraus, dass der Client auf einen **bösartigen Homeserver** zeigt.
Unsere Nutzer verwenden ihn gegen den eigenen Server, und Föderation ist seit
[ADR-0021](../adr/0021-foederation-geschlossen.md) geschlossen.
**Aber die Voraussetzung ist erreichbar:** `disable_custom_urls` ist in
`element-values.yaml` **nicht gesetzt**, Element erlaubt damit standardmäßig die
Eingabe eines beliebigen Homeservers — und die Desktop-Builds werden verteilt. Wer
sich mit unserem Client woanders anmeldet, ist ungeschützt.
### Was daraus folgt
Die Antwort auf „dringend oder Vorsorge" lautet: **weder Panik noch Vorsorge, sondern
überfällig.** Ein Fork ohne Merge-Weg hat vier Wochen gebraucht, um eine bekannte
Lücke *nicht* zu schließen — genau die Zeitspanne, die dieses Issue als das
eigentliche Problem benennt.
**Sofort möglich, unabhängig vom Merge-Problem:** `disable_custom_urls: true` setzen.
Das nimmt dem Advisory für unsere Nutzer die Voraussetzung, kostet eine Zeile und
berührt die Fork-Frage nicht. Ob fremde Homeserver überhaupt erlaubt sein sollen, ist
ohnehin eine eigene Entscheidung — bei geschlossener Föderation liegt „nein" nahe.
Schritte 24 (Upstream als Remote, Graft-Versuch, Patch-Auftragsverfahren) bleiben
offen; sie sind der eigentliche Umfang dieses Issues.
## Teilmaßnahme 2026-08-19 — `disable_custom_urls` an beiden Stellen
Entscheidung sorb. Gesetzt in **zwei** Dateien, nicht in einer:
| Ort | Datei | Commit | wirksam |
|---|---|---|---|
| Web-Client | `gitops:apps/production/custom-configs/element-values.yaml` | `d809b3f` | mit dem Flux-Abgleich (Element liest `config.json` beim Laden) |
| Desktop-Client | `ThreadNet-Web:apps/desktop/axion1337/config.json` | `e1e9a19` | **erst mit dem nächsten Desktop-Build** |
Die zweite Stelle wäre fast durchgerutscht: Der Desktop-Client lädt seine **eigene**
`config.json`, nicht die des Web-Deployments — derselbe Fallstrick wie beim
Themes-Rollout. Eine Änderung nur in `element-values.yaml` hätte den Web-Client
abgesichert und genau die Builds unberührt gelassen, die verteilt werden.
### Was sie bewirkt — und was nicht
Im Code unserer Fassung nachgelesen, nicht angenommen:
- `ServerPicker.tsx`: Der **„Bearbeiten"-Knopf am Servernamen wird nicht gerendert**.
Über die Oberfläche ist der Homeserver damit nicht mehr wechselbar.
- `ErrorUtils.tsx`: Bei 401/403 benennt die Fehlermeldung den Server, statt generisch
zu bleiben — kleiner Nebengewinn für den Alltag.
⚠️ **Es ist eine Oberflächen-Sperre, keine technische.** `MatrixChat.tsx` übernimmt
`hs_url` weiterhin aus der URL — in zwei Registrierungs-Flüssen (mobile Registrierung,
Bestätigungslink) und **ohne** `disable_custom_urls` zu prüfen. Ein präparierter Link
bleibt davon unberührt.
**Für `GHSA-wrcp-5v3v-3j6v` heißt das:** Der bequeme, sichtbare Weg zu einem fremden
Homeserver ist weg, alle Wege sind es nicht. Die Maßnahme senkt die Wahrscheinlichkeit,
dass jemand versehentlich oder auf Zuruf woanders landet — sie ist **kein Ersatz** für
das Upstream-Update. Das bleibt Schritt 24 dieses Issues.
## Schritt 2 erledigt 2026-08-19 — und er widerlegt eine Annahme des Issues
`upstream` (`element-hq/element-web`) ist als zweiter Remote eingerichtet, `v1.12.17`
und `v1.12.26` sind flach geholt. Der Remote ist **lokale Konfiguration** und wird
nicht mitcommittet; die Einrichtung steht jetzt in
`ThreadNet-Web:docs/axion1337-fork.md` (Commit `fa5dcc5`).
### Der Import ist NICHT der Tag `v1.12.17`
Das Issue und die Fork-Doku behaupteten beide, „Element Web 1.12.17 kam am 2026-05-10
als kompletter Baum herein". Die erste Messung mit dem neuen Remote widerspricht dem:
| Messung | Ergebnis |
|---|---|
| `git diff --stat v1.12.17 3da3635` | **783 Dateien, +17443 / 10371** |
| nur bei uns vorhanden | **115 Dateien** |
| nur im Tag vorhanden | 62 Dateien |
Entscheidend sind zwei davon, weil sie datieren:
`apps/web/src/viewmodels/room/timeline/event-tile/body/TextualBodyViewModel.tsx` und
`packages/shared-components/src/core/roving/RovingTabIndex.tsx` — im Tag `v1.12.17`
**nicht vorhanden**, in `v1.12.26` **und in unserem Import vorhanden**. Der
`CHANGELOG.md` unseres Imports endet bei 1.12.17 (2026-04-30), weil er erst beim
Release fortgeschrieben wird.
**Die Grundlage ist also ein `develop`-Stand nach dem 1.12.17-Release** (1.12.18 kam am
2026-05-12), nicht der Tag. Ausgerechnet MVVM-Dateien belegen es — der Bereich, vor dem
dieses Issue ohnehin warnt.
### Folge für Schritt 3
**Ein Graft auf `v1.12.17` wäre falsch.** Er würde jede künftige Zusammenführung auf
eine erfundene Ahnenreihe stellen und Konflikte an Stellen erzeugen, wo keine sind —
oder schlimmer: keine erzeugen, wo welche gehören. Wer den gemeinsamen Vorfahren
nachträglich herstellen will, muss den passenden **`develop`-Commit** finden. Das
braucht die **volle** Upstream-Historie; ein flacher Tag-Fetch reicht dafür nicht.
Schritt 3 bleibt damit offen, aber er hat jetzt eine tragfähige Fragestellung statt
einer falschen Zielmarke.
### Unverändert: die Advisory-Bewertung
Der Basisstand liegt zwischen 1.12.17 und 1.12.18 und damit klar unter 1.12.22 —
`GHSA-wrcp-5v3v-3j6v` trifft uns weiterhin. Die Korrektur ändert die Dringlichkeit
nicht, nur den Weg dorthin.
## Schritt 3 beantwortet 2026-08-19 — der Graft trägt, und er halbiert das Problem
Volle Upstream-Historie geholt (70.330 Commits, `.git` wächst von 87 MB auf 603 MB).
Damit ließ sich der gesuchte gemeinsame Vorfahre **suchen statt raten**.
### Die Basis ist `deadd548` vom 2026-05-08
Verfahren: für jeden `develop`-Commit im Fenster 25.04.16.05. die Zahl der gegen
unseren Import abweichenden Dateien messen — der Basis-Commit ist das Minimum. Gemessen
wurde **der Baum, nicht das Datum**.
Ergebnis: `deadd548` („Update storybook", #33437) mit **43 abweichenden Dateien**
gegenüber **783** beim fälschlich angenommenen Tag `v1.12.17`. Und diese 43 zerfallen
sauber:
| Art | Anzahl | Was es ist |
|---|---|---|
| nur Modus (755 → 644) | **31** | der Import-Bug, den die Fork-Doku als historisch beschreibt |
| echte Inhaltsänderung | 12 | davon 5 + 1 Test = **unser Discord-Feature**, dazu `LICENSE` und `CHANGELOG.md` |
| Hinzufügungen | 2 | unsere Feature-Dateien |
Der inhaltliche Rest ist also **genau das, was wir damals selbst geändert haben**. Damit
ist `deadd548` der Stand, aus dem der Fork genommen wurde.
### Der Graft wurde getestet, nicht behauptet
In einem **Wegwerf-Klon** (Produktions-Repo unberührt, danach entfernt):
| | |
|---|---|
| vor dem Graft | `git merge-base main upstream/develop`**leer** |
| nach `git replace --graft 3da3635 deadd548` | gemeinsamer Vorfahre **`deadd548`** |
| `git merge upstream/develop` | läuft durch, **32 konfliktbehaftete Dateien** |
⚠️ Ein Zwischenergebnis war wertlos und wäre fast als Erfolg durchgegangen: Der erste
Merge-Versuch gegen `v1.12.26` meldete „0 Konflikte" — er war aber **gar nicht
gelaufen** („refusing to merge unrelated histories"), weil der Tag flach geholt war und
nur einen einzigen Commit trägt. Erst die Gegenprobe auf `MERGE_HEAD` deckte das auf.
### Was die 32 Konflikte sind
**Nur vier sind Quellcode** — und es sind genau unsere Patches:
`MImageBody.tsx`, `MAudioBody.tsx` (ClamAV), `SdkConfig.ts` (Branding),
`RoomListItemViewModel.ts` (Discord-Feature). Der Rest sind
`.github/workflows/*` (die Fork-Doku führt sie als „gering — eigene Strecke") und
`pnpm-lock.yaml`, das ohnehin regeneriert wird.
### Was das ändert
Aus „neuen Upstream-Baum beschaffen und **zwölf Patches von Hand neu auftragen**" wird
„**vier Quellcode-Konflikte in einem normalen Drei-Wege-Merge** lösen".
Wichtiger noch ist die stille Gefahr, die verschwindet. Das Issue warnt: *„Verschiebt
Element beim MVVM-Umbau eine der `viewmodels/`-Dateien, entsteht kein Konflikt — unsere
Zeilen sind schlicht weg, und Git meldet nichts."* Mit echtem gemeinsamem Vorfahren
**meldet Git es**. Das ist der eigentliche Gewinn, nicht die Zahl.
### Entscheidung, die noch aussteht
Der Graft ist eine **lokale** Ersetzung (`refs/replace/*`). Damit alle davon haben,
müsste er mitgeliefert werden — `git push origin 'refs/replace/*'` und ein Hinweis in
der Doku, dass Klone `git fetch origin 'refs/replace/*:refs/replace/*'` brauchen.
Alternative: kein Graft, sondern beim nächsten Update einmalig einen echten
Merge-Commit mit `--allow-unrelated-histories` bauen und ab dann normal weitermergen.
Beides ändert die Historie-Wahrnehmung des Repos dauerhaft und ist damit
**ADR-pflichtig**. Entscheidung sorb steht aus; Schritt 4 (Auftragsverfahren für die
Patches) wird bei Weg 1 oder 2 weitgehend gegenstandslos.
## Weg B belegt 2026-08-19 — mit einer wichtigen Präzisierung
Entscheidung sorb: **Weg B** ([ADR-0022](../adr/0022-upstream-anschluss-durch-einmaligen-merge.md)).
### `--allow-unrelated-histories` allein genügt NICHT
Gemessen gegen `v1.12.26`, im selben Klon, nacheinander:
| Vorgehen | Konflikte |
|---|---|
| nur `--allow-unrelated-histories`, ohne Vorfahren | **1757** |
| mit lokal gesetztem Graft auf `deadd548` | **32** |
Ohne gemeinsamen Vorfahren macht Git einen Zwei-Wege-Vergleich — das ist die „praktisch
jede Datei konfliktet"-Lage aus der Fork-Doku. **Der Graft ist also kein Gegenentwurf zu
Weg B, sondern sein Werkzeug:** lokal setzen, den Merge damit rechnen lassen, committen —
der Merge-Commit trägt danach die echten Eltern, und der Graft kann weg. Ab dann läuft
die Abstammung über den Merge-Commit selbst. Das steht so nicht in ADR-0022s
Optionsbeschreibung und ist hier nachgetragen.
⚠️ **Stolperstein auf dem Weg dahin:** Die mit `--depth=1` geholten Tags tragen eine
`.git/shallow`-Grenze. `v1.12.26` war dadurch auf **einen** Commit abgemauert, obwohl
derselbe Commit über `develop` vollständig vorlag — jeder Merge-Versuch scheiterte mit
„refusing to merge unrelated histories", was wie ein Grundproblem aussah und keines war.
`git fetch --unshallow upstream` löst es.
### Die 32 Konflikte, vollständig aufgeschlüsselt
| Menge | Was | Auflösung |
|---|---|---|
| 24 | `.github/workflows/*` | unsere Fassung behalten — eigene CI-Strecke |
| 1 | `pnpm-lock.yaml` | regenerieren |
| 1 | `apps/web/package.json` | **Produktentscheidung, siehe unten** |
| 6 | Quellcode, je 12 Konfliktblöcke | von Hand |
**Der Fund, der die ganze Übung rechtfertigt:** Upstream hat
`RoomListItemAccessibilityWrapper` in `RoomListItemWrapper` **umbenannt**. Weil es jetzt
einen echten Vorfahren gibt, **meldet Git den Konflikt** — genau der Fall, den dieses
Issue als stille Gefahr beschreibt („unsere Zeilen sind schlicht weg, und Git meldet
nichts").
### Warum ich hier gestoppt habe
`apps/web/package.json` enthält zwei Zeilen, die keine Konfliktauflösung sind, sondern
Entscheidungen:
```
ours: "@sorb/threadnet-call-embedded": "0.19.2-threadnet.12"
upstream: "@element-hq/element-call-embedded": "0.22.0"
ours: "matrix-js-sdk": "github:matrix-org/matrix-js-sdk#d19cb751..."
upstream: "matrix-js-sdk": "42.2.0"
```
Die erste falsch aufgelöst, und die **gesamte KI-Geräuschunterdrückung (#0054) ist
stillschweigend weg**. Die zweite betrifft den Git-Ref-Pin, der seinerzeit ein Build-Fix
war (Abschnitt 3 der Fork-Doku) — ob Upstreams 42.2.0 ihn erübrigt, zeigt nur ein Build.
Beides ist ohne Build und ohne den ClamAV-Funktionstest nicht absicherbar. Der Merge ist
damit **vorbereitet und vermessen, aber nicht ausgeführt**; das Experiment wurde
abgeräumt (kein Zweig, kein Replace-Ref, Arbeitsverzeichnis sauber). Die Upstream-Historie
bleibt im Klon liegen, damit der nächste Anlauf nicht wieder 600 MB holen muss.
**Nächster Schritt:** den Merge als eigenes Vorhaben fahren, mit Build und
ClamAV-Abnahme als Voraussetzung — nicht als Nebenschritt.
## Ausgeführt und abgenommen 2026-08-19 — `v0.6.0` läuft in Produktion
Der Merge ist vollzogen. `ThreadNet-Web:main` steht auf `8ca03fe`; der Merge-Commit
`88c4e15` trägt beide echten Eltern (`fa5dcc5` = unser bisheriger `main`,
`c43ef70b` = `v1.12.26`). Damit ist die Abstammung hergestellt, der Graft ist weg,
und `GHSA-wrcp-5v3v-3j6v` ist mit dem Versionssprung erledigt.
### Die beiden Produktentscheidungen aus dem Abschnitt davor
| Zeile | Auflösung | Beleg |
|---|---|---|
| `@sorb/threadnet-call-embedded` | **unsere** behalten, `0.19.2-threadnet.12` | auf `main` und im Zweig identisch — die KI-Geräuschunterdrückung (#0054) ist unberührt |
| `matrix-js-sdk` | **Upstreams** `42.2.0` genommen, Git-Ref-Pin fällt weg | Build läuft; der seinerzeitige Build-Fix hat sich erübrigt |
### Drei Anläufe, zwei davon gescheitert
| | |
|---|---|
| `v0.6.0-rc.1` | **kein Image.** `docker_web` scheiterte: `.npmrc` stand nie in der `COPY`-Zeile des Dockerfiles. Unter pnpm 10 folgenlos, weil `--frozen-lockfile` die gepinnte Tarball-URL nahm; pnpm 11 prüft mit `minimumReleaseAgeStrict` das Alter jedes Eintrags, löst den Scope wieder auf und landet ohne `.npmrc` bei npmjs → 404. Genau der Ablauf, den die `.npmrc` selbst vorhersagt (#0055). |
| `v0.6.0-rc.2` | **ging live und brach die Raumliste.** Nach wenigen Minuten auf `v0.5.4` zurückgenommen. Siehe unten. |
| `v0.6.0-rc.3` | Abnahme bestanden, als `v0.6.0` freigegeben. |
### Der rc.2-Vorfall — eine stille Leiche der anderen Sorte
`RoomListItemViewModel.ts` rief `SettingsStore.getValue("feature_room_list_sections")`
auf einen Labs-Schalter, den Upstream **entfernt** hat; Sektionen laufen dort über
`RoomList.showSections`. Die Auflösung hatte überall Upstreams Seite genommen — Menü,
View, Snapshot, Typen — und nur diese eine `const`-Zeile aus unserer Seite stehen
lassen. Sie wurde tree-weit von niemandem gelesen und warf trotzdem: bei **jedem**
Raumlisteneintrag, als `react-soft-crash`.
Bemerkenswert ist die Richtung: Dieses Issue warnt vor Zeilen, die *verschwinden*.
Hier ist eine Zeile **übrig geblieben**, die verschwinden musste. Der Merge meldet
Konflikte, wo Dateien wandern — er merkt aber nicht, wenn beide Seiten überleben und
nur eine davon noch Sinn ergibt.
Abgesichert statt gehofft: alle **135** im Quellbaum abgefragten Einstellungen gegen
die **152** in `Settings.tsx` registrierten verglichen — genau diese eine Leiche,
keine weitere. Ein zweiter Rest derselben Art (ungenutzter Import
`ElementDesktopLogoSvg` in `SdkConfig.ts`) war harmlos; geprüft wurde dabei
ausdrücklich, ob unser Rebrand gelitten hat — hat er nicht, `desktopBuilds` trägt
weiter eigenes Logo und eigenen Release-Pfad.
### Warum kein Build das fangen konnte — und was daraus folgt
Der CI-Job `web` führt ausschließlich `pnpm --dir apps/web build` aus. webpack
entfernt Typen, ohne sie zu prüfen; ein unbekannter Einstellungsschlüssel ist zur
Bauzeit bloß ein String. `tsc` dagegen meldete den Fehler die ganze Zeit — **zweimal**
(`TS2345` unbekannter Schlüssel, `TS6133` ungenutzte Konstante). Gefragt hatte ihn
niemand.
Konsequenz, umgesetzt in `8ca03fe`: neuer Job `typecheck`, den `docker_web` als
`needs` führt. **Kein Image mehr ohne bestandene Typprüfung.** Maßstab ist „kein
Fehler außerhalb von `node_modules`", weil Upstream v1.12.26 selbst nicht typrein ist
`matrix-js-sdk@42.2.0` wirft drei Fehler in der eigenen Quelle, in einem sauberen
v1.12.26-Checkout gegengeprüft.
⚠️ Das Tor wäre beim Bau selbst fast wertlos geworden: Das erste `grep "error TS"`
hätte nie gegriffen, weil nx auch in der Pipe färbt und zwischen `error` und `TS` eine
Escape-Sequenz steht. Es ist jetzt in **beide** Richtungen belegt — mit wieder
eingesetzter Zeile scheitert es und benennt beide Fehler, ohne sie besteht es — und
scheitert zusätzlich bei leerer `tsc`-Ausgabe, damit ein stiller Erfolg nicht als
Prüfung durchgeht.
### Was ADR-0022 dabei nicht vorhergesehen hat
Die ADR begründet die Entscheidung damit, dass Git künftig **meldet**, wenn Upstream
eine Datei verschiebt, die wir angefasst haben. Das hat gehalten — die Umbenennung
`RoomListItemAccessibilityWrapper``RoomListItemWrapper` kam als Konflikt.
Was ein Drei-Wege-Merge **nicht** meldet, ist der umgekehrte Fall: Beide Seiten
überleben die Auflösung, und nur eine ergibt noch Sinn. Die stille Klasse verschwindet
also nicht, sie dreht sich um — aus „unsere Zeile ist weg" wird „ihre Zeile ist noch
da". Für künftige Merges heißt das: nach der Konfliktauflösung auf **überlebende**
Reste prüfen, nicht nur auf verlorene. Der billigste Hebel ist die Typprüfung, sie fand
beide 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.
(Kein Nachtrag in ADR-0022 selbst: Eine angenommene ADR wird nicht editiert.)
### Die Abnahme, wie dieses Issue sie verlangt
Gefordert war „verschlüsselte Datei senden, abgelehnte empfangen" — geprüft am
laufenden System, nicht am Build:
| Prüfpunkt | Ergebnis |
|---|---|
| Raumliste lädt | ✅ mit konfigurierten Sektionen, also im kritischen Pfad |
| ClamAV Sendepfad | ✅ blockiert vor dem Upload — **ein** Scan-Aufruf, kein zweiter |
| ClamAV Empfangspfad (Datei) | ✅ EICAR erkannt, Abweisung wird angezeigt |
| ClamAV Bildpfad (`.png`) | ✅ zugestellt, beim Herunterladen abgewiesen, **Meldung gerendert** |
| Call-Teilnehmerliste | ✅ |
Der `.png`-Fall ist der wichtigste: Er ist der einzige, der den portierten Code
`ImageBodyViewModel.computeErrorLabel()` tatsächlich durchläuft. Wäre die Datei schon
beim Senden geblockt worden, hätte der Test den Sendepfad ein zweites Mal geprüft und
den Bildpfad gar nicht — beides sieht im Scanner-Log gleich aus, weil die Meldung
`flagged an upload/download` nicht unterscheidet.
### Stand
`v0.6.0` ist in Produktion (`gitops:d87c432`), `/version` liefert `0.6.0`.
Rückhebel bleibt der Tag-Revert auf `v0.5.4`.
**Alle vier Schritte dieses Issues sind beantwortet**; Schritt 4 ist wie in ADR-0022
vorhergesagt gegenstandslos geworden. Das nächste Upstream-Update ist ein gewöhnlicher
Merge.