einvoice / Deutsch

E-Rechnungen offline validieren

einvoice prüft elektronische Rechnungen gegen die Geschäftsregeln von EN 16931 und der deutschen XRechnung — als Kommandozeilenwerkzeug, komplett offline, mit null Abhängigkeiten (reine Python-3-Standardbibliothek: kein Java, kein Saxon, keine Schematron-Toolchain, kein Netzwerkzugriff). Diese Seite ist der deutschsprachige Schnellstart: was das Werkzeug ist, was es ehrlich abdeckt, und wie Sie es installieren, eine erste Rechnung prüfen und als CI-Gate verdrahten.

Was das Werkzeug ist

Ein Konformitätsprüfer für E-Rechnungen. Beide zulässigen XRechnung-Syntaxen werden direkt geprüft: UBL 2.1 (ubl:Invoice oder CreditNote) und UN/CEFACT CII (rsm:CrossIndustryInvoice — die Syntax, die ZUGFeRD und Factur-X tragen). Beide laufen durch dieselbe Regel-Engine — denselben syntaxunabhängigen EN 16931-Kern plus die deutsche BR-DE-*-CIUS-Schicht —, dasselbe --profile und dieselben Exit-Codes. Die Erkennung geschieht am Wurzelelement: es gibt keinen Syntax-Schalter, und eine rohe CII-.xml braucht keinen PDF-Container. Zur Verfügung stehen die Profile en16931 (europäischer Kern) und xrechnung (Kern plus die deutsche KoSIT-Schicht: BR-DE-*-Regeln wie Leitweg-ID/Käuferreferenz, Verkäuferkontakt, Zahlungsangaben, Skonto-Grammatik). Insgesamt setzt der Prüfer 297 Geschäftsregeln durch; jede ist differentiell gegen die offiziellen CEN-/KoSIT-Schematron-Artefakte bewiesen, mit 0 Abweichungen. Das Ergebnis kommt vierfach: als menschenlesbare Zusammenfassung, als Exit-Code (das, worauf ein CI-Gate reagiert), als --json-Maschinenprotokoll und — mit --format html — als weiterleitbares Berichtsdokument (weiter unten).

Die Fehlermeldungen gibt es auch auf Deutsch: mit --lang de zeigt die CLI zu jeder Regel den deutschen Korrekturtext — wo das offizielle KoSIT-Artefakt einen deutschen Text mitliefert, exakt diesen; sonst eine klar als Übersetzung gekennzeichnete Fassung, die nie als amtlicher Text ausgegeben wird.

Der zeitliche Rahmen. Seit dem 1. Januar 2025 muss jedes deutsche Unternehmen E-Rechnungen nach EN 16931 empfangen können. Die Pflicht, sie selbst zu stellen, wird gestaffelt eingeführt: Sie beginnt am 1. Januar 2027, und ab dem 1. Januar 2028 gilt sie für alle übrigen Unternehmen. Welche Unternehmen in die erste Stufe fallen, sagt diese Seite bewusst nicht: Das steht im Gesetz, wir erteilen hier keine Rechtsauskunft, und eine ungenaue Angabe dazu wäre schlimmer als gar keine. Was in diesem Zeitraum praktisch gebraucht wird, ist etwas anderes — eine Antwort auf die Frage, ob die Datei, die Ihr System erzeugt, beim Empfänger durchgeht, und zwar bevor sie gesendet wird. Genau dafür ist dieses Werkzeug da.

PDF-Rechnungen (ZUGFeRD 2.x / Factur-X): dieselbe CII, nur verpackt. Eine hybride PDF/A-3-Rechnung trägt genau dasselbe rsm:CrossIndustryInvoice-XML als eingebetteten Anhang. Sie können den Container deshalb direkt demselben Kommando übergeben, das Sie ohnehin benutzen: einvoice validate invoice.pdf holt den Anhang allein mit der Standardbibliothek heraus (kein PyPDF, kein Ghostscript, kein Netzzugriff) und bewertet ihn nach exakt denselben Regeln, die es auf dieses XML allein anwenden würde. Es ist also keine zweite Engine, sondern derselbe Prüflauf mit einer anderen Verpackung: derselbe rules.ALL_RULES-Kern, dieselben --profile-Werte, dieselben Exit-Codes. Auch validate-batch läuft über PDFs. Am mitgelieferten Factur-X-Fixture aus dem Repository (corpus/pdf/facturx-valid.pdf — eine Datei des Repository-Checkouts, nicht des Wheels) endet der Lauf mit Exit-Code 0 unter dem Standardprofil en16931 und mit 1 unter --profile xrechnung: 3 fatale Verletzungen (BR-DE-23-b, BR-DE-31, BR-TMP-3) und 2 Warnungen. Die deutsche KoSIT-Schicht greift auf den Inhalt eines Containers also genauso wie auf loses XML.

Ehrliche Grenzen, die Sie kennen sollten, bevor Sie das in CI verdrahten: Der Extraktor ist ein PDF-Leser für den klassischen Fall. Verschlüsselte PDFs (/Encrypt), Cross-Reference-Stream-/Object-Stream-Layouts (PDF 1.5+ ohne klassischen trailer), abgeschnittene Dateien und unbekannte Stream-Filter lassen sich nicht öffnen. Diese Fälle enden mit Exit-Code 3, dem wörtlichen Token unsupported-container und einer Zeile, die den konkreten Grund benennt — nie eine Vermutung und nie eine falsche 0. In der Praxis sind die PDF/A-3-Dateien, die echte ERP-Exporter erzeugen, der klassische Fall; ein von einem Optimierer neu geschriebenes PDF möglicherweise nicht. Gelesen wird nur die erste eingebettete Rechnungs-Dateispezifikation. Und die FX-CONTAINER-*-Prüfungen (/AFRelationship, /AF, das XMP-Factur-X-Profil und seine Übereinstimmung mit der CustomizationID der CII, die PDF/A-3-Kennzeichnung) prüfen die Verpackung, nicht die Rechnung: Berichtet werden sie von der Report-Oberfläche (python3 -m einvoice.report <invoice.pdf>), von validate-batch und von einvoice validate --format <fmt> <invoice.pdf>nicht in der Standard-Textausgabe und nicht im --json des Einzeldatei-validate, das das Rechnungsurteil ausgibt.

Ehrliche Abdeckung: was ein grünes Ergebnis bedeutet

Ein grünes Ergebnis heißt: keine implementierte fatale Regel hat ausgelöst. Es heißt nicht „rechtsverbindlich konforme XRechnung“. Die Grenzen im Einzelnen:

Nichts davon müssen Sie glauben: Das maßgebliche Regelinventar mit jeder Ausnahme und wörtlichen Artefakt-Belegen ist die Abdeckungsmatrix (COVERAGE.md) im Repository, und jede Zahl auf dieser Seite lässt sich dort nachrechnen. Lassen Sie vor dem tatsächlichen Einreichen trotzdem den offiziellen Validator Ihres Empfängers laufen — dieses Werkzeug ist der schnelle Vorab-Check, der die typischen Fehler früh fängt.

Installation und erste Prüfung

Zuerst installieren: das auf PyPI veröffentlichte Paket heißt verifyhash-einvoice (nicht einvoice — das ist ein fremdes Paket) und zieht keine einzige Abhängigkeit nach (dependencies = [] in pyproject.toml, Python 3.8+). Kein Java, kein Saxon, kein Netzzugriff beim Prüfen:

python3 -m pip install verifyhash-einvoice

Das legt das Kommando einvoice in den PATH — die drei Schritte unten sind genau das, was Sie danach tippen. invoice.xml ist dabei Ihre eigene Rechnung (UBL oder CII, XRechnung oder EN 16931); broken.xml ist dieselbe Datei mit einer entfernten Pflichtangabe — der Käuferreferenz BT-10 —, damit Sie einmal sehen, wie ein Fehlschlag aussieht, bevor Ihr Empfänger ihn Ihnen zeigt.

1. Erste Prüfung einer gültigen Rechnung

einvoice validate --profile xrechnung invoice.xml

Ausgabe PASS: invoice.xml, Exit-Code 0 — keine implementierte fatale Regel hat ausgelöst. --profile xrechnung ist für deutsche Rechnungsempfänger der entscheidende Schalter: die Voreinstellung ist en16931 (der europäische Kern), erst xrechnung schaltet die deutsche CIUS-Schicht mit den BR-DE-*- und BR-DEX-*-Regeln dazu. Ein grünes Ergebnis unter en16931 sagt über die deutsche Schicht nichts aus — das ist die häufigste Überraschung hier.

2. Eine kaputte Rechnung fällt durch — mit Regel-ID

einvoice validate --profile xrechnung broken.xml

Exit-Code 1; die Ausgabe nennt die verletzte fatale Regel (BR-DE-15, fehlende Käuferreferenz BT-10) samt betroffenem Element (cbc:BuyerReference) — und danach zwei Zeilen, die genau diese Regel erklären: how to fix: einvoice --explain BR-DE-15 und die rule page:-URL derselben Regel auf dieser Seite. Die Exit-Codes sind der ganze Vertrag, den ein CI-Gate braucht: 0 = keine implementierte fatale Regel verletzt, 1 = mindestens eine fatale Verletzung, 2 = Bedienfehler, 3 = kein wohlgeformtes XML.

Diese Erklärung gibt es auch auf Deutsch, offline und ohne Rechnungsdatei — --lang=de zieht für die BR-DE-*-Familie den amtlichen KoSIT-Text heran (der fix:-Satz ist eine als solche gekennzeichnete Übersetzung, nie amtlicher Text). Dasselbe Flag wirkt auch bei einvoice validate --lang de:

einvoice --explain BR-DE-15 --lang=de

3. Maschinenlesbar: --json

einvoice validate --json --profile xrechnung broken.xml

Gibt das vollständige Ergebnis als JSON auf stdout aus (Exit-Code unverändert 1): "valid": false, "violation_count": 1 und der Befund zu BR-DE-15 mit element, severity, fix_hint und den betroffenen Geschäftsbegriffen (terms: ["BT-10"]). valid spiegelt exakt den Exit-Code und kippt nur bei fatal-Befunden — beratende warning/information-Einträge bewegen ihn nie. Verlassen Sie sich in Skripten auf valid oder den Exit-Code, nicht auf den menschenlesbaren Text.

Aus einem Repository-Checkout — air-gapped, oder wenn Sie einen exakten Stand pinnen wollen — läuft derselbe Code auch ganz ohne Installation, direkt über den Wrapper im einvoice/-Verzeichnis. Beachten Sie: dieser Wrapper und die Beispieldateien unter examples/ liegen nur im Repository und sind nicht im Paket verifyhash-einvoice enthalten — das Wheel enthält ausschließlich den Validator.

python3 einvoice.py validate --profile xrechnung examples/01-missing-fields/fixed.xml

CI-Anbindung: kein Build mit kaputter Rechnung

Für ein Repository voller Rechnungen gibt es ein fertiges Gate-Skript (POSIX sh, keine Abhängigkeiten außer python3): Es prüft rekursiv jede *.xml-Datei, lässt den Build bei jeder fatalen Verletzung mit der Regel-ID im Log fehlschlagen und schreibt pro Rechnung einen JUnit-Report, den CI-Oberflächen als Testergebnis anzeigen. Im CI-Job installieren Sie den Validator mit demselben Befehl wie oben — ein Schritt, kein Cache-Aufbau, keine transitiven Pakete:

python3 -m pip install verifyhash-einvoice

Offline-Alternative für Runner ohne PyPI-Zugriff (air-gapped) oder wenn Sie einen exakten Stand reproduzierbar pinnen wollen: das Produktverzeichnis vendoren (z. B. nach third_party/einvoice/) und die vendorte Kopie installieren — derselbe Code, nur ohne Paketindex:

python3 -m pip install ./third_party/einvoice

Dann das Gate über Ihre Rechnungsdateien laufen lassen. Das Gate-Skript selbst kopieren Sie mit ins Repository (der Pfad im Beispiel zeigt auf die vendorte Ablage; es ruft nur python3 -m einvoice.report auf, egal woher der Validator installiert wurde):

sh third_party/einvoice/ci/validate-invoices.sh invoices/

Kopierfertige GitHub-Actions- und GitLab-CI-Definitionen liegen daneben im CI-Rezept (einvoice/ci/); eine uses:-pinnbare GitHub Action annotiert Befunde per SARIF direkt im Pull Request. Jedes Kommando auf dieser Seite ist byte-identisch mit den englischen Anleitungen, deren Kommandos die Testsuite gegen die echte Engine ausführt — die Doku kann nicht von dem abdriften, was das Werkzeug wirklich tut.

Bestand statt grüner Wiese: Wer den Prüfer über ein bestehendes Rechnungsarchiv einführt, das heute schon durchfallen würde, braucht kein rotes Build am ersten Tag — und genau dafür gibt es zwei Mechanismen. Das Unterkommando validate-batch nimmt ein Verzeichnis oder ein Glob-Muster, läuft rekursiv hindurch, prüft jede gefundene .xml- und .pdf-Rechnung und gibt ein einziges aggregiertes Ergebnis aus — eine Zeile pro Datei, die Gesamtzählung und eine Rangliste der häufigsten Regelverstöße —, und --fail-on=fatal|warning|information legt fest, ab welcher Schwere der Exit-Code von 0 abweicht: Ihr Team wählt die Höhe seiner eigenen Schwelle, statt unsere zu erben (fatal ist die Voreinstellung — Warnungen und Hinweise werden gemeldet, lassen den Build aber nicht scheitern). Die Aufrufform lautet einvoice validate-batch --profile xrechnung <Verzeichnis>: Auch der Stapellauf folgt dem Standardprofil, ohne --profile xrechnung prüft er nur die europäischen Kernregeln ohne die deutsche KoSIT-Schicht.

Drei Grenzen, bevor Sie den CI-Job schreiben. Die aggregierte Form gibt es nur für json, junit und text; die anderen sechs Ausgabeformate beschreiben genau eine Rechnung, und ein solches Format über ein Verzeichnis wird mit Exit-Code 2 abgelehnt, statt still etwas anderes zu bedeuten. Ein Altbestand wird Datei für Datei in einer CI-Schleife abgesichert: --baseline — der Regressionsvergleich, der nur bei Befunden fehlschlägt, die gegenüber einem gespeicherten Bericht neu sind — wird auf dem Stapelkommando bewusst abgelehnt (Exit-Code 2: ein gespeicherter Einzelbericht lässt sich nicht gegen ein Aggregat vergleichen). Es gibt also keinen einzelnen Aufruf, der ein ganzes Archiv einfriert; dafür rufen Sie einvoice validate --baseline prev.json invoice.xml pro Datei auf, oder Sie halten das Stapel-Gate bei --fail-on=fatal und ziehen die Schwelle an, sobald der Rückstand abgearbeitet ist. Und drittens: Auch ein grünes Aggregat heißt nur keine implementierte fatale Regel hat ausgelöst — nicht „rechtsverbindlich konforme XRechnung“.

Einfrieren, was schon kaputt ist — scheitern an dem, was neu kaputtgeht. Diese Schleife Datei für Datei verdient einen zweiten Blick, denn sie ist der Mechanismus, der ein bestehendes Rechnungsarchiv überhaupt einführbar macht. --baseline <vorheriger-bericht.json> macht aus einvoice validate ein Regressions-Gate: Es prüft die Rechnung jetzt, vergleicht die Befunde mit einem früher aufgezeichneten JSON-Bericht und setzt den Exit-Code nur dann auf ungleich 0, wenn ein Verstoß gegenüber diesem Bericht neu ist. Nichts Neues — Exit-Code 0, auch wenn die Rechnung weiterhin jeden fatalen Befund von gestern trägt. Ein neuer fataler Befund — Exit-Code 1, und das Dokument einvoice-conformance-diff/v1 nennt ihn unter new_violations; was Sie behoben haben, erscheint unter resolved_violations, der Fortschritt steht also im selben Artefakt. Ein Lieferantenarchiv, das pro Rechnung an vier Regeln scheitert, kann damit heute in die CI — und blockiert die fünfte. Aufgezeichnet wird mit python3 -m einvoice.report --profile xrechnung --format json invoice.xml > prev-report.json, geprüft im CI-Job mit einvoice validate --baseline prev-report.json --profile xrechnung invoice.xml.

Drei Hinweise zu diesen beiden Zeilen, danach die zwei Grenzen, an denen sich entscheidet, ob das der richtige Schalter ist. Aufgezeichnet wird mit python3 -m einvoice.report und nicht mit einvoice validate --json, weil nur dieser Einstiegspunkt profile in den erzeugten Bericht schreibt — ein Bericht ohne Profilangabe wird zwar weiterhin verglichen, aber ein Profilunterschied fällt dann nicht mehr auf: Sie erhalten eine note:-Zeile auf stderr, dass das Profil nicht geprüft werden konnte, nicht die Ablehnung. Die Aufzeichnung endet bei einer fehlerhaften Rechnung mit Exit-Code 1 und schreibt den vollständigen Bericht trotzdem; dieser Exit-Code ist das erwartete Ergebnis und kein Fehler — eine Shell mit set -e darf den Lauf hier nicht abbrechen. Und beide Zeilen führen dasselbe --profile xrechnung: Aufzeichnung und Prüfung müssen dasselbe Profil nennen, sonst wird der Lauf mit Exit-Code 2 unter Nennung beider Profile abgelehnt, statt Ihren Schalterwechsel als Regression zu werten. Nun die Grenzen. Der Vergleich toleriert bereits vorhandene fatale Befunde bewusst — genau dafür ist der Schalter da —, ein grüner --baseline-Lauf heißt also „nichts ist schlechter geworden“, nie „diese Rechnung ist konform“: Es ist ein Migrationswerkzeug, und das Konformitätsurteil liefert derselbe Aufruf ohne den Schalter. Und verglichen wird eine Rechnung gegen einen gespeicherten Bericht: validate-batch --baseline wird mit Exit-Code 2 abgelehnt, ein Altbestand wird also Datei für Datei in einer CI-Schleife abgesichert, mit einem gespeicherten Bericht je Rechnung — es gibt keinen einzelnen Aufruf, der ein ganzes Archiv einfriert.

Einmal festlegen, im Repository. Dieselben drei Entscheidungen — Ausgabeform, Schwellenhöhe, Sprache der Meldungen — werden am Ende an drei Stellen getippt: im CI-Job, im Pre-Commit-Hook und in dem, was eine Entwicklerin lokal aufruft. Sie driften auseinander, und das erste Symptom ist ein Build, der dort scheitert, wo er eben noch durchlief. Legen Sie eine .einvoice.toml in das Projektverzeichnis — oder ergänzen Sie eine [tool.einvoice]-Tabelle in der pyproject.toml, die es ohnehin schon gibt —, und sie setzt die Voreinstellungen für genau drei Schlüssel: format, fail-on und lang. Diese drei sind das gesamte Vokabular; es gibt keinen vierten Schlüssel und keinen, der Regeln an- oder abschaltet. einvoice --show-config gibt anschließend jeden aufgelösten Wert aus — zusammen damit, woher er stammt: flag, der Dateiname der Konfiguration oder default —, liest dabei keine Rechnung und führt keine Prüfung aus. Ohne Konfigurationsdatei steht dort format: text (source: default), fail-on: fatal (source: default) und lang: en (source: default). Eine zweizeilige .einvoice.toml mit lang = "de" reicht aus, damit ein einvoice validate --profile xrechnung invoice.xml ganz ohne Schalter auf Deutsch berichtet.

Vier Grenzen sollten Sie kennen, bevor Sie die Datei einchecken. Wenn beide Dateien vorliegen, gewinnt .einvoice.toml vollständig: Die [tool.einvoice]-Tabelle wird nicht hinzugemischt und nicht einmal gelesen — eine kaputte Tabelle neben einer guten .einvoice.toml ändert nichts, und sie zu reparieren ebenso wenig. Der Konfigurationslayer gehört allein dem Konsolenprogramm einvoice: python3 -m einvoice.report liest weder eine .einvoice.toml noch eine [tool.einvoice]-Tabelle. Das gehört ausdrücklich gesagt, weil der empfohlene Weg, einen --baseline-Bericht aufzuzeichnen, genau über diesen Einstiegspunkt läuft — dort geben Sie die Einstellungen weiterhin auf der Kommandozeile mit. Eine fehlerhafte Konfigurationsdatei ist ein Bedienfehler, Exit-Code 2, mit Nennung der Datei und des Schlüssels: ein unbekannter Schlüssel, ein Wert, der keine Zeichenkette ist, oder eine unlesbare Datei brechen den Lauf ab, statt still auf die eingebauten Voreinstellungen zurückzufallen. Und ein ausdrücklicher Schalter auf der Kommandozeile gewinnt weiterhin: Die Reihenfolge ist Schalter > Konfigurationsdatei > eingebaute Voreinstellung. Die Datei ändert also, was Sie bekommen, wenn Sie nichts sagen — nie das, was Sie bekommen, wenn Sie etwas sagen.

Ein Beleg zum Prüfergebnis. Ein Validierungslauf ist flüchtig: Der Exit-Code scrollt vorbei, und neben der archivierten Rechnung bleibt nichts liegen, was festhält, was überhaupt geprüft wurde. einvoice receipt invoice.xml schreibt ein kleines, deterministisches JSON-Dokument, das festhält, welche Bytes geprüft wurden — input_sha256, der SHA-256-Wert der rohen Eingabedatei —, unter welchem Profil und mit welcher Programmversion, mit welchem Ergebnis und welche fatalen Regel-IDs ausgelöst haben, umschlossen von einem content_sha256 über den kanonisierten Rumpf des Dokuments selbst; einvoice receipt --verify receipt.json rechnet genau diesen Wert mit einem einzigen Befehl nach, offline, ohne Server und ohne dass irgendetwas hochgeladen wird.

Das heißt: Manipulationen werden erkennbar, nicht verhindert. content_sha256 deckt nur den Rumpf ab und ist nicht Teil seines eigenen Hash-Eingangs. Jede Änderung an einem Rumpffeld — und ebenso ein verfälschter content_sha256 selbst — wird erkannt; wer dagegen Rumpf und Hash gemeinsam neu schreibt, erhält einen in sich stimmigen, aber gefälschten Beleg, der sauber durch die Prüfung geht. Dagegen hilft nur ein Anker außerhalb des Dokuments: den content_sha256 mit einem unabhängig aufbewahrten Wert vergleichen, oder die Validierung auf den Originalbytes wiederholen, die der Beleg über input_sha256 benennt, und den Rumpf vergleichen. Das Nachrechen-Rezept für beliebige Sprachen steht in RECEIPT-VERIFICATION.md im Quell-Checkout. Eine Uhrzeit trägt der Beleg standardmäßig bewusst nicht: Gleiche Eingabebytes und gleiches Profil ergeben bei jedem Lauf ein byte-identisches Dokument, der Beleg hält also fest, was geprüft wurde, nicht wann. Und er ist ein Protokoll, kein Zertifikat: Ein grünes Ergebnis darin heißt nur keine implementierte fatale Regel hat ausgelöst — nicht „rechtsverbindlich konforme XRechnung“.

Ein Bericht zum Weiterleiten. Die Terminalausgabe ist für Sie. Wer die Rechnung korrigieren muss, ist meist jemand anderes — der Lieferant, der sie geschickt hat, oder die Steuerberatung, die sie ablegt —, und keiner von beiden startet dafür eine Kommandozeile. --format html macht aus demselben Lauf ein in sich geschlossenes Konformitätsdokument: eine einzige HTML-Datei auf der Standardausgabe, das gesamte Styling eingebettet und keine einzige externe Ressource — kein Skript, kein Bild, keine Web-Schrift, kein CDN, kein Tracking — und kein Zeitstempel, also stellt sie sich offline aus einem Archivordner in fünf Jahren noch genauso dar, und zwei Läufe über dieselbe Rechnung liefern byte-identische Dateien. Mit --lang de ist es ein deutsches Dokument: Titel, Überschriften, Statusbanner und Feldbeschriftungen deutsch, die Deklaration <html lang="de">, und zu jedem Befund ein deutscher Regeltitel und ein deutscher Korrekturhinweis unter der Überschrift Behebung. Der Fußbereich belegt die Herkunft des Ergebnisses — Engine-Version, Zahl der durchgesetzten Geschäftsregeln und der vollständige 64-stellige Attestierungs-Hash, nie abgekürzt, denn ein gekürzter Hash lässt sich nicht nachrechnen. Diese Werte werden nicht in den Bericht getippt, sondern aus derselben Nutzlast gelesen, die einvoice info --json ausgibt: Der Empfänger führt einvoice info --json auf seiner eigenen Installation aus und vergleicht die Zahlen, statt uns zu glauben. Was ein Build nicht ermitteln kann, lässt der Fußbereich als ganze Zeile weg, statt „unbekannt“ hinzuschreiben — er wird kürzer, nie falsch. Der Befehl lautet einvoice validate --profile xrechnung --lang de --format html invoice.xml, wortgleich so, wie ihn QUICKSTART.md führt.

Wie deutsch dieses Dokument ist, bestimmen zwei verschiedene Zahlen über zwei verschiedene Felder. Genau 50 der 297 Regeln tragen einen amtlichen deutschen Meldungstext: die Katalogeinträge mit de_source == "kosit", deren vendorierter KoSIT-<sch:assert>-Text selbst deutsch ist (die deutsch verfasste Familie BR-DE-*, BR-DE-CVD-*, BR-TMP-*, BR-DEX), Wort für Wort übernommen. Ein Befund außerhalb dieser 50 behält den maßgeblichen englischen Originalsatz und sagt das auch: Der Absatz ist sichtbar mit [en] markiert, mit lang="en" ausgezeichnet, und eine Anmerkung im Dokument erklärt die Markierung — den verbindlichen Wortlaut zu zeigen ist ehrlicher, als für eine Regel einen deutschen Satz zu erfinden, den die Norm nie geschrieben hat. Die Regeltitel und die Behebungshinweise sind eine andere Sache: Sie sind für alle 297 Regeln deutsch, und der Bericht vermerkt pro Regel, woher dieses Deutsch stammt — amtlicher KoSIT-Wortlaut bei den 50, eine Übersetzung dieses Projekts bei den übrigen 247 (alle 297 Korrekturhinweise sind projekteigen formuliert, die 50 eingeschlossen). Genau diese Kennzeichnung ist der Punkt: Projektprosa wird einem Leser nie als Wortlaut der Norm untergeschoben. Und ein grüner Bericht ist keine Rechtskonformität — er heißt „keine implementierte fatale Regel hat ausgelöst“, nicht „rechtsverbindlich konforme XRechnung“.

Weiterführend