Die Auslieferung trägt nur, was der Browser wirklich braucht

Die Browserfassung soll künftig über Cloudflare Workers gehen, und dort gilt
eine Grenze von 25 MiB je Datei, unkomprimiert gemessen. Zwei der
ausgelieferten Dateien standen dem entgegen: Das Quelltextarchiv maß 29,7 MB
und war damit schon nicht mehr hochzuladen, das Wasm-Modul mit 24,5 MB zu
achtundneunzig Hundertsteln der Grenze und ohne jeden Spielraum.

Das Archiv war ein ungefiltertes „git archive“ des Repositoriums, und über
fünfundneunzig Hundertstel davon waren die Beispiel-PDFs. Der Quelltext selbst
misst keine dreihunderttausend Zeichen. Die Gesetzes- und Drucksachentexte sind
fremde Werke, an denen allein die Tests messen; sie sind nicht der Quelltext,
aus dem sich das Ausgelieferte herstellen ließe, und die Auflage des AGPLv3 § 13
verlangt sie deshalb nicht. Sie sind aus dem Archiv genommen, und
„quelltext-fassung.txt“ sagt fortan, dass sie fehlen, warum sie fehlen und wo
sie vollständig liegen. Damit dies nicht stillschweigend zurückfällt, bricht
„webpaket.sh“ ab, sobald das Archiv acht Mebibyte überschreitet, und ebenso,
wenn irgendeine ausgelieferte Datei die Grenze der Zielplattform reißt: Lieber
hier auffallen als beim Hochladen.

Der Korpus lag überdies unter „src/main/resources“ und wanderte deshalb in
jedes Erzeugnis: in das Klassenverzeichnis, in die ausführbare Archivdatei —
achtundzwanzig Megabyte für ein Werk von sechshundert Kilobyte Java — und auf
den Klassenpfad des Übersetzers, dessen Ressourcentabelle die Verzeichnisnamen
bis ins Wasm trug. Er liegt nunmehr unter „src/test/resources“, wo er hingehört,
und ist von der Kopie nach „target/test-classes“ ausgenommen: Die Tests lesen
ihn über Dateisystempfade, eine Kopie wäre bei jedem Bau fünfunddreißig
Megabyte umsonst.

Das Modul wiegt statt 24,5 nunmehr 17,2 Megabyte; komprimiert geht es mit 5,5
statt 7,1 Megabyte über die Leitung. Vier Ursachen liegen dem zugrunde. Der
Picocli-Annotationsprozessor meldete die Befehlszeilenklasse mit sämtlichen
Methoden zur Reflexion an, worauf die Erreichbarkeitsanalyse die ganze
Befehlszeilenfassung samt Dateizugriffen in ein Bild zog, in dem es keine
Befehlszeile gibt; im Profil „wasm“ läuft er nicht mehr, und eine von einem
früheren Lauf liegengebliebene Konfiguration wird vor dem Übersetzen entfernt,
damit die Größe nicht davon abhängt, was vorher lief. Der Ressourcen-Glob
„org/apache/fontbox/**“ bettete 3,3 Megabyte ostasiatischer CMaps, die
Schrifttabelle „Scripts.txt“ und — weil zwei Sterne auch Klassendateien treffen
— 0,7 Megabyte „.class“-Dateien ein, von denen ein deutsches Gesetzes-PDF nichts
braucht; geblieben sind die beiden Identity-CMaps. Die Metriken unter
„org/apache/pdfbox/resources“ bleiben vollständig, denn an ihnen hängt die
Breitenberechnung bei nicht eingebetteten Schriften. Übersetzt wird mit „-Os“,
und den Rest holt „wasm-opt -Oz“ im Webpaket.

Dessen Merkmale sind einzeln aufgezählt und nicht als „--all-features“ erteilt.
Der bequeme Weg war gangbar und führte doch ins Leere: Binaryen nutzte
daraufhin Vorschläge, die noch kein Browser annimmt, und das Modul scheiterte
erst beim Instanziieren — erst an einem exakten Heap-Typ aus den „custom
descriptors“, nach dessen Abschaltung an der kompakten Importsektion. Zugelassen
ist nunmehr, was ausgeliefert in den Browsern steht und was Web Image braucht.

Die Vorkompression nach „.gz“ und „.br“ entfällt als Regelfall, denn Cloudflare
komprimiert selbst und jede Beilage wäre dort eine weitere Datei; wer mit nginx
selbst ausliefert, fordert sie mit VORKOMPRIMIEREN=1 an. Das
Auslieferungsverzeichnis fällt damit von achtundsechzig auf siebzehn Megabyte,
und ein liegengebliebenes „.DS_Store“ geht nicht mehr mit hoch.

Dreihundertfünfundzwanzig Testfälle bestehen. Das Modul ist im Browser
gelaufen: Vier Dokumentenpaare — IfSG, UWG, AGG und das Bayerische Jagdgesetz,
also gii-XML wie PDF als Stammfassung, Bundesgesetzblatt, Drucksache und
Gesetz- und Verordnungsblatt als Änderungsdokument — ergeben Synopsen, deren
SHA-256-Summen denen der Befehlszeilenfassung gleichen. Der Ressourcenbeschnitt
kostet also keine Zeichen.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Change-Id: I31a47d1dcf3b27ffa4ae891947b1c4e083107a71
diff --git a/.gitattributes b/.gitattributes
new file mode 100644
index 0000000..ea305c0
--- /dev/null
+++ b/.gitattributes
@@ -0,0 +1,5 @@
+# Der Beispielkorpus besteht aus Gesetzes- und Drucksachen-PDFs fremder Urheberschaft.
+# Er ist nicht „Corresponding Source“ im Sinne der AGPLv3 — gebaut wird ohne ihn —, und
+# er machte das der Browserfassung beigelegte Quelltextarchiv dreißigmal so groß wie den
+# Quelltext selbst. Wer ihn braucht (für die Tests), holt das Repository.
+src/test/resources/sampledata/** export-ignore
diff --git a/README.adoc b/README.adoc
index 996a0be..ee363d4 100644
--- a/README.adoc
+++ b/README.adoc
@@ -111,7 +111,7 @@
 (2) Fortlaufende Quelltextquelle ist
 https://gerrit.benkard.de/plugins/gitiles/aendggner. Die jeweils betriebene
 Fassung liegt der Browserfassung überdies als `aendggner-quelltext.tar.gz` bei
-(§ 15 Absatz 4).
+(§ 15 Absatz 5).
 
 (3) Das Erzeugnis steht unter der GNU Affero General Public License, Fassung 3;
 der Lizenztext ist der Datei `COPYING` zu entnehmen.
@@ -486,15 +486,42 @@
 ----
 
 (4) Ergebnis ist `target/web/` mit `index.html`, `app.js`, `worker.js`,
-`style.css`, `favicon.svg`, `aendggner.js` und `aendggner.js.wasm` (rund 24 MB,
-komprimiert etwa 7 MB). Das Verzeichnis ist so, wie es dasteht, auslieferbar:
+`style.css`, `favicon.svg`, `aendggner.js` und `aendggner.js.wasm` (rund 17 MB,
+komprimiert etwa 5 MB). Das Verzeichnis ist so, wie es dasteht, auslieferbar:
 `deploy/webpaket.sh` läuft am Ende desselben Befehls, wirft den mehrere hundert
-Megabyte großen Textzwischenschritt `aendggner.js.wat` fort, legt den Quelltext
-der gebauten Fassung als `aendggner-quelltext.tar.gz` samt
-`quelltext-fassung.txt` bei und komprimiert die großen Dateien nach `.gz` und
-`.br` vor.
+Megabyte großen Textzwischenschritt `aendggner.js.wat` fort, schickt das Modul
+durch `wasm-opt -Oz` und legt den Quelltext der gebauten Fassung als
+`aendggner-quelltext.tar.gz` samt `quelltext-fassung.txt` bei.
 
-(5) Trägt der Arbeitsbaum uneingecheckte Änderungen, so bricht die Herstellung
+(5) Die Größe des Moduls beruht auf vier Vorkehrungen; wer eine davon zurücknimmt,
+handelt sich die Megabyte wieder ein:
+
+1. `-Os` beim Übersetzen statt der auf Durchsatz gerichteten Voreinstellung;
+2. kein Picocli-Annotationsprozessor im Profil `wasm`. Er meldete die
+   Befehlszeilenklasse zur Reflexion an, worauf die Erreichbarkeitsanalyse die
+   gesamte Befehlszeilenfassung ins Browser-Image zog, die dort niemand aufruft;
+3. ein enger Ressourcen-Glob in `reachability-metadata.json`. Das frühere
+   `org/apache/fontbox/**` bettete 3,3 MB CJK-CMaps, `Scripts.txt` und — weil
+   `**` auch Klassendateien trifft — 0,7 MB `.class`-Dateien ein, von denen
+   deutsche Gesetzes-PDFs nichts brauchen. Geblieben sind die beiden
+   Identity-CMaps; `org/apache/pdfbox/resources/**` bleibt vollständig, damit die
+   Breitenberechnung bei nicht eingebetteten Schriften unangetastet ist;
+4. `wasm-opt -Oz` als Nachlauf. Die zugelassenen Wasm-Merkmale sind in
+   `webpaket.sh` einzeln aufgezählt und nicht als `--all-features` erteilt: Sonst
+   nutzt Binaryen auch Vorschläge, die noch kein Browser annimmt, und das Modul
+   scheitert erst beim Instanziieren.
+
+(6) Im Quelltextarchiv fehlt der Beispielkorpus; `.gitattributes` nimmt
+`src/test/resources/sampledata` von `git archive` aus. Es sind Gesetzes- und
+Drucksachentexte fremder Urheberschaft, an denen allein die Tests messen —
+Quelltext im Sinne der AGPLv3 sind sie nicht, gebaut wird ohne sie, und sie
+machten das Archiv dreißigmal so groß wie den Quelltext (29,7 MB statt 0,26 MB).
+`quelltext-fassung.txt` sagt dies und verweist für den vollständigen Korpus auf
+die Anschrift nach § 3 Absatz 2. Wächst das Archiv wieder über 8 MiB, so bricht
+`webpaket.sh` ab; alsdann sind Massendaten ins Repository geraten, die dort nicht
+hingehören.
+
+(7) Trägt der Arbeitsbaum uneingecheckte Änderungen, so bricht die Herstellung
 nach Absatz 3 ab, denn der beigelegte Quelltext wäre alsdann nicht der gebaute.
 Für einen Probelauf hilft `QUELLTEXT_UNGEPRUEFT=1`.
 
@@ -513,26 +540,37 @@
 [[ausrollen]]
 == § 15 Ausrollen
 
-(1) Betrieben wird die Fassung unter einem Unterpfad einer bestehenden Domain,
-https://matthias.benkard.de/aendggner/. Ein eigener Server und ein eigenes
-Zertifikat sind hierfür nicht erforderlich; erforderlich ist nur ein Webserver
-für statische Dateien.
+(1) Erforderlich ist nur ein Ort für statische Dateien. Ausgeliefert wird über
+Cloudflare Workers. Dort gilt eine Grenze von 25 MiB je Datei — unkomprimiert
+gemessen —, und komprimiert wird beim Ausliefern ohnehin. `webpaket.sh` legt
+deshalb keine `.gz`/`.br`-Beilagen mehr an und hält am Ende jede Datei gegen
+diese Grenze; überschreitet eine sie, so bricht der Bau ab, statt das Hochladen
+scheitern zu lassen.
 
 [source,shell script]
 ----
 JAVA_HOME=/pfad/zu/oracle-graalvm ./mvnw -Pwasm package
+----
+
+(2) Wer die Fassung stattdessen selbst ausliefert — unter einem Unterpfad einer
+bestehenden Domain, wie zuvor unter https://matthias.benkard.de/aendggner/ —,
+braucht die Vorkompression und fordert sie beim Bau an:
+
+[source,shell script]
+----
+JAVA_HOME=/pfad/zu/oracle-graalvm VORKOMPRIMIEREN=1 ./mvnw -Pwasm package
 rsync -av --delete target/web/ server:/var/www/aendggner/
 ----
 
-(2) `deploy/nginx-aendggner.conf` ist kein eigener `server`-Block, sondern ein
+(3) `deploy/nginx-aendggner.conf` ist kein eigener `server`-Block, sondern ein
 Schnipsel zum Einfügen in den vorhandenen (`include`). Er bringt mit:
 
 1. die Weiterleitung von `/aendggner` auf `/aendggner/`, ohne die alle relativen
    Verweise der Seite auf die Domainwurzel zielten;
 2. den MIME-Typ `application/wasm`, ohne den der Browser die Instanziierung des
    Moduls verweigert;
-3. `gzip_static`/`brotli_static` für die vorkomprimierten Dateien, statt 24 MB je
-   Abruf neu zu packen;
+3. `gzip_static`/`brotli_static` für die nach Absatz 2 vorkomprimierten Dateien,
+   statt 17 MB je Abruf neu zu packen;
 4. `Cache-Control: no-cache` statt einer Haltefrist. Die Dateinamen tragen keine
    Fassungskennung, und ein Browser mit altem `app.js` und neuem `.wasm` bekäme
    sonst eine Mischfassung, die es nie gegeben hat. Revalidiert wird per ETag; das
@@ -543,11 +581,11 @@
    Synopse als `blob:`-Dokument die Richtlinie der erzeugenden Seite erbt, ihr
    Stylesheet aber eingebettet trägt.
 
-(3) `impressum.html` und `datenschutz.html` tragen die Angaben nach § 5 DDG und
+(4) `impressum.html` und `datenschutz.html` tragen die Angaben nach § 5 DDG und
 Art. 13 DSGVO. Die dort genannte Aufbewahrungsfrist der Zugriffsprotokolle
 (14 Tage) muss zu derjenigen des Servers passen.
 
-(4) Der Footer der Startseite verweist auf den beigelegten Quelltext-Tarball; dies
+(5) Der Footer der Startseite verweist auf den beigelegten Quelltext-Tarball; dies
 verlangt AGPLv3 § 13 für den Netzwerkbetrieb. Als fortlaufende Zweitquelle ist
 die Anschrift nach § 3 Absatz 2 genannt.
 
diff --git a/deploy/webpaket.sh b/deploy/webpaket.sh
index 2003134..75ad471 100755
--- a/deploy/webpaket.sh
+++ b/deploy/webpaket.sh
@@ -3,11 +3,19 @@
 # „package“ des Profils -Pwasm, unmittelbar nach native-image.
 #
 #   1. Der Textzwischenschritt aendggner.js.wat (mehrere hundert Megabyte) wird entfernt.
-#   2. Der Quelltext der gebauten Fassung wird als Tarball beigelegt — AGPLv3 §13 verlangt
+#   2. Das Modul geht durch wasm-opt -Oz, sofern Binaryen zur Hand ist.
+#   3. Der Quelltext der gebauten Fassung wird als Tarball beigelegt — AGPLv3 §13 verlangt
 #      beim Netzwerkbetrieb einen Quellcode-Zugang für die Nutzer:innen, und der Footer der
 #      Startseite verweist darauf.
-#   3. Die großen Dateien werden vorkomprimiert, damit nginx sie mit gzip_static bzw.
-#      brotli_static ausliefern kann, statt 24 MB je Abruf neu zu packen.
+#   4. Nur die vom Bau erzeugten Dateien bleiben stehen; alles hier Liegende wird
+#      hochgeladen.
+#   5. Auf Wunsch (VORKOMPRIMIEREN=1) werden die großen Dateien vorkomprimiert.
+#
+# Zielplattform ist Cloudflare Workers: dort gilt eine Grenze von 25 MiB je Datei
+# (unkomprimiert), und komprimiert wird beim Ausliefern ohnehin. Deshalb ist die
+# Vorkompression abgeschaltet und die Größe jeder Datei wird geprüft. Wer stattdessen
+# selbst mit nginx ausliefert (deploy/nginx-aendggner.conf, gzip_static/brotli_static),
+# ruft das Skript mit VORKOMPRIMIEREN=1 auf.
 #
 # Aufruf: deploy/webpaket.sh <ausgabeverzeichnis>   (Standard: target/web)
 
@@ -16,15 +24,56 @@
 ziel="${1:-target/web}"
 quelle="$(CDPATH='' cd -- "$(dirname -- "$0")/.." && pwd)"
 
+# Cloudflare Workers nimmt keine Datei über 25 MiB an.
+grenze_datei=26214400
+# Der Quelltext misst ein Viertel Megabyte. Wächst das Archiv über diese Schranke, sind
+# wieder Massendaten ins Repository geraten, die dort nicht hingehören (siehe
+# .gitattributes) — dann lieber der Bau ab als ein unbrauchbares Paket.
+grenze_archiv=8388608
+
 if [ ! -d "$ziel" ]; then
   echo "webpaket: $ziel gibt es nicht — erst -Pwasm package laufen lassen." >&2
   exit 1
 fi
 
+groesse() {
+  wc -c <"$1" | tr -d ' '
+}
+
 # 1. Zwischenschritt fort.
 rm -f "$ziel/aendggner.js.wat"
 
-# 2. Quelltext der gebauten Fassung.
+# 2. Nachoptimierung. -Oz zieht die Codegröße noch einmal spürbar zusammen.
+#
+# Die Merkmale sind einzeln aufgezählt, nicht als --all-features: Binaryen darf ausgeben,
+# was es darf, und mit --all-features nutzt es auch Vorschläge, die noch kein Browser
+# annimmt — das Modul scheitert dann erst beim Instanziieren („invalid heap type 'exact'“
+# aus den custom descriptors, „invalid import kind 127“ aus der kompakten
+# Importsektion). Aufgeführt ist deshalb nur, was ausgeliefert in den Browsern steht und
+# was Web Image braucht: WasmGC samt Referenztypen, das Ausnahmen-Proposal, endständige
+# Aufrufe. Fehlt wasm-opt, bleibt das Modul, wie es ist — der Bau soll daran nicht
+# scheitern.
+wasm_merkmale="--enable-gc --enable-reference-types --enable-exception-handling
+               --enable-tail-call --enable-bulk-memory --enable-bulk-memory-opt
+               --enable-nontrapping-float-to-int --enable-sign-ext
+               --enable-mutable-globals --enable-multivalue --enable-extended-const
+               --enable-simd --enable-call-indirect-overlong"
+
+if [ -f "$ziel/aendggner.js.wasm" ]; then
+  vorher="$(groesse "$ziel/aendggner.js.wasm")"
+  if command -v wasm-opt >/dev/null 2>&1; then
+    # shellcheck disable=SC2086  # die Merkmalsliste soll in Wörter zerfallen
+    wasm-opt $wasm_merkmale -Oz \
+      -o "$ziel/aendggner.js.wasm.neu" "$ziel/aendggner.js.wasm"
+    mv "$ziel/aendggner.js.wasm.neu" "$ziel/aendggner.js.wasm"
+    echo "webpaket: wasm-opt: $vorher -> $(groesse "$ziel/aendggner.js.wasm") Bytes."
+  else
+    echo "webpaket: wasm-opt nicht gefunden — Modul bleibt unnachoptimiert" \
+         "($vorher Bytes)." >&2
+  fi
+fi
+
+# 3. Quelltext der gebauten Fassung.
 #
 # Ein veränderter Arbeitsbaum bricht den Bau ab: Der angebotene Quelltext muss der
 # ausgelieferten Fassung entsprechen, sonst ist die Auflage aus §13 verfehlt. Wer nur
@@ -43,8 +92,16 @@
 fi
 
 fassung="$(git -C "$quelle" rev-parse HEAD)"
-git -C "$quelle" archive --format=tar.gz --prefix=aendggner/ HEAD \
-  -o "$(CDPATH='' cd -- "$ziel" && pwd)/aendggner-quelltext.tar.gz"
+archiv="$(CDPATH='' cd -- "$ziel" && pwd)/aendggner-quelltext.tar.gz"
+git -C "$quelle" archive --format=tar.gz --prefix=aendggner/ HEAD -o "$archiv"
+
+archivgroesse="$(groesse "$archiv")"
+if [ "$archivgroesse" -gt "$grenze_archiv" ]; then
+  echo "webpaket: Das Quelltextarchiv misst $archivgroesse Bytes und überschreitet damit" >&2
+  echo "          die Schranke von $grenze_archiv. Vermutlich sind Massendaten ins" >&2
+  echo "          Repository geraten; sie gehören in .gitattributes (export-ignore)." >&2
+  exit 1
+fi
 
 {
   echo "ÄndGgner — Quelltext der ausgelieferten Fassung"
@@ -54,20 +111,46 @@
   echo
   echo "Vollständig in aendggner-quelltext.tar.gz; Bauanleitung darin in README.adoc."
   echo "Fortlaufend: https://gerrit.benkard.de/plugins/gitiles/aendggner"
+  echo
+  echo "Nicht im Archiv liegt der Beispielkorpus (src/test/resources/sampledata): Gesetzes-"
+  echo "und Drucksachentexte fremder Urheberschaft, an denen die Tests messen. Gebaut wird"
+  echo "ohne ihn; nur die Tests verlangen danach. Wer sie laufen lassen will, holt das"
+  echo "Repository von der oben genannten Adresse, wo der Korpus vollständig liegt."
 } > "$ziel/quelltext-fassung.txt"
 
-# 3. Vorkompression. Der Tarball ist bereits gepackt und bleibt außen vor.
-for datei in "$ziel"/*.wasm "$ziel"/*.js "$ziel"/*.css "$ziel"/*.html "$ziel"/*.svg \
-             "$ziel"/*.txt; do
+# 4. Nur das Gebaute bleibt liegen — was hier steht, geht hoch.
+rm -f "$ziel/.DS_Store"
+
+# 5. Vorkompression, nur auf Anforderung (siehe Kopf). Der Tarball ist bereits gepackt und
+# bleibt außen vor.
+if [ "${VORKOMPRIMIEREN:-}" = 1 ]; then
+  for datei in "$ziel"/*.wasm "$ziel"/*.js "$ziel"/*.css "$ziel"/*.html "$ziel"/*.svg \
+               "$ziel"/*.txt; do
+    [ -f "$datei" ] || continue
+    gzip -9 -f -k -- "$datei"
+    if command -v brotli >/dev/null 2>&1; then
+      brotli -f -- "$datei"
+    fi
+  done
+
+  if ! command -v brotli >/dev/null 2>&1; then
+    echo "webpaket: brotli nicht gefunden — nur .gz angelegt." >&2
+  fi
+fi
+
+# Was zu groß ist, nimmt Cloudflare nicht an. Lieber hier auffallen als beim Hochladen.
+zugross=""
+for datei in "$ziel"/*; do
   [ -f "$datei" ] || continue
-  gzip -9 -f -k -- "$datei"
-  if command -v brotli >/dev/null 2>&1; then
-    brotli -f -- "$datei"
+  if [ "$(groesse "$datei")" -gt "$grenze_datei" ]; then
+    zugross="$zugross  $(basename "$datei") ($(groesse "$datei") Bytes)
+"
   fi
 done
-
-if ! command -v brotli >/dev/null 2>&1; then
-  echo "webpaket: brotli nicht gefunden — nur .gz angelegt." >&2
+if [ -n "$zugross" ]; then
+  echo "webpaket: Diese Dateien überschreiten die 25-MiB-Grenze von Cloudflare Workers:" >&2
+  printf '%s' "$zugross" >&2
+  exit 1
 fi
 
 echo "webpaket: $ziel ist auslieferbar (Fassung $(git -C "$quelle" rev-parse --short HEAD))."
diff --git a/pom.xml b/pom.xml
index b66267f..fde6de0 100644
--- a/pom.xml
+++ b/pom.xml
@@ -20,6 +20,7 @@
     <enforced.maven-version>3.9.0</enforced.maven-version>
 
     <build-helper-plugin.version>3.6.0</build-helper-plugin.version>
+    <clean-plugin.version>3.3.2</clean-plugin.version>
     <compiler-plugin.version>3.15.0</compiler-plugin.version>
     <dependency-plugin.version>3.11.0</dependency-plugin.version>
     <enforcer-plugin.version>3.6.3</enforcer-plugin.version>
@@ -124,6 +125,20 @@
       </resource>
     </resources>
 
+    <!--
+      Der Beispielkorpus liegt unter src/test/resources, wird aber von den Tests über
+      Dateisystempfade gelesen, nicht über den Klassenpfad. Eine Kopie nach
+      target/test-classes wäre deshalb bei jedem Bau 35 MB umsonst.
+    -->
+    <testResources>
+      <testResource>
+        <directory>src/test/resources</directory>
+        <excludes>
+          <exclude>sampledata/**</exclude>
+        </excludes>
+      </testResource>
+    </testResources>
+
     <pluginManagement>
       <plugins>
 
@@ -332,20 +347,51 @@
             <artifactId>maven-compiler-plugin</artifactId>
             <configuration>
               <compilerArgs>
-                <arg>-Aproject=${project.groupId}/${project.artifactId}</arg>
                 <arg>--add-modules</arg>
                 <arg>org.graalvm.webimage.api</arg>
               </compilerArgs>
-              <annotationProcessorPaths>
-                <path>
-                  <groupId>info.picocli</groupId>
-                  <artifactId>picocli-codegen</artifactId>
-                  <version>${picocli.version}</version>
-                </path>
-              </annotationProcessorPaths>
+              <!--
+                Ohne Picocli-Annotationsprozessor: Er meldet die Befehlszeilenklasse
+                eu.mulk.aendggner.AendGgner mit „allDeclaredMethods“ zur Reflexion an, und
+                die Erreichbarkeitsanalyse zieht daraufhin die gesamte Befehlszeilenfassung
+                samt Picocli und den Dateizugriffen ins Browser-Image, obwohl BrowserMain
+                nichts davon anfasst. Im Browser gibt es keine Befehlszeile.
+              -->
+              <proc>none</proc>
+              <annotationProcessorPaths/>
             </configuration>
           </plugin>
 
+          <!--
+            Ein vorangegangener gewöhnlicher Bau hat die Picocli-Konfiguration womöglich
+            schon nach target/classes gelegt; native-image liest sie von dort auch dann,
+            wenn dieser Bau sie nicht mehr erzeugt. Also fort damit, sonst hinge die
+            Bildgröße davon ab, was vorher lief.
+          -->
+          <plugin>
+            <artifactId>maven-clean-plugin</artifactId>
+            <version>${clean-plugin.version}</version>
+            <executions>
+              <execution>
+                <id>picocli-konfiguration-fort</id>
+                <phase>prepare-package</phase>
+                <goals>
+                  <goal>clean</goal>
+                </goals>
+                <configuration>
+                  <excludeDefaultDirectories>true</excludeDefaultDirectories>
+                  <filesets>
+                    <fileset>
+                      <directory>
+                        ${project.build.outputDirectory}/META-INF/native-image/picocli-generated
+                      </directory>
+                    </fileset>
+                  </filesets>
+                </configuration>
+              </execution>
+            </executions>
+          </plugin>
+
           <plugin>
             <artifactId>maven-resources-plugin</artifactId>
             <executions>
@@ -381,6 +427,9 @@
                   <executable>${native-image.executable}</executable>
                   <arguments>
                     <argument>--tool:svm-wasm</argument>
+                    <!-- Größe vor Durchsatz: Das Modul muss über die Leitung, und die
+                         Rechenzeit einer Synopse liegt ohnehin im Sekundenbereich. -->
+                    <argument>-Os</argument>
                     <!-- Der Textzwischenschritt (aendggner.js.wat) wird sonst mehrere hundert
                          Megabyte groß; auf die Größe des .wasm hat das keinen Einfluss. -->
                     <argument>-H:WasmComments=NONE</argument>
diff --git a/src/main/resources/META-INF/native-image/eu.mulk/aendggner/reachability-metadata.json b/src/main/resources/META-INF/native-image/eu.mulk/aendggner/reachability-metadata.json
index 4f85735..5b0cc81 100644
--- a/src/main/resources/META-INF/native-image/eu.mulk/aendggner/reachability-metadata.json
+++ b/src/main/resources/META-INF/native-image/eu.mulk/aendggner/reachability-metadata.json
@@ -352,7 +352,10 @@
       "glob": "jdk/internal/icu/impl/data/icudt76b/ubidi.icu"
     },
     {
-      "glob": "org/apache/fontbox/**"
+      "glob": "org/apache/fontbox/cmap/Identity-H"
+    },
+    {
+      "glob": "org/apache/fontbox/cmap/Identity-V"
     },
     {
       "glob": "org/apache/pdfbox/resources/**"
diff --git a/src/test/java/eu/mulk/aendggner/EndToEndTest.java b/src/test/java/eu/mulk/aendggner/EndToEndTest.java
index 22f1662..75df65e 100644
--- a/src/test/java/eu/mulk/aendggner/EndToEndTest.java
+++ b/src/test/java/eu/mulk/aendggner/EndToEndTest.java
@@ -34,7 +34,7 @@
  */
 class EndToEndTest {
 
-  private static final Path SAMPLEDATA = Path.of("src/main/resources/sampledata");
+  private static final Path SAMPLEDATA = Path.of("src/test/resources/sampledata");
   private static final Path STAMMGESETZ = SAMPLEDATA.resolve("IfSG/BJNR104510000.xml");
   private static final Path AENDERUNGSGESETZ = SAMPLEDATA.resolve("IfSG/bgbl120s2397_78991.pdf");
 
diff --git a/src/test/java/eu/mulk/aendggner/aenderung/parse/DokumentErkennerTest.java b/src/test/java/eu/mulk/aendggner/aenderung/parse/DokumentErkennerTest.java
index 7271efb..b90e990 100644
--- a/src/test/java/eu/mulk/aendggner/aenderung/parse/DokumentErkennerTest.java
+++ b/src/test/java/eu/mulk/aendggner/aenderung/parse/DokumentErkennerTest.java
@@ -12,7 +12,7 @@
 
 class DokumentErkennerTest {
 
-  private static final Path SAMPLEDATA = Path.of("src/main/resources/sampledata");
+  private static final Path SAMPLEDATA = Path.of("src/test/resources/sampledata");
 
   /**
    * Die Erkennung ist eine Heuristik auf Kopfzeilen; sie wird deshalb gegen wirkliche Dokumente
diff --git a/src/test/java/eu/mulk/aendggner/aenderung/parse/ZusammenstellungsLeserTest.java b/src/test/java/eu/mulk/aendggner/aenderung/parse/ZusammenstellungsLeserTest.java
index 8e994a6..baaf056 100644
--- a/src/test/java/eu/mulk/aendggner/aenderung/parse/ZusammenstellungsLeserTest.java
+++ b/src/test/java/eu/mulk/aendggner/aenderung/parse/ZusammenstellungsLeserTest.java
@@ -16,7 +16,7 @@
 class ZusammenstellungsLeserTest {
 
   private static final Path EMPFEHLUNG =
-      Path.of("src/main/resources/sampledata/GEG/BT-Drs-20-7619_Beschlussempfehlung.pdf");
+      Path.of("src/test/resources/sampledata/GEG/BT-Drs-20-7619_Beschlussempfehlung.pdf");
 
   private static String fassung() throws Exception {
     try (var dokument = Loader.loadPDF(EMPFEHLUNG.toFile())) {
diff --git a/src/main/resources/sampledata/AGG/AGG.pdf b/src/test/resources/sampledata/AGG/AGG.pdf
similarity index 100%
rename from src/main/resources/sampledata/AGG/AGG.pdf
rename to src/test/resources/sampledata/AGG/AGG.pdf
Binary files differ
diff --git a/src/main/resources/sampledata/AGG/BJNR189710006.epub b/src/test/resources/sampledata/AGG/BJNR189710006.epub
similarity index 100%
rename from src/main/resources/sampledata/AGG/BJNR189710006.epub
rename to src/test/resources/sampledata/AGG/BJNR189710006.epub
Binary files differ
diff --git a/src/main/resources/sampledata/AGG/BJNR189710006.xml b/src/test/resources/sampledata/AGG/BJNR189710006.xml
similarity index 100%
rename from src/main/resources/sampledata/AGG/BJNR189710006.xml
rename to src/test/resources/sampledata/AGG/BJNR189710006.xml
diff --git a/src/main/resources/sampledata/AGG/BT-Drs-21-6178_Regierungsentwurf.pdf b/src/test/resources/sampledata/AGG/BT-Drs-21-6178_Regierungsentwurf.pdf
similarity index 100%
rename from src/main/resources/sampledata/AGG/BT-Drs-21-6178_Regierungsentwurf.pdf
rename to src/test/resources/sampledata/AGG/BT-Drs-21-6178_Regierungsentwurf.pdf
Binary files differ
diff --git a/src/main/resources/sampledata/AGG/RefE_2_AGGAEndG_2026-04-14.pdf b/src/test/resources/sampledata/AGG/RefE_2_AGGAEndG_2026-04-14.pdf
similarity index 100%
rename from src/main/resources/sampledata/AGG/RefE_2_AGGAEndG_2026-04-14.pdf
rename to src/test/resources/sampledata/AGG/RefE_2_AGGAEndG_2026-04-14.pdf
Binary files differ
diff --git a/src/main/resources/sampledata/AGG/RegE_AGG_AendG_2026-05-06.pdf b/src/test/resources/sampledata/AGG/RegE_AGG_AendG_2026-05-06.pdf
similarity index 100%
rename from src/main/resources/sampledata/AGG/RegE_AGG_AendG_2026-05-06.pdf
rename to src/test/resources/sampledata/AGG/RegE_AGG_AendG_2026-05-06.pdf
Binary files differ
diff --git a/src/main/resources/sampledata/AGG/SOURCES b/src/test/resources/sampledata/AGG/SOURCES
similarity index 100%
rename from src/main/resources/sampledata/AGG/SOURCES
rename to src/test/resources/sampledata/AGG/SOURCES
diff --git a/src/main/resources/sampledata/AGG/Synopse_AGGAendG_RegE_2026.pdf b/src/test/resources/sampledata/AGG/Synopse_AGGAendG_RegE_2026.pdf
similarity index 100%
rename from src/main/resources/sampledata/AGG/Synopse_AGGAendG_RegE_2026.pdf
rename to src/test/resources/sampledata/AGG/Synopse_AGGAendG_RegE_2026.pdf
Binary files differ
diff --git a/src/main/resources/sampledata/BadenWuerttemberg/GBl-2026-26_KommunalwahlO-AendVO.pdf b/src/test/resources/sampledata/BadenWuerttemberg/GBl-2026-26_KommunalwahlO-AendVO.pdf
similarity index 100%
rename from src/main/resources/sampledata/BadenWuerttemberg/GBl-2026-26_KommunalwahlO-AendVO.pdf
rename to src/test/resources/sampledata/BadenWuerttemberg/GBl-2026-26_KommunalwahlO-AendVO.pdf
Binary files differ
diff --git a/src/main/resources/sampledata/BayJG/BayJG-alt.txt b/src/test/resources/sampledata/BayJG/BayJG-alt.txt
similarity index 100%
rename from src/main/resources/sampledata/BayJG/BayJG-alt.txt
rename to src/test/resources/sampledata/BayJG/BayJG-alt.txt
diff --git a/src/main/resources/sampledata/BayJG/BayJG.pdf b/src/test/resources/sampledata/BayJG/BayJG.pdf
similarity index 100%
rename from src/main/resources/sampledata/BayJG/BayJG.pdf
rename to src/test/resources/sampledata/BayJG/BayJG.pdf
Binary files differ
diff --git a/src/main/resources/sampledata/BayJG/Ltg-Drs-19-10365_Aenderungsantrag-Gruene.pdf b/src/test/resources/sampledata/BayJG/Ltg-Drs-19-10365_Aenderungsantrag-Gruene.pdf
similarity index 100%
rename from src/main/resources/sampledata/BayJG/Ltg-Drs-19-10365_Aenderungsantrag-Gruene.pdf
rename to src/test/resources/sampledata/BayJG/Ltg-Drs-19-10365_Aenderungsantrag-Gruene.pdf
Binary files differ
diff --git a/src/main/resources/sampledata/BayJG/Ltg-Drs-19-9707_Gesetzentwurf.pdf b/src/test/resources/sampledata/BayJG/Ltg-Drs-19-9707_Gesetzentwurf.pdf
similarity index 100%
rename from src/main/resources/sampledata/BayJG/Ltg-Drs-19-9707_Gesetzentwurf.pdf
rename to src/test/resources/sampledata/BayJG/Ltg-Drs-19-9707_Gesetzentwurf.pdf
Binary files differ
diff --git a/src/main/resources/sampledata/BayJG/Plenarprotokoll-19-72_2te-Lesung.pdf b/src/test/resources/sampledata/BayJG/Plenarprotokoll-19-72_2te-Lesung.pdf
similarity index 100%
rename from src/main/resources/sampledata/BayJG/Plenarprotokoll-19-72_2te-Lesung.pdf
rename to src/test/resources/sampledata/BayJG/Plenarprotokoll-19-72_2te-Lesung.pdf
Binary files differ
diff --git a/src/main/resources/sampledata/BayJG/SOURCES b/src/test/resources/sampledata/BayJG/SOURCES
similarity index 100%
rename from src/main/resources/sampledata/BayJG/SOURCES
rename to src/test/resources/sampledata/BayJG/SOURCES
diff --git a/src/main/resources/sampledata/BayJG/gvbl-2026-06.pdf b/src/test/resources/sampledata/BayJG/gvbl-2026-06.pdf
similarity index 100%
rename from src/main/resources/sampledata/BayJG/gvbl-2026-06.pdf
rename to src/test/resources/sampledata/BayJG/gvbl-2026-06.pdf
Binary files differ
diff --git a/src/main/resources/sampledata/Berlin/GVBl-2026-17_ASOG-LAF-AendG.pdf b/src/test/resources/sampledata/Berlin/GVBl-2026-17_ASOG-LAF-AendG.pdf
similarity index 100%
rename from src/main/resources/sampledata/Berlin/GVBl-2026-17_ASOG-LAF-AendG.pdf
rename to src/test/resources/sampledata/Berlin/GVBl-2026-17_ASOG-LAF-AendG.pdf
Binary files differ
diff --git a/src/main/resources/sampledata/GEG/BJNR172810020.epub b/src/test/resources/sampledata/GEG/BJNR172810020.epub
similarity index 100%
rename from src/main/resources/sampledata/GEG/BJNR172810020.epub
rename to src/test/resources/sampledata/GEG/BJNR172810020.epub
Binary files differ
diff --git a/src/main/resources/sampledata/GEG/BJNR172810020.xml b/src/test/resources/sampledata/GEG/BJNR172810020.xml
similarity index 100%
rename from src/main/resources/sampledata/GEG/BJNR172810020.xml
rename to src/test/resources/sampledata/GEG/BJNR172810020.xml
diff --git a/src/main/resources/sampledata/GEG/BR-Drs-170-23.pdf b/src/test/resources/sampledata/GEG/BR-Drs-170-23.pdf
similarity index 100%
rename from src/main/resources/sampledata/GEG/BR-Drs-170-23.pdf
rename to src/test/resources/sampledata/GEG/BR-Drs-170-23.pdf
Binary files differ
diff --git a/src/main/resources/sampledata/GEG/BT-Drs-20-6875_Regierungsentwurf.pdf b/src/test/resources/sampledata/GEG/BT-Drs-20-6875_Regierungsentwurf.pdf
similarity index 100%
rename from src/main/resources/sampledata/GEG/BT-Drs-20-6875_Regierungsentwurf.pdf
rename to src/test/resources/sampledata/GEG/BT-Drs-20-6875_Regierungsentwurf.pdf
Binary files differ
diff --git a/src/main/resources/sampledata/GEG/BT-Drs-20-7619_Beschlussempfehlung.pdf b/src/test/resources/sampledata/GEG/BT-Drs-20-7619_Beschlussempfehlung.pdf
similarity index 100%
rename from src/main/resources/sampledata/GEG/BT-Drs-20-7619_Beschlussempfehlung.pdf
rename to src/test/resources/sampledata/GEG/BT-Drs-20-7619_Beschlussempfehlung.pdf
Binary files differ
diff --git a/src/main/resources/sampledata/GEG/BT-Drs-21-6278_Regierungsentwurf.pdf b/src/test/resources/sampledata/GEG/BT-Drs-21-6278_Regierungsentwurf.pdf
similarity index 100%
rename from src/main/resources/sampledata/GEG/BT-Drs-21-6278_Regierungsentwurf.pdf
rename to src/test/resources/sampledata/GEG/BT-Drs-21-6278_Regierungsentwurf.pdf
Binary files differ
diff --git a/src/main/resources/sampledata/GEG/BT-Drs-21-7071_Beschlussempfehlung.pdf b/src/test/resources/sampledata/GEG/BT-Drs-21-7071_Beschlussempfehlung.pdf
similarity index 100%
rename from src/main/resources/sampledata/GEG/BT-Drs-21-7071_Beschlussempfehlung.pdf
rename to src/test/resources/sampledata/GEG/BT-Drs-21-7071_Beschlussempfehlung.pdf
Binary files differ
diff --git a/src/main/resources/sampledata/GEG/GEG.pdf b/src/test/resources/sampledata/GEG/GEG.pdf
similarity index 100%
rename from src/main/resources/sampledata/GEG/GEG.pdf
rename to src/test/resources/sampledata/GEG/GEG.pdf
Binary files differ
diff --git a/src/main/resources/sampledata/GEG/Referentenentwurf_GModG_2026-05-05.pdf b/src/test/resources/sampledata/GEG/Referentenentwurf_GModG_2026-05-05.pdf
similarity index 100%
rename from src/main/resources/sampledata/GEG/Referentenentwurf_GModG_2026-05-05.pdf
rename to src/test/resources/sampledata/GEG/Referentenentwurf_GModG_2026-05-05.pdf
Binary files differ
diff --git a/src/main/resources/sampledata/GEG/Regierungsentwurf_GModG_2026-05-13.pdf b/src/test/resources/sampledata/GEG/Regierungsentwurf_GModG_2026-05-13.pdf
similarity index 100%
rename from src/main/resources/sampledata/GEG/Regierungsentwurf_GModG_2026-05-13.pdf
rename to src/test/resources/sampledata/GEG/Regierungsentwurf_GModG_2026-05-13.pdf
Binary files differ
diff --git a/src/main/resources/sampledata/GEG/SOURCES b/src/test/resources/sampledata/GEG/SOURCES
similarity index 100%
rename from src/main/resources/sampledata/GEG/SOURCES
rename to src/test/resources/sampledata/GEG/SOURCES
diff --git a/src/main/resources/sampledata/GEG/bgbl123s0280_regelungstext.pdf b/src/test/resources/sampledata/GEG/bgbl123s0280_regelungstext.pdf
similarity index 100%
rename from src/main/resources/sampledata/GEG/bgbl123s0280_regelungstext.pdf
rename to src/test/resources/sampledata/GEG/bgbl123s0280_regelungstext.pdf
Binary files differ
diff --git a/src/main/resources/sampledata/Hessen/GVBl-2026-05_11-AendVO-verkehrsrechtl-Zustaendigkeiten.pdf b/src/test/resources/sampledata/Hessen/GVBl-2026-05_11-AendVO-verkehrsrechtl-Zustaendigkeiten.pdf
similarity index 100%
rename from src/main/resources/sampledata/Hessen/GVBl-2026-05_11-AendVO-verkehrsrechtl-Zustaendigkeiten.pdf
rename to src/test/resources/sampledata/Hessen/GVBl-2026-05_11-AendVO-verkehrsrechtl-Zustaendigkeiten.pdf
Binary files differ
diff --git a/src/main/resources/sampledata/IfSG/0645-20.pdf b/src/test/resources/sampledata/IfSG/0645-20.pdf
similarity index 100%
rename from src/main/resources/sampledata/IfSG/0645-20.pdf
rename to src/test/resources/sampledata/IfSG/0645-20.pdf
Binary files differ
diff --git a/src/main/resources/sampledata/IfSG/1918111.pdf b/src/test/resources/sampledata/IfSG/1918111.pdf
similarity index 100%
rename from src/main/resources/sampledata/IfSG/1918111.pdf
rename to src/test/resources/sampledata/IfSG/1918111.pdf
Binary files differ
diff --git a/src/main/resources/sampledata/IfSG/1923944.pdf b/src/test/resources/sampledata/IfSG/1923944.pdf
similarity index 100%
rename from src/main/resources/sampledata/IfSG/1923944.pdf
rename to src/test/resources/sampledata/IfSG/1923944.pdf
Binary files differ
diff --git a/src/main/resources/sampledata/IfSG/1924232.pdf b/src/test/resources/sampledata/IfSG/1924232.pdf
similarity index 100%
rename from src/main/resources/sampledata/IfSG/1924232.pdf
rename to src/test/resources/sampledata/IfSG/1924232.pdf
Binary files differ
diff --git a/src/main/resources/sampledata/IfSG/1924334.pdf b/src/test/resources/sampledata/IfSG/1924334.pdf
similarity index 100%
rename from src/main/resources/sampledata/IfSG/1924334.pdf
rename to src/test/resources/sampledata/IfSG/1924334.pdf
Binary files differ
diff --git a/src/main/resources/sampledata/IfSG/BJNR104510000.epub b/src/test/resources/sampledata/IfSG/BJNR104510000.epub
similarity index 100%
rename from src/main/resources/sampledata/IfSG/BJNR104510000.epub
rename to src/test/resources/sampledata/IfSG/BJNR104510000.epub
Binary files differ
diff --git a/src/main/resources/sampledata/IfSG/BJNR104510000.xml b/src/test/resources/sampledata/IfSG/BJNR104510000.xml
similarity index 100%
rename from src/main/resources/sampledata/IfSG/BJNR104510000.xml
rename to src/test/resources/sampledata/IfSG/BJNR104510000.xml
diff --git "a/src/main/resources/sampledata/IfSG/IfSG - Gesetz zur Verh\303\274tung und Bek\303\244mpfung von Infektionskrankheiten beim Menschen.webarchive" "b/src/test/resources/sampledata/IfSG/IfSG - Gesetz zur Verh\303\274tung und Bek\303\244mpfung von Infektionskrankheiten beim Menschen.webarchive"
similarity index 100%
rename from "src/main/resources/sampledata/IfSG/IfSG - Gesetz zur Verh\303\274tung und Bek\303\244mpfung von Infektionskrankheiten beim Menschen.webarchive"
rename to "src/test/resources/sampledata/IfSG/IfSG - Gesetz zur Verh\303\274tung und Bek\303\244mpfung von Infektionskrankheiten beim Menschen.webarchive"
Binary files differ
diff --git a/src/main/resources/sampledata/IfSG/IfSG.pdf b/src/test/resources/sampledata/IfSG/IfSG.pdf
similarity index 100%
rename from src/main/resources/sampledata/IfSG/IfSG.pdf
rename to src/test/resources/sampledata/IfSG/IfSG.pdf
Binary files differ
diff --git a/src/main/resources/sampledata/IfSG/SOURCES b/src/test/resources/sampledata/IfSG/SOURCES
similarity index 100%
rename from src/main/resources/sampledata/IfSG/SOURCES
rename to src/test/resources/sampledata/IfSG/SOURCES
diff --git a/src/main/resources/sampledata/IfSG/bgbl120s2397_78991.pdf b/src/test/resources/sampledata/IfSG/bgbl120s2397_78991.pdf
similarity index 100%
rename from src/main/resources/sampledata/IfSG/bgbl120s2397_78991.pdf
rename to src/test/resources/sampledata/IfSG/bgbl120s2397_78991.pdf
Binary files differ
diff --git a/src/main/resources/sampledata/Landesrecht-Beispiele.adoc b/src/test/resources/sampledata/Landesrecht-Beispiele.adoc
similarity index 100%
rename from src/main/resources/sampledata/Landesrecht-Beispiele.adoc
rename to src/test/resources/sampledata/Landesrecht-Beispiele.adoc
diff --git a/src/main/resources/sampledata/NRW/17-RAEStV-AusfG-alt.txt b/src/test/resources/sampledata/NRW/17-RAEStV-AusfG-alt.txt
similarity index 100%
rename from src/main/resources/sampledata/NRW/17-RAEStV-AusfG-alt.txt
rename to src/test/resources/sampledata/NRW/17-RAEStV-AusfG-alt.txt
diff --git a/src/main/resources/sampledata/NRW/GV-NRW-2026-S202_22-Rundfunkaenderungsgesetz.pdf b/src/test/resources/sampledata/NRW/GV-NRW-2026-S202_22-Rundfunkaenderungsgesetz.pdf
similarity index 100%
rename from src/main/resources/sampledata/NRW/GV-NRW-2026-S202_22-Rundfunkaenderungsgesetz.pdf
rename to src/test/resources/sampledata/NRW/GV-NRW-2026-S202_22-Rundfunkaenderungsgesetz.pdf
Binary files differ
diff --git a/src/main/resources/sampledata/NRW/LMG-NRW-alt.txt b/src/test/resources/sampledata/NRW/LMG-NRW-alt.txt
similarity index 100%
rename from src/main/resources/sampledata/NRW/LMG-NRW-alt.txt
rename to src/test/resources/sampledata/NRW/LMG-NRW-alt.txt
diff --git a/src/main/resources/sampledata/NRW/SOURCES b/src/test/resources/sampledata/NRW/SOURCES
similarity index 100%
rename from src/main/resources/sampledata/NRW/SOURCES
rename to src/test/resources/sampledata/NRW/SOURCES
diff --git a/src/main/resources/sampledata/NRW/TMZ-Gesetz-alt.txt b/src/test/resources/sampledata/NRW/TMZ-Gesetz-alt.txt
similarity index 100%
rename from src/main/resources/sampledata/NRW/TMZ-Gesetz-alt.txt
rename to src/test/resources/sampledata/NRW/TMZ-Gesetz-alt.txt
diff --git a/src/main/resources/sampledata/NRW/WDR-Gesetz-alt.txt b/src/test/resources/sampledata/NRW/WDR-Gesetz-alt.txt
similarity index 100%
rename from src/main/resources/sampledata/NRW/WDR-Gesetz-alt.txt
rename to src/test/resources/sampledata/NRW/WDR-Gesetz-alt.txt
diff --git a/src/main/resources/sampledata/Niedersachsen/NEFG-alt.txt b/src/test/resources/sampledata/Niedersachsen/NEFG-alt.txt
similarity index 100%
rename from src/main/resources/sampledata/Niedersachsen/NEFG-alt.txt
rename to src/test/resources/sampledata/Niedersachsen/NEFG-alt.txt
diff --git a/src/main/resources/sampledata/Niedersachsen/Nds-GVBl-2026-10_ELER-Foerdergesetz-AendG.pdf b/src/test/resources/sampledata/Niedersachsen/Nds-GVBl-2026-10_ELER-Foerdergesetz-AendG.pdf
similarity index 100%
rename from src/main/resources/sampledata/Niedersachsen/Nds-GVBl-2026-10_ELER-Foerdergesetz-AendG.pdf
rename to src/test/resources/sampledata/Niedersachsen/Nds-GVBl-2026-10_ELER-Foerdergesetz-AendG.pdf
Binary files differ
diff --git a/src/main/resources/sampledata/Niedersachsen/Nds-GVBl-2026-23_BeurtVO-Steuer_amtliche-Satznummern.pdf b/src/test/resources/sampledata/Niedersachsen/Nds-GVBl-2026-23_BeurtVO-Steuer_amtliche-Satznummern.pdf
similarity index 100%
rename from src/main/resources/sampledata/Niedersachsen/Nds-GVBl-2026-23_BeurtVO-Steuer_amtliche-Satznummern.pdf
rename to src/test/resources/sampledata/Niedersachsen/Nds-GVBl-2026-23_BeurtVO-Steuer_amtliche-Satznummern.pdf
Binary files differ
diff --git a/src/main/resources/sampledata/ProdHaftG/BJNR021980989.epub b/src/test/resources/sampledata/ProdHaftG/BJNR021980989.epub
similarity index 100%
rename from src/main/resources/sampledata/ProdHaftG/BJNR021980989.epub
rename to src/test/resources/sampledata/ProdHaftG/BJNR021980989.epub
Binary files differ
diff --git a/src/main/resources/sampledata/ProdHaftG/BJNR021980989.xml b/src/test/resources/sampledata/ProdHaftG/BJNR021980989.xml
similarity index 100%
rename from src/main/resources/sampledata/ProdHaftG/BJNR021980989.xml
rename to src/test/resources/sampledata/ProdHaftG/BJNR021980989.xml
diff --git a/src/main/resources/sampledata/ProdHaftG/BT-Drs-21-4297_Regierungsentwurf.pdf b/src/test/resources/sampledata/ProdHaftG/BT-Drs-21-4297_Regierungsentwurf.pdf
similarity index 100%
rename from src/main/resources/sampledata/ProdHaftG/BT-Drs-21-4297_Regierungsentwurf.pdf
rename to src/test/resources/sampledata/ProdHaftG/BT-Drs-21-4297_Regierungsentwurf.pdf
Binary files differ
diff --git a/src/main/resources/sampledata/ProdHaftG/ProdHaftG.pdf b/src/test/resources/sampledata/ProdHaftG/ProdHaftG.pdf
similarity index 100%
rename from src/main/resources/sampledata/ProdHaftG/ProdHaftG.pdf
rename to src/test/resources/sampledata/ProdHaftG/ProdHaftG.pdf
Binary files differ
diff --git a/src/main/resources/sampledata/ProdHaftG/RefE_Produkthaftung_2025-09-11.pdf b/src/test/resources/sampledata/ProdHaftG/RefE_Produkthaftung_2025-09-11.pdf
similarity index 100%
rename from src/main/resources/sampledata/ProdHaftG/RefE_Produkthaftung_2025-09-11.pdf
rename to src/test/resources/sampledata/ProdHaftG/RefE_Produkthaftung_2025-09-11.pdf
Binary files differ
diff --git a/src/main/resources/sampledata/ProdHaftG/RegE_Produkthaftungsrecht_2025-12-17.pdf b/src/test/resources/sampledata/ProdHaftG/RegE_Produkthaftungsrecht_2025-12-17.pdf
similarity index 100%
rename from src/main/resources/sampledata/ProdHaftG/RegE_Produkthaftungsrecht_2025-12-17.pdf
rename to src/test/resources/sampledata/ProdHaftG/RegE_Produkthaftungsrecht_2025-12-17.pdf
Binary files differ
diff --git a/src/main/resources/sampledata/ProdHaftG/SOURCES b/src/test/resources/sampledata/ProdHaftG/SOURCES
similarity index 100%
rename from src/main/resources/sampledata/ProdHaftG/SOURCES
rename to src/test/resources/sampledata/ProdHaftG/SOURCES
diff --git a/src/main/resources/sampledata/ProdHaftG/Synopse_Produkthaftungsrecht_RegE.pdf b/src/test/resources/sampledata/ProdHaftG/Synopse_Produkthaftungsrecht_RegE.pdf
similarity index 100%
rename from src/main/resources/sampledata/ProdHaftG/Synopse_Produkthaftungsrecht_RegE.pdf
rename to src/test/resources/sampledata/ProdHaftG/Synopse_Produkthaftungsrecht_RegE.pdf
Binary files differ
diff --git a/src/main/resources/sampledata/Sachsen/SaechsBeamtVG-alt.txt b/src/test/resources/sampledata/Sachsen/SaechsBeamtVG-alt.txt
similarity index 100%
rename from src/main/resources/sampledata/Sachsen/SaechsBeamtVG-alt.txt
rename to src/test/resources/sampledata/Sachsen/SaechsBeamtVG-alt.txt
diff --git a/src/main/resources/sampledata/Sachsen/SaechsGVBl-2026-S134_AendG-SaechsBeamtVG_revosax.pdf b/src/test/resources/sampledata/Sachsen/SaechsGVBl-2026-S134_AendG-SaechsBeamtVG_revosax.pdf
similarity index 100%
rename from src/main/resources/sampledata/Sachsen/SaechsGVBl-2026-S134_AendG-SaechsBeamtVG_revosax.pdf
rename to src/test/resources/sampledata/Sachsen/SaechsGVBl-2026-S134_AendG-SaechsBeamtVG_revosax.pdf
Binary files differ
diff --git a/src/main/resources/sampledata/SchleswigHolstein/GVOBl-2026-27_Kommunalrecht-AendG.pdf b/src/test/resources/sampledata/SchleswigHolstein/GVOBl-2026-27_Kommunalrecht-AendG.pdf
similarity index 100%
rename from src/main/resources/sampledata/SchleswigHolstein/GVOBl-2026-27_Kommunalrecht-AendG.pdf
rename to src/test/resources/sampledata/SchleswigHolstein/GVOBl-2026-27_Kommunalrecht-AendG.pdf
Binary files differ
diff --git a/src/main/resources/sampledata/Thueringen/GVBl-TH-2026-02_KiGaFinanzVO-AendVO-ua.pdf b/src/test/resources/sampledata/Thueringen/GVBl-TH-2026-02_KiGaFinanzVO-AendVO-ua.pdf
similarity index 100%
rename from src/main/resources/sampledata/Thueringen/GVBl-TH-2026-02_KiGaFinanzVO-AendVO-ua.pdf
rename to src/test/resources/sampledata/Thueringen/GVBl-TH-2026-02_KiGaFinanzVO-AendVO-ua.pdf
Binary files differ
diff --git a/src/main/resources/sampledata/Thueringen/SOURCES b/src/test/resources/sampledata/Thueringen/SOURCES
similarity index 100%
rename from src/main/resources/sampledata/Thueringen/SOURCES
rename to src/test/resources/sampledata/Thueringen/SOURCES
diff --git a/src/main/resources/sampledata/Thueringen/ThuerKigaFinVO-alt.txt b/src/test/resources/sampledata/Thueringen/ThuerKigaFinVO-alt.txt
similarity index 100%
rename from src/main/resources/sampledata/Thueringen/ThuerKigaFinVO-alt.txt
rename to src/test/resources/sampledata/Thueringen/ThuerKigaFinVO-alt.txt
diff --git a/src/main/resources/sampledata/UWG/BJNR141400004.epub b/src/test/resources/sampledata/UWG/BJNR141400004.epub
similarity index 100%
rename from src/main/resources/sampledata/UWG/BJNR141400004.epub
rename to src/test/resources/sampledata/UWG/BJNR141400004.epub
Binary files differ
diff --git a/src/main/resources/sampledata/UWG/BJNR141400004.xml b/src/test/resources/sampledata/UWG/BJNR141400004.xml
similarity index 100%
rename from src/main/resources/sampledata/UWG/BJNR141400004.xml
rename to src/test/resources/sampledata/UWG/BJNR141400004.xml
diff --git a/src/main/resources/sampledata/UWG/BR-Drs-8-26.pdf b/src/test/resources/sampledata/UWG/BR-Drs-8-26.pdf
similarity index 100%
rename from src/main/resources/sampledata/UWG/BR-Drs-8-26.pdf
rename to src/test/resources/sampledata/UWG/BR-Drs-8-26.pdf
Binary files differ
diff --git a/src/main/resources/sampledata/UWG/BR-Drs-8-26_zu.pdf b/src/test/resources/sampledata/UWG/BR-Drs-8-26_zu.pdf
similarity index 100%
rename from src/main/resources/sampledata/UWG/BR-Drs-8-26_zu.pdf
rename to src/test/resources/sampledata/UWG/BR-Drs-8-26_zu.pdf
Binary files differ
diff --git a/src/main/resources/sampledata/UWG/BT-Drs-21-1855_Regierungsentwurf.pdf b/src/test/resources/sampledata/UWG/BT-Drs-21-1855_Regierungsentwurf.pdf
similarity index 100%
rename from src/main/resources/sampledata/UWG/BT-Drs-21-1855_Regierungsentwurf.pdf
rename to src/test/resources/sampledata/UWG/BT-Drs-21-1855_Regierungsentwurf.pdf
Binary files differ
diff --git a/src/main/resources/sampledata/UWG/SOURCES b/src/test/resources/sampledata/UWG/SOURCES
similarity index 100%
rename from src/main/resources/sampledata/UWG/SOURCES
rename to src/test/resources/sampledata/UWG/SOURCES
diff --git a/src/main/resources/sampledata/UWG/UWG.pdf b/src/test/resources/sampledata/UWG/UWG.pdf
similarity index 100%
rename from src/main/resources/sampledata/UWG/UWG.pdf
rename to src/test/resources/sampledata/UWG/UWG.pdf
Binary files differ
diff --git a/src/main/resources/sampledata/UWG/bgbl126s0043_regelungstext.pdf b/src/test/resources/sampledata/UWG/bgbl126s0043_regelungstext.pdf
similarity index 100%
rename from src/main/resources/sampledata/UWG/bgbl126s0043_regelungstext.pdf
rename to src/test/resources/sampledata/UWG/bgbl126s0043_regelungstext.pdf
Binary files differ