Decision sorb: the filter is live in v0.5.4, proven in real calls on both engine families, gated for rollback, and regression-tested on all three silent failures found along the way. The phone test stays deliberately deferred; if it becomes necessary it is a new issue, not a reopen. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
21 KiB
type, id, status, created, milestone, priority, area, related
| type | id | status | created | milestone | priority | area | related | |
|---|---|---|---|---|---|---|---|---|
| issue | 0054 | done | 2026-08-15 | M4 | medium | element |
|
Tastaturgeräusche in Calls: quelloffene KI-Geräuschunterdrückung im Client prüfen
Problem
Der WebRTC-Standardfilter (noiseSuppression) ist auf stationäres Rauschen ausgelegt
(Lüfter, Netzbrummen). Transiente Geräusche — Tastaturanschläge — rutschen durch: sie
haben eine sehr schnelle Anstiegszeit und ein unvorhersehbares Spektrum, sodass die
laufende Rauschprofil-Schätzung sie nicht als Störung erkennt. Betroffen sind ausdrücklich
auch leise Chiclet-Tastaturen (MacBook), nicht nur mechanische.
Grundlage ist eine externe Architekturspezifikation (Gemini Deep Research, 2026-07-29):
client-seitige KI-Filterung via WebAssembly, eingehängt über das LiveKit-TrackProcessor-
Interface, mit Intensitätsregler in den Audio-Einstellungen.
Bewertung der Spezifikation
Trägt
- Diagnose stimmt. Stationär vs. transient ist die richtige Erklärung dafür, warum die vorhandenen Toggles nicht helfen.
- Client-seitig ist der richtige Ort — und kein Widerspruch zur bisherigen Linie.
threadnet-call:docs/axion1337-fork.md§5 verwirft ML-Rauschunterdrückung server-seitig (LiveKit Agents), weil es dort keinen unterstützten Weg gibt, bereinigtes Audio an andere Teilnehmer weiterzureichen. Genau dieser Einwand greift client-seitig nicht. Die Spezifikation setzt die alte Entscheidung fort, statt ihr zu widersprechen. - AudioWorklet statt ScriptProcessor, eigener hochpriorer Audio-Thread: richtig und nicht verhandelbar.
- Der 128-vs-480-Sample-Mismatch (Web Audio liefert 128er-Blöcke, die Modelle brauchen 480) und der nötige Ringpuffer sind sauber benannt — daran scheitern naive Umsetzungen.
- Chromium-AudioWorklet-Leak und die Gegenmaßnahme (eigener
AudioContext, hart schließen) sind real und richtig adressiert. - Wasm SIMD ist tatsächlich Voraussetzung, nicht Optimierung.
Trägt nicht
- ⚠️ Konkreter Fehler: die Latenzkompensation fehlt. Der finale Dry/Wet-Code mischt das
unverzögerte Original mit dem ~40 ms verzögerten KI-Signal. Das erzeugt Kammfilter und
Phasenauslöschung — hörbar als blechernes Echo, also genau das Gegenteil des Ziels. Ein
früherer Entwurf im selben Gespräch hatte dafür einen
DelayNode; in der Endfassung ist er verschwunden. Das ist kein Detail. - Der Dry/Wet-Ansatz ist konzeptionell fragwürdig. Die Begründung („neuronale Netze kennen nur An/Aus") ist für DeepFilterNet falsch — es hat einen nativen Parameter zur Begrenzung der Dämpfung. „Weniger aggressiv" heißt richtig: das Modell weniger dämpfen lassen. Dry/Wet mischt stattdessen ungefiltertes Signal zurück — inklusive der Tastaturanschläge, die man loswerden wollte.
- Die Zahlen taugen nicht als Entscheidungsgrundlage. PESQ „RNNoise ~3.88" gegen „DFN3 3.5–4.34": die untere DFN3-Grenze läge unter RNNoise. Werte aus verschiedenen Testsets, nicht vergleichbar.
- Bundle-Größe geschätzt, nicht gemessen („15–25 MB"). Für eine Browser-App, die beim Call-Start lädt, ist das der kritische Wert überhaupt — muss gemessen werden.
- Die genannten NPM-Pakete sind Experimente (
deepfilternet3-worker-test,…-noise-filter-trong). Die Spec empfiehlt selbst, aus dem Rust-Quellcode zu bauen — dann gehört ehrlich dazu: wir übernehmen eine Rust/wasm-Toolchain in die Build-Kette. - Mobil fehlt. Element Call läuft auf Telefonen; DFN3 auf einem Mittelklasse-Android ist offen und wird mit „läuft auf modernen Prozessoren" abgetan.
getUserMedia-Constraints fehlen im Code. Wer selbst filtert, muss die Browser-eigenenoiseSuppressionabschalten (sonst arbeiten zwei Filter gegeneinander) undechoCancellationerhalten. Im Gespräch erwähnt, im finalen Code verschwunden.- Erzwungene 48 kHz ohne Fallback — Geräte mit 44,1 kHz brauchen einen Pfad.
- ⚠️ Der größte Posten fehlt ganz: Fork-Wartung. Das wäre eine erhebliche
Eigenentwicklung in
threadnet-call, die bei jedem Upstream-Rebase mitgeschleppt und inaxion1337-fork.mdgepflegt werden muss. Die Spec erwähnt das mit keinem Wort. - Lizenz nur behauptet. DeepFilterNet-Code ist MIT/Apache-2.0 — die Modellgewichte sind separat zu prüfen, bevor „null Lizenzkosten" behauptet wird.
Vorgeschlagenes Vorgehen (vor jeder Zeile Produktivcode)
- Messen statt annehmen. Reproduzierbarer A/B-Test mit mechanischer und Chiclet-Tastatur gegen die heutigen Toggles — belegt das Problem und liefert die Referenz für „besser".
- Wegwerf-Prototyp außerhalb des Forks. DFN3 als Wasm auf einer eigenen Testseite: Bundle-Größe, CPU und Latenz auf echten Geräten messen (inkl. Telefon). Erst diese Zahlen entscheiden über Modell und Machbarkeit.
- Regler über den Modellparameter, nicht über Dry/Wet. Falls doch Dry/Wet: Delay-Node zur Latenzkompensation ist Pflicht.
- Dann erst Integration als
TrackProcessorund Eintrag inaxion1337-fork.md.
Offen (Entscheidung sorb)
Ob der Aufwand lohnt. Die Plattform hat derzeit einen sehr kleinen Nutzerkreis; dem steht eine dauerhaft zu pflegende Fork-Anpassung mit Rust/wasm-Build gegenüber. Die Alternative — Tastaturgeräusche als hinnehmbar erklären und stattdessen Push-to-Talk bzw. bewusstes Stummschalten dokumentieren — ist billiger und sollte bewusst verworfen werden, nicht übersehen.
Prototyp gebaut und getestet 2026-08-15
Wegwerf-Aufbau außerhalb des Forks (deepfilternet3-noise-filter v1.3.0 + livekit-client,
lokale Testseite, Assets selbst ausgeliefert). Ziel war, die drei ungeklärten Punkte der
Spezifikation durch Messung zu ersetzen.
Ergebnis: es funktioniert — und zwar besser als nötig
Höreindruck sorb: „die Tastatur ist weg, Stimme klingt natürlich" — bei 35 % Dämpfung.
Das ist der wichtigste Einzelbefund, und er entscheidet die Reglerfrage:
- Der Dry/Wet-Mix der Spezifikation entfällt ersatzlos. Das Paket bietet
setSuppressionLevel(), und die wasm-Signatur trägtatten_lim— DeepFilterNet begrenzt die Dämpfung nativ. Die Prämisse der Spec („neuronale Netze kennen nur An/Aus") ist widerlegt. Damit entfallen zugleich der fehlende Delay-Node und das Phasenproblem — es gibt gar keinen zweiten Signalpfad mehr, der phasenversetzt zurückgemischt werden müsste. - 35 % statt 100 % als Vorgabe. Die Spec setzt „standardmäßig 100 % Filter-Aktivität"; gemessen reicht gut ein Drittel für „Tastatur weg und Stimme natürlich". Weniger Dämpfung heißt weniger Artefaktrisiko — der Standardwert sollte bei ~35 % liegen, nicht am Anschlag.
Gemessen (ersetzt die Schätzungen der Spec)
| Größe | Wert | Bemerkung |
|---|---|---|
| Download je Client | 23,27 MB | 15,66 MB df_bg.wasm + 7,61 MB Modell — Spec-Schätzung („15–25 MB") bestätigt, oberer Rand |
| Vergleich RNNoise | 2,0–4,6 MB | rund ein Zehntel (@jitsi/rnnoise-wasm, @shiguredo/rnnoise-wasm) |
| Lizenz Paket | Apache-2.0 oder MIT | wie behauptet; Modellgewichte kommen aus dem DeepFilterNet-Projekt |
⚠️ Befund, der die Paketwahl bestimmt: fremdes CDN
deepfilternet3-noise-filter lädt Modell und wasm zur Laufzeit von cdn.mezon.ai. Für
eine selbstgehostete Plattform ist das nicht hinnehmbar: jeder Teilnehmer meldet bei jedem
Call-Start seine IP an einen Dritten, und die Verfügbarkeit des Calls hinge an fremder
Infrastruktur.
Entschärft: assetConfig.cdnUrl ist konfigurierbar. Der Prototyp liefert die Assets
bereits lokal aus — der CDN-freie Betrieb ist damit nachgewiesen, nicht nur angenommen.
Für eine Integration hieße das: die 23 MB gehören mit ausgeliefert (Image/Ingress), nicht
nachgeladen.
Ebenfalls korrigiert gegenüber der Spec
Die getUserMedia-Constraints, die im finalen Spec-Code fehlten, sind im Prototyp gesetzt:
noiseSuppression: false (sonst arbeiten Browser-Filter und Modell gegeneinander),
echoCancellation: true.
Weiterhin offen — und entscheidungsrelevant
- CPU-Last (Jitter mit/ohne Filter) noch nicht abgelesen.
- Telefon. Die eigentliche Härteprobe: 23 MB Download und DFN3-Inferenz auf einem Mittelklasse-Gerät. Fällt das durch, braucht es einen Pfad (RNNoise als leichte Variante, oder Filter auf Mobilgeräten aus).
- Fork-Wartung bleibt der ungemessene Posten — die Anpassung muss jeden Upstream-Rebase überleben.
Entscheidung 2026-08-15 → ADR-0018
sorb: integrieren, opt-in mit Nachladen, Checkbox plus Regler, Standard 35 %. Festgehalten als ADR-0018.
Der Größeneinwand entfällt damit für alle, die den Filter nicht nutzen: erst das Einschalten löst den Download aus. Der Telefontest wurde bewusst ausgesetzt — vertretbar, weil der Filter auf schwachen Geräten schlicht aus bleibt.
Umsetzung (offen)
deepfilternet3-noise-filterals Abhängigkeit inthreadnet-call.TrackProcessoran den lokalen Audio-Track hängen;assetConfig.cdnUrlauf das eigene Deployment zeigen (nicht aufcdn.mezon.ai).- Assets (23,3 MB) in die Auslieferung aufnehmen.
- Audio-Einstellungen: Checkbox + Regler, Standard aus / 35 %.
getUserMedia:noiseSuppression: false,echoCancellation: true.- Eintrag in
threadnet-call:docs/axion1337-fork.mdals Fork-Anpassung mit Portier-Hinweis (§5 dort ergänzen — die Absage galt der Server-Seite und bleibt gültig).
Der Prototyp liegt außerhalb der Repos und ist Wegwerf-Material; er wird nicht eingecheckt.
Umgesetzt 2026-08-15 — threadnet-call 3f17001
Nach ADR-0018 gebaut. Alle Prüfungen des Repos grün: tsc 0, eslint 0, prettier sauber,
ECConnectionFactory.test.ts 9/9 (der Test deckt genau die geänderte
noiseSuppression-Logik ab), pnpm build erfolgreich.
| Datei | Änderung |
|---|---|
src/livekit/aiNoiseSuppression.ts |
neu — baut den TrackProcessor, undefined wenn ausgeschaltet |
src/settings/settings.ts |
ai-noise-suppression (false), ai-noise-suppression-level (35) |
ConnectionFactory.ts |
processor: in audioCaptureDefaults; Browser-NS aus bei aktivem Filter |
SettingsModal.tsx |
Checkbox + Regler im Audio-Tab (nicht im Entwickler-Tab) |
public/assets/dfn3/** |
Modell + wasm, selbst ausgeliefert |
Dockerfile |
gzip für das Modell-wasm |
docs/axion1337-fork.md |
§5b mit Portier-Checkliste |
Zwei Funde beim Bauen, die die Zahlen verbessern
- Der Dockerfile hätte die 23 MB ungzippt ausgeliefert. Sein
gzip-Glob greift nur auf oberster Ebene, das Modell-wasm liegt aber in einem Unterordner. Gemessen: 15,7 → 4,1 MB (26 %). Zusammen mit dem bereits komprimierten Modell sind es 11,7 statt 23,3 MB pro Client. Behoben. - Die 23 MB landen nicht im JS-Bundle. Verifiziert:
dist/assets/index-*.jsbleibt bei 2,9 MB, die Assets liegen daneben als statische Dateien. Das Opt-in-Nachladen funktioniert also wie entworfen — wer den Filter aus lässt, lädt nichts.
Entscheidung sorb: Assets ins Git
Statt beim Bauen zu holen. Begründung: ein Build-Zeit-Download von cdn.mezon.ai hätte genau
die Fremdabhängigkeit wieder eingeführt, die wir zur Laufzeit entfernt haben — nur verschoben.
So baut das Repo offline und aus sich heraus. Preis: +23 MB dauerhaft (bisher größte Datei:
1,4 MB), und dasselbe nochmal bei jedem Modell-Update.
Offen
- Image bauen und ausrollen — bis dahin läuft die Änderung nirgends.
- Echter Call zu zweit als Abnahme: der Prototyp lief gegen die eigenen Kopfhörer, nicht über die Leitung.
- Mobil weiterhin ungeprüft (ADR-0018, bewusst).
Rollout 2026-08-16 — Auslieferungsweg korrigiert, wartet auf Publish
⚠️ Beim Ausrollen kam heraus, dass die erste Umsetzung im Produktivpfad nicht funktioniert hätte. Element Call läuft nicht als eigenes Image. Der Weg ist:
threadnet-call → npm-Paket @sorb/threadnet-call-embedded → Gitea
→ ThreadNet-Web (webpack kopiert nach webapp/widgets/element-call/) → threadnet-web-Image
Der Embedded-Build setzt publicDir: false — Upstream begründet das damit, public/ enthalte
nur das Favicon. Seit die Modell-Assets dort liegen, stimmt das nicht mehr. Gemessen, nicht
vermutet: mit dem Upstream-Wert baut alles fehlerfrei, der Standalone-Build funktioniert, und
nur im Widget wäre der Filter tot (404 auf das Modell). Genau der stille Fehlschlag, der
ohne Prüfung des Auslieferungswegs live gegangen wäre.
Behoben in threadnet-call d270e0c: publicDir aktiviert, Version auf
0.19.2-threadnet.8, Fork-Doku um den Auslieferungsweg und den Rebase-Hinweis ergänzt
(diese eine Zeile ist der wahrscheinlichste stille Rückfall beim nächsten Rebase).
Verifiziert:
- URL-Auflösung im Widget:
…/widgets/element-call/assets/dfn3/v3/pkg/df_bg.wasm✓ - CI-Pipeline #371 grün,
build_embeddederfolgreich - Das CI-Artefakt enthält die Assets (24,3 MB) — nicht nur der lokale Build
Korrektur einer Zahl aus der Entscheidungsvorlage
Ich hatte beim Abfragen der Asset-Entscheidung gesagt, das npm-Paket wachse „von ~2 MB auf ~25 MB". Gemessen: 41 → 66 MB. Der Aufschlag stimmt (+24 MB), die Ausgangsbasis war falsch — das Paket enthielt bereits 17,6 MB Source-Maps und 16 MB Crypto-/Vision-wasm. Der relative Aufschlag ist also +60 %, nicht das Zwölffache.
Offen — beides bewusst nicht von mir ausgelöst
publish_npmsteht aufmanual. Laut Fork-Doku §4 ist das Absicht: „bewusst kein Automatismus — Veröffentlichen bleibt ein Akt." Auslösen gehört sorb.- Danach ThreadNet-Web: Abhängigkeit auf
0.19.2-threadnet.8anheben, bauen, Image in die Registry, Tag im gitops-Repo anheben. - Abnahme im echten Call zu zweit steht weiterhin aus.
Vorfall 2026-08-16: v0.5.0 brach das Entmuten — behoben in v0.5.1, Filter stillgelegt
⚠️ Das erste Produktivimage mit dem Filter (v0.5.0, embedded .8) hat das Entmuten fuer
ALLE gebrochen — nicht nur fuer Nutzer, die den Filter eingeschaltet hatten. Rueckrollung
auf v0.4.3 stellte den Dienst wieder her; die Ursache wurde ueber ein von sorb angefordertes
Diagnosefenster (v0.5.0 gezielt wieder ausgerollt, Browser-Konsole gesichert) festgenagelt.
Zwei getrennte Fehler derselben Aenderung:
- Filter an:
LocalAudioTrack.setProcessor()verlangt einen AudioContext auf dem Track. Den gibt es nur mitwebAudioMixbeim Raum-Bau — und das steht in Element Call als unerprobtes Upstream-TODO auskommentiert. Konsole: "Audio context needs to be set on LocalAudioTrack in order to enable processors"; im SFU-Log erschien nie ein Track-Publish. ADR-0018 hat die Integrationstiefe unterschaetzt: der Prototyp verdrahtete seinen Audio-Graphen selbst und hat LiveKits Prozessor-Anbindung nie mitgeprueft. - Filter aus: Der Optionen-Bau setzte
processor: undefinedund schriebnoiseSuppressionum. LiveKit kopiert jeden Schluessel deraudioCaptureDefaultsbis in die getUserMedia-Constraints durch,undefinedeingeschlossen — in Safari brach damit das Entmuten trotz publiziertem Track. In Chromium unauffaellig; die fruehe Entlastung dieses Verdachts war ein Browser-uebergreifender Fehlschluss aus einem Chromium-Test.
Fix (threadnet-call dcc8643, embedded .9, ThreadNet-Web v0.5.1):
- Aus-Pfad als bedingtes Spread — identisch mit Upstream, kein
processor-Schluessel. - Feature-Tor
AI_NOISE_SUPPRESSION_AVAILABLE = false: neutralisiert auch Clients mit noch aktivierter Einstellung im localStorage (real existierender Fall), UI dahinter versteckt. - Zwei Regressionstests, auf dem alten Stand nachweislich rot — erst dadurch beweisen sie etwas.
Abnahme bestanden 2026-08-16: Call sorb+frank auf v0.5.1, Muten/Entmuten beidseitig, sorbs Client mit unveraendertem localStorage. Konsolen-Fehlerbild identisch mit dem funktionierenden v0.4.3-Stand (bekanntes Rauschen, als eigene Beobachtung notiert: einmaliger 401 auf ein m.call-State-Event, einmaliger LiveKit-Connect-Fehlversuch mit erfolgreichem Folgeversuch).
Lehre (Wiederholung des Sitzungsmusters): Die Auslieferungskette war penibel verifiziert — npm, node_modules, webpack, CI-Artefakt, Container, ausgelieferte URL. Geprueft wurde, ob die Dateien ankommen, nicht, ob die Funktion im Zielsystem tut. Der Call-Test zu zweit stand als „danach" auf der Liste statt als Voraussetzung. Fuer Aenderungen im Mikrofon-Pfad gilt ab jetzt: Abnahme im echten Call ist Rollout-Voraussetzung, nicht Nacharbeit.
Offen (Folge-Entscheidung, nicht Teil dieses Issues): Der Filter bleibt stillgelegt, bis
webAudioMix entschieden ist — Upstream-TODO mit Nebenwirkungen auf Echo-Unterdrueckung und
Ausgabegeraete-Wahl. Erst diese Entscheidung macht ADR-0018 umsetzbar oder widerlegt ihn.
Weg B umgesetzt (2026-08-16/17): v0.5.2 live, Tor zu, Abnahme ausstehend
Entscheidung sorb: Weg B — AudioContext nur auf dem lokalen Mikrofon-Track
(LocalAudioTrack.setAudioContext() unmittelbar vor setProcessor()), statt webAudioMix
am ganzen Raum. Kleinster Wirkradius: Wiedergabe, Ausgabegeraete-Wahl und Echo-Verhalten
bleiben unberuehrt.
Umsetzung (threadnet-call df4e5ee, embedded .10, ThreadNet-Web v0.5.2):
- Anbindung in
Publisher.onLocalTrackPublished, also erst nach der Publikation — ein scheiternder Filter kann das Entmuten per Konstruktion nicht mehr verhindern. - Verschaerfte Invariante:
audioCaptureDefaultstraegt in keinem Zustand einenprocessor-Schluessel mehr; Regressionstest deckt auch den Aktiv-Fall ab. - Tor bleibt zu; ein einzelner Test-Client aktiviert ueber zwei localStorage-Schluessel
(
ai-noise-suppression-dev+ normale Einstellung). Die normale Einstellung allein bleibt wirkungslos. 77 Tests gruen ueber die beruehrten Suiten.
Stolperstein aus dem ersten Testversuch: sorb suchte die Filter-Einstellung in den Client-Einstellungen — die UI ist aber absichtlich hinter dem Tor versteckt, und sie sass auch vorher nicht in Element Web, sondern in den Einstellungen im laufenden Call-Widget. Der Testweg ist in dieser Phase bewusst UI-los (Konsole); ausserdem kein privater Tab (eigener, leerer localStorage) und harter Reload noetig. Fuer die Tor-Oeffnung notiert: die Wiederauffindbarkeit der Einstellung gehoert in die Anwenderdoku.
Offen — Abnahme in zwei Stufen (Rollout-Voraussetzung fuer die Tor-Oeffnung):
- Normaler Call zu zweit ohne Schalter: muss sich exakt wie v0.5.1 verhalten.
- Test-Client mit beiden Schluesseln: Konsole meldet den aktiven Filter, ~23 MB dfn3 laden erst beim Entmuten, Gegenseite hoert keine Tastatur, Entmuten funktioniert weiterhin.
Erst danach: Tor oeffnen + UI einblenden (v0.5.3). Alternativ bleibt Abbruch (Option C) jederzeit moeglich — v0.5.2 ist fuer alle Nutzer verhaltensgleich mit v0.5.1.
Nachtrag 2026-08-17/18: Safari sendete ungefiltert — behoben in v0.5.4
Nach der Tor-Oeffnung (v0.5.3) meldete sorb: Filter wirkungslos, Staerke 0-100 ohne jeden
Unterschied, sauberer Alleintest (Hoergeraet gemutet). Auf anderen Rechnern (Chromium-Familie)
funktionierte derselbe Stand. Messungen mit einem lokalen Pruefstand (Playwright, echter
LiveKit-setProcessor-Pfad, Sprachsignal mit Klick-Transienten) ergaben: Prozessor und Modell
arbeiten korrekt — Sprache passiert, Klicks verschwinden.
Ursache: LiveKits setProcessor tauscht den Sender-Track per
this.sender?.replaceTrack(processedTrack). Ist der Sender in dem Moment nicht am Track
(Safari-Timing beim LocalTrackPublished-Event), wird der Tausch stumm uebersprungen — der
Prozessor laedt seine 23 MB, meldet Erfolg, und das rohe Mikrofon bleibt auf der Leitung.
Viertes Vorkommen des Sitzungsmusters "meldet Erfolg, ist aber blind", diesmal in Fremdcode
(das ?. verschluckt den Fehlschlag).
Zwei Irrwege der Diagnose, festgehalten weil lehrreich:
- Ein synthetischer Sinuston als "Stimme" liess den Filter wie einen Totalausfall aussehen — fuer ein Sprach-Modell ist ein Ton Rauschen, die Daempfung bis zum Limit war korrektes Verhalten am falschen Signal. Messsignale muessen dem Modell entsprechen.
- Zwei Geraete im selben Raum machen jeden Hoertest wertlos (Tastatur akustisch und ueber das zweite, ungefilterte Mikro hoerbar). Der brauchbare Alleintest: Hoergeraet gemutet mit Kopfhoerer.
Fix (threadnet-call fee9866, embedded .12, ThreadNet-Web v0.5.4): Nach dem Anhaengen
wird verifiziert, dass der RTCRtpSender den gefilterten Track traegt — notfalls wird auf den
Sender gewartet und der Tausch explizit erzwungen. Die Konsolen-Zeile weist den Zustand aus:
Sendepfad gefiltert: ja/NEIN. Drei Tests decken die Sender-Faelle ab.
Test sorb 2026-08-18 (Safari als sendender Client): bestanden. Damit ist der Filter auf beiden Engine-Familien nachgewiesen. Weiterhin offen und bewusst vertagt: Telefontest (mobil).
Geschlossen 2026-08-18 (Entscheidung sorb)
Der Filter ist in Produktion (v0.5.4), auf beiden Engine-Familien im echten Call nachgewiesen, mit Feature-Tor als Rueckhebel und Regressionstests auf allen drei stillen Fehlschlaegen des Wegs dorthin. Der Telefontest bleibt bewusst ausgesetzt (ADR-0018-Konsequenz: opt-in macht das vertretbar); sollte er noetig werden, ist das ein neues Issue, kein Wiederoeffnen.