Skip to content

release: 2.0.1 - #20

Merged
Henner4746 merged 14 commits into
mainfrom
release/2.0.1
Oct 24, 2026
Merged

Henner4746 merged 14 commits into
mainfrom
release/2.0.1

Conversation

@Henner4746

Copy link
Copy Markdown
Owner

Entwurf, wartet auf Henriks Ja. Fassungssprung auf 2.0.1+23 auf dem Stand von main plus #19 (Audit-Fixes; solange #19 offen ist, stehen dessen Commits hier mit im Diff).

Noch nicht enthalten (kommt nach Tag und Signieren, Checkliste Phase 6): Downloads und Hashes auf der Seite und in den READMEs, Badges, install.sh REF.

Ablauf: C:\KI-Workstation\BitDM-RELEASE-2.0.1-CHECKLISTE.md. Reihenfolge beim Ausrollen: Zwischenlager → Relay → App.

🤖 Generated with Claude Code

Henner4746 and others added 14 commits October 23, 2026 23:45
…s Lager (SV-1, SV-2)

Audit vom 16.10.2026, SV-1 und SV-2 (beide Mittel).

SV-1: Jede Anfrage nach einer Lager-Marke rechnete zwei SELECT SUM ueber
blob_marken, synchron im WebSocket-Handler und damit auf dem einzigen
Event-Loop: je Adresse ueber bis zu 25 600 Zeilen (gemessen 76 ms), ueber
den ganzen Relay ueber bis zu 204 800 (498 ms). Solange lief keine
Zustellung, keine Anmeldung, keine Quittung - fuer alle Verbindungen.

SV-2: Die Tagesmenge des ganzen Relays (200 GiB) wurde beim AUSSTELLEN
der Marken angerechnet. Eine Marke kostet keinen Upload; neun kostenlose
Adressen holten sich die 200 GiB in Sekunden, luden nichts hoch, und
danach bekam jeder Nutzer 24 Stunden lang "Zwischenlager fuer heute
ausgelastet". Der Relay kann das nicht selbst beheben: er erfaehrt nie,
ob hochgeladen wurde, und erreicht das Lager nicht.

Behebung:
- relay_server.py: blob_marken haelt eine Zeile je Adresse und
  angefangener Stunde (blob_menge_anrechnen), nicht mehr eine je Marke.
  Die Summe je Adresse laeuft damit ueber hoechstens 25 Zeilen, und die
  Tabelle waechst mit den Adressen statt mit den Marken. Gezaehlt werden
  Stunden, deren Ende in den letzten 24 Stunden liegt (BLOB_FENSTER =
  25 h, die strengere Richtung). Alte Einzelzeilen zaehlen bis zu ihrem
  Ablauf weiter.
- relay_server.py: die Summe ueber den ganzen Relay und
  BLOB_TAGESMENGE_GESAMT sind entfernt. Die Grenze je Adresse bleibt beim
  Ausstellen (wer Marken verfallen laesst, verbraucht sein eigenes
  Kontingent).
- blob_server.py: das Lager zaehlt die Tagesmenge des ganzen Lagers nach
  Bytes, die wirklich unter ihrem Namen liegen (TAGESMENGE_GESAMT, Env
  BITDM_BLOB_QUOTA_TOTAL, 200 GiB), je Stunde, mit Vormerkung laufender
  Uploads und Nachzaehlen aus den Aenderungszeiten nach einem Neustart.
  Abgebrochene Uploads kosten nichts. Am Dach 507 "Zwischenlager fuer
  heute ausgelastet" - fuer die App seit jeher LagerVoll ("spaeter noch
  einmal"), also auch fuer 2.0.0 ohne Aenderung verstaendlich.
- docs/ZWISCHENLAGER.md und deploy/README.md nachgezogen.

Verträglichkeit: kein Rahmen und keine Antwort des Relays aendert sich
fuer die App; "Zwischenlager fuer heute ausgelastet" kommt jetzt vom
Lager als 507 statt vom Relay als Fehlerrahmen. Beim Aufspielen zuerst
das Lager, dann den Relay - sonst gibt es dazwischen keine Gesamtgrenze
ausser BITDM_BLOB_MIN_FREE.

Tests (ohne die Behebung rot, gegengeprueft):
test_sv1_marken_einer_stunde_sind_eine_zeile,
test_sv1_keine_summe_ueber_den_ganzen_relay,
test_sv2_marken_anderer_adressen_sperren_niemanden_aus (ersetzt
test_die_tagesmenge_des_ganzen_relays, die den Befund festschrieb),
test_blob.py::test_sv2_* (Tagesmenge nach geschriebenen Bytes,
Abbruch, Vormerkung, Neustart, Fenster).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…(KR-2, Server)

Audit vom 16.10.2026, KR-2 (Mittel, mit SV-9 zusammengefuehrt).

Bis 2.0.0 signiert die App bei der Anmeldung das Nonce des Relays roh:
/ws `nonce` bzw. `nonce || uint32be(geraet)`, /register
`nonce || sha256(buendel)` - ohne Zweck, ohne Relay, ohne Adresse. Den
Teil "fremde Formate signieren" (33 Byte 0x05||K, 64 Byte Registrierung)
schliesst die App seit 2.0.0 selbst (Nonce genau 32 Byte). Offen war die
Domaenentrennung: ein boesartiger Relay A reicht die Challenge von Relay
B an das Opfer weiter und meldet sich mit der Signatur bei B als das
Opfer an (Warteschlange abholen und damit loeschen, Geraet mit 4409
verdraengen, eigenes Buendel registrieren).

Behebung im Relay - beide Formen werden angenommen:
- Neue Form, gekennzeichnet durch das Feld `relay` (der Name, unter dem
  die App den Relay erreicht hat):
    /ws:       "BitDM-Relay-Anmeldung-v1\n" host "\n" adresse "\n"
               geraet "\n" nonce
    /register: "BitDM-Relay-Registrierung-v1\n" host "\n" nonce
               sha256(buendel)
  Angenommen nur fuer Namen aus BITDM_RELAY_NAMEN; eine Signatur fuer
  einen anderen Relay gilt hier nicht. Das Geraet steht ausdruecklich in
  den Bytes, auch Geraet 1. Mit Geraeteliste unterschreibt der
  Geraeteschluessel dieselben Bytes hinter ANMELDE_PRAEFIX.
- Challenge und Antwort auf /register/challenge nennen die Namen
  (`relay`), wenn welche eingestellt sind; ohne BITDM_RELAY_NAMEN bleiben
  sie Byte fuer Byte wie bisher.
- Ohne `relay` gilt die alte Form unveraendert - die App 2.0.0 meldet
  sich weiter an. BITDM_ANMELDUNG_PFLICHT=1 weist die alte Form ab; das
  ist der Schalter fuer spaeter, Vorgabe aus.
- install-relay.sh traegt BITDM_RELAY_NAMEN=<domain> in die Unit ein,
  deploy/README.md beschreibt beide Variablen. Auf relay.bitdm.net muss
  beim Aufspielen "relay.bitdm.net,<onion>.onion" gesetzt werden.

Wirksam gegen den Angriff wird das erst mit der App-Seite (folgender
Commit) und endgueltig mit BITDM_ANMELDUNG_PFLICHT=1, wenn keine App vor
der neuen Form mehr unterwegs ist.

Tests: test_kr2_* in test_relay.py (neue Form meldet an, bindet Geraet,
fremder Relay-Name abgewiesen bei /ws und /register ohne Nonce-Verbrauch,
Geraeteliste, Schalter, Challenge mit/ohne Namen, Bytes fuer die App).
Ohne die Behebung rot: 7 von 11; die uebrigen 4 sind Gegenproben
(alte Form der App 2.0.0 gilt weiter, fremder Name wird abgewiesen).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…R-2, App)

Audit vom 16.10.2026, KR-2, App-Teil. Gegenstueck zum vorigen Commit im
Relay.

Die 32-Byte-Pruefung (seit 2.0.0) haelt fremde Formate ab, aber nicht die
weitergereichte Challenge eines anderen Relays. relay_client.dart
signiert jetzt in der neuen Form, wenn die Challenge (bzw. die Antwort
auf /register/challenge) im Feld `relay` den eigenen Namen nennt:
- /ws:       "BitDM-Relay-Anmeldung-v1\n" host "\n" adresse "\n"
             geraet "\n" nonce   (Geraet 1 ausdruecklich)
- /register: "BitDM-Relay-Registrierung-v1\n" host "\n" nonce
             sha256(buendel)
`host` ist baseUri.host - der Name, unter dem die App den Relay erreicht
hat, nie ein Wert des Relays. Die Antwort traegt `relay: host`; mit
Geraeteliste unterschreibt der Geraeteschluessel dieselben Bytes hinter
anmeldePraefix.

Nennt der Relay keine Namen (Relay bis 2.0.0) oder fehlt der eigene
darin (etwa die Onion-Adresse nicht eingetragen), bleibt es bei der
alten Form - kein Aussperren, in keiner Reihenfolge des Aufspielens.
Einem Relay, der die Liste absichtlich weglaesst, gibt die App nur, was
sie ihm bisher auch gab; ein Relay mit BITDM_ANMELDUNG_PFLICHT=1 nimmt
diese alte Form nicht mehr an.

Tests:
- test/net/anmeldung_kontext_test.dart: ein nachgebauter Relay prueft die
  Signatur gegen die ausgeschriebenen Bytes (neue Form mit Name, Geraet
  in den Bytes, Rueckfall ohne/mit fremder Liste, Unsinn im Feld), dazu
  der echte relay_server.py mit BITDM_ANMELDUNG_PFLICHT=1: ein
  vollstaendiger Nutzer kommt online. Ohne die App-Aenderung rot (neue
  Form, Geraet, echter Relay), gegengeprueft.
- test/support/relay_process.dart: Test-Relays laufen mit
  BITDM_RELAY_NAMEN=127.0.0.1, damit jeder App-Test gegen den echten
  Relay die neue Form faehrt (Registrierung, Geraeteliste, mehrere
  Geraete). test/net und test/core: 429 gruen.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…icht gibt (SV-11)

Audit vom 16.10.2026, SV-11 (Hinweis).

install.sh schloss mit "Settings > Receiving > server address,
wss://<domain>". Die App hat diese Einstellung seit dem 26.07.2026 nicht
mehr (docs/EIGENER-SERVER.md); der Relay wird beim Bauen gewaehlt
(--dart-define=BITDM_RELAY). Wer einen Relay aufsetzte, suchte die
Einstellung vergeblich und erfuhr nicht, warum. docs.html ("In der App
eintragen") sagte dasselbe.

Behebung: install.sh und docs.html sagen jetzt, dass die ausgelieferten
Apps nur mit relay.bitdm.net sprechen und wie man die App fuer einen
eigenen Relay baut (BITDM_RELAY, BITDM_RELAY_ONION, BITDM_LAGER), samt
Hinweis auf BITDM_RELAY_NAMEN fuer die Onion-Adresse (KR-2).

Test: test_sv11_install_sh_verweist_auf_keine_einstellung_die_es_nicht_gibt
(ohne die Behebung rot, gegengeprueft) - prueft auch, dass die App den
genannten Schalter BITDM_RELAY noch hat.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…SV-8)

Audit vom 16.10.2026, SV-8 (Hinweis).

push_endpunkt_gueltig prueft Schema, Host (push.bitdm.net) und Pfad
(/up...), aber nicht Port und Benutzerangabe.
"https://push.bitdm.net:<beliebig>/up..." wurde angenommen; ohne
BITDM_PUSH_TARGET (Vorgabe fuer fremde Relays, die hinausduerfen) ginge
der leere POST genau dorthin - an jeden Port der Maschine hinter
push.bitdm.net, sobald der Relay einmal hinaus darf.

Behebung: nur Port 443 oder keiner, keine Benutzer-/Passwortangabe; ein
Port ausserhalb 0..65535 gilt als ungueltig statt als Ausnahme.
UnifiedPush-Endpunkte haben beides nie, die App 2.0.0 ist nicht
betroffen.

Test: test_sv8_push_endpunkt_ohne_fremden_port_und_ohne_nutzer (ohne die
Behebung rot, gegengeprueft).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…(SV-10)

Audit vom 16.10.2026, SV-10 (Hinweis).

client_ip nahm den ersten Eintrag von X-Forwarded-For, gleich von wem die
Verbindung kam. Mit den Vorlagen in deploy/ war das nicht ausnutzbar
(nginx ersetzt die Kopfzeile). Lauscht ein selbst betriebener Relay aber
versehentlich ohne nginx nach aussen, genuegten
`X-Forwarded-For: 127.0.0.1` und `X-BitDM-Onion: 1`, und jede Bremse je
Herkunft war aus.

Behebung: client_ip liefert request.client.host. Die Kopfzeile wertet
uvicorn bereits aus, und nur, wenn der unmittelbare Gegenueber nginx auf
der Schleife ist (--proxy-headers --forwarded-allow-ips 127.0.0.1 in der
Unit, bei uvicorn ohnehin die Vorgabe). Hinter nginx kommt damit dieselbe
IP heraus wie bisher; von jedem anderen Gegenueber dessen eigene. Damit
zaehlt auch X-BitDM-Onion/-Kreis nur ueber die Schleife.

Tests: test_sv10_ohne_nginx_hilft_eine_gefaelschte_kopfzeile_nichts (ohne
die Behebung rot, gegengeprueft) und die Gegenprobe hinter nginx. Der
Testhelfer _Kopf traegt X-Forwarded-For jetzt auch in `client` ein, so
wie uvicorn hinter nginx; `gegenueber` stellt den Fall ohne nginx nach.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Audit vom 16.10.2026, SV-5 (Niedrig).

Die Behebung von B3 gibt jeder Herkunft acht eigene Nonce-Plaetze je
(Adresse, Geraet); am Dach des Paares (16 Herkuenfte x 8 = 128) faellt
aber das aelteste, gleich von welcher Herkunft. Herkunft war bei IPv6
das /64 - ein einziger Heimanschluss (/56 = 256 /64) brachte die 16
Herkuenfte kostenlos zusammen und verdraengte das Nonce des Opfers
zwischen dessen Challenge und /register.

Behebung: nonce_herkunft zaehlt IPv6 je /48 (limit_schluessel bekommt
dafuer v6_praefix). Ein Anschluss ist damit eine Herkunft und verdraengt
nur sich selbst; fuer 16 Herkuenfte braucht ein Stoerer 16 Anschluesse.
Die Bremse je Herkunft bleibt beim /64 - je /48 wuerde sie ganze
Mobilfunknetze in einen Eimer werfen; bei den Nonces teilen sich nur
Challenges fuer dieselbe Adresse einen Platz. Bewusst nicht gewaehlt:
fremde Eintraege am Dach gar nicht mehr zu verdraengen - dann hielte ein
Stoerer das Dach dauerhaft voll. Die zustandslose Challenge (Vorschlag
des Audits) waere die vollstaendige Loesung, braucht aber, dass die App
ihr Nonce bei /register mitschickt; das tut 2.0.0 nicht.

Tests: test_sv5_ein_heimanschluss_verdraengt_das_nonce_nicht,
test_sv5_die_bremse_zaehlt_weiter_je_64 (beide ohne die Behebung rot,
gegengeprueft).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…(SV-7)

Audit vom 16.10.2026, SV-7 (Niedrig).

Jede gepufferte Zeile startete eine eigene Aufgabe, und jede baute einen
neuen httpx.AsyncClient - samt TLS-Kontext und CA-Buendel, synchron auf
dem Event-Loop (gemessen 180 ms unter Windows), auch auf relay.bitdm.net,
wo der POST ohne TLS ueber die Schleife geht. Die Zahl der Aufgaben war
unbegrenzt; wer an ein abwesendes Geraet mit Push-Endpunkt flutete, auch
an eine eigene Zweitadresse, liess die Zustellung fuer alle stocken.

Behebung:
- push_klient(): EIN AsyncClient, gemerkt mit seiner Event-Loop (im
  Betrieb gibt es eine; die Tests bekommen je asyncio.run einen neuen),
  geschlossen im lifespan.
- stosse_im_hintergrund_an: hoechstens ein Anstoss je (Adresse, Geraet)
  unterwegs; was waehrenddessen kommt, loest danach GENAU EINEN weiteren
  aus - eine Zeile, die nach dem Wecken eintraf, bleibt nicht ohne
  Anstoss liegen.
- Hoechstens BITDM_PUSH_PARALLEL (200) Geraete gleichzeitig; darueber
  entfaellt der Anstoss (gemeldet ueber _push_ging_daneben), die
  Nachricht bleibt wie bei einem stummen Push-Server in der
  Warteschlange.

Fuer die App aendert sich nichts: sie wird weiterhin geweckt, wenn etwas
fuer sie liegt.

Tests (ohne die Behebung rot, gegengeprueft):
test_sv7_ein_client_fuer_alle_anstoesse,
test_sv7_eine_flut_an_ein_geraet_weckt_es_nicht_hundertmal,
test_sv7_die_zahl_gleichzeitiger_anstoesse_ist_gedeckelt.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Audit vom 16.10.2026, SV-3 (Mittel).

Neue Adressen werden ab IDENTITAETEN_MAX (50 000) mit 507 abgewiesen.
Eine Registrierung kostete je Herkunft nur 5 von 120 Marken der
allgemeinen Bremse (rund 34 500 am Tag), bei IPv6 zaehlte jedes /64 als
eigene Herkunft (ein Heimanschluss hat 256), ueber Tor ohne
Circuit-Kennung gab es keine Bremse. Ein Akteur fuellte das Dach in
Minuten und hielt es mit einer Erneuerung im Monat; danach konnte sich
niemand mehr neu anmelden.

Behebung (drei Riegel, alle nur fuer NEUE Adressen):
1. neue_adresse_ok: eigener, enger Eimer je Herkunft und Tag
   (BITDM_NEUE_ADRESSEN, 100), IPv6 je /48. Ueber den Onion-Dienst
   zusaetzlich ein gemeinsamer Eimer aller Tor-Nutzer
   (BITDM_NEUE_ADRESSEN_ONION, 500), weil Circuits billig zu wechseln
   sind. Geprueft erst nach dem Besitznachweis; 429 "zu viele neue
   Adressen von hier". Bundle erneuern und weitere Geraete anlegen kostet
   nichts daraus.
2. raeume_vergessene_geraete raeumt Adressen, die sich nach dem
   Registrieren nie per /ws angemeldet haben (last_seen = 0), nach
   BITDM_NIE_VERBUNDEN_TTL (48 h) statt nach 30 Tagen. Bestandszeilen
   tragen den Zeitpunkt der Wanderung und sind nicht betroffen; eine App,
   die doch noch kommt, bekommt 4401 und registriert sich neu.
3. Ab 80 % des Dachs eine Zeile im Journal, hoechstens einmal je Stunde,
   ohne Adresse und ohne Herkunft.

Preis, bewusst: hinter einer IPv4 (Carrier-Grade-NAT) bzw. in einem /48
teilen sich mehrere Kunden den Eimer fuer neue Adressen; 100 am Tag sind
fuer diesen Relay viel und per Umgebungsvariable einstellbar. Ganz
schliesst sich SV-3 so nicht: wer viele IPv4-Adressen oder /48 hat,
fuellt das Dach weiterhin, nur langsamer. Ein Arbeitsnachweis bei der
Registrierung braeuchte eine App-Aenderung und wuerde 2.0.0 aussperren.

App 2.0.0: unveraendert verstaendlich (429 bei der Registrierung kennt
sie schon). Test-Relays der App-Tests bekommen BITDM_NEUE_ADRESSEN=5000,
aus demselben Grund wie BITDM_RATE_BURST.

Tests (ohne die Behebung rot, gegengeprueft): test_sv3_* - ein Anschluss
legt nicht beliebig viele Adressen an, wer schon da ist kommt durch,
gemeinsamer Tor-Eimer, nie verbundene Adresse nach zwei Tagen, Meldung
vor dem Dach. App: test/net und test/core gegen den echten Relay
429 gruen.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
neue_adresse_ok lief in /register vor den Pruefungen, an denen eine
Registrierung noch scheitern kann (Kyber-Signaturen, Geraeteliste,
Adressdach 507, Speichern). Jeder Fehlversuch kostete so die ganze
Herkunft eine Marke; ein dauerhaft scheiternder Client hinter einer
geteilten IPv4 (CGNAT) hielt den Eimer fuer alle dort leer.

Jetzt wird die Marke erst im Zweig fuer eine noch unbekannte Adresse
genommen, nach dem Adressdach und direkt vor dem INSERT, unter
schreibsperre: "Adresse unbekannt" und "Marke genommen" sind ein
Schritt, parallele Anfragen kommen nicht ueber die Grenze, und zwei
gleichzeitige erste Registrierungen derselben Adresse zahlen einmal.
Laesst sich das Buendel danach nicht speichern, geht die Marke zurueck
(_gib_zurueck). Scheitert ueber Tor der gemeinsame Onion-Eimer, bekommt
der Circuit-Eimer seine Marke ebenfalls zurueck.

Tests: Fehlversuche (Kyber, Geraeteliste, INSERT, Adressdach) kosten
keine Marke; die Grenze von 100 je Herkunft und Tag gilt genau, auch
mit Fehlversuchen dazwischen und bei 20 gleichzeitigen Anfragen; eine
bekannte Adresse (Erneuern, weitere Geraete) zahlt nichts.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
RELAY_NAME_MUSTER ([a-z0-9.:-]{1,253}) liess wegen des Doppelpunkts fuer
IPv6 auch "relay.bitdm.net:443" als Namen durch. Die App nennt ihren
Namen ohne Port (Uri.host), fand sich nicht in der Liste und fiel still
auf die alte Anmeldeform zurueck; mit BITDM_ANMELDUNG_PFLICHT=1 waere sie
ausgesperrt worden, ohne Hinweis im Journal.

Jetzt prueft relay_name_ok jeden Eintrag: Hostname aus Labels nach
RFC 1123, IPv4 oder IPv6 (ein Doppelpunkt nur dort, keine Klammern,
keine Zonenkennung). Ungueltige Eintraege werden nie uebernommen.
melde_relay_namen() schreibt beim Start (lifespan) jeden abgelehnten
Eintrag laut mit [!] ins Journal und die gueltigen Namen einmal mit
[i]; ein falscher Eintrag laesst den Dienst weiterlaufen.
deploy/README.md nennt die Regel.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- pubspec 2.0.1+23, fassung.dart 2.0.1 (fassung_test gruen)
- CHANGELOG: Datum 24.10.2026, Audit-Befunde des Relays (#19), Hinweis
  "erst das Zwischenlager, dann den Relay"
- fastlane changelogs/23.txt de-DE (385 Zeichen) und en-US (327)
- fdroid/com.bitdm.bitdm.yml: versionName 2.0.1, versionCode 23; commit
  bleibt Nullen, den traegt der Tag-Workflow ein

Downloads, Badges, Hashes und install.sh folgen nach dem Tag und dem
Signieren (Checkliste Phase 6).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Store-Texte 23.txt (de 434, en 390 Zeichen): versprachen, ein Relay
  koenne eine Abmeldung nicht mehr verschweigen; das widerspricht "Was
  bleibt" im CHANGELOG. Jetzt: Kontakte erfahren sie auch ueber die
  Nachrichten. Dazu: dauerhaft im Hintergrund nur noch mit "Nie".
- CHANGELOG: Schutz vor Sitzungen unter fremder Geraetekennung gilt erst
  mit der Pflicht ab 2.0.2 (geraeteNachweisPflicht ist in 2.0.1 aus);
  /48 nur fuer Nonces und neue Adressen; camera 0.12.1 genannt.
- README/README.de: Abzeichen und /DVersion auf 2.0.1.
- Bluetooth-Seite: versprach, nur Kontakte erkennten, wer sendet. Bis
  2.0.1 sind die Funkpakete nicht eigens verschluesselt (Buendel-Antwort
  offen mit user_id, real_messenger_core.dart; Fix liegt auf
  fix/nahfunk-verschluesselung, noch nicht ausgeliefert). Ehrlicher Text,
  de und en.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@sonarqubecloud

Copy link
Copy Markdown

Quality Gate Failed Quality Gate failed

Failed conditions
0.0% Coverage on New Code (required ≥ 80%)

See analysis details on SonarQube Cloud

@Henner4746
Henner4746 marked this pull request as ready for review October 24, 2026 12:13
@Henner4746
Henner4746 merged commit fa96092 into main Oct 24, 2026
11 of 14 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant