# Phase 2 auf Prod in Betrieb nehmen Reihenfolge ist verbindlich. Jeder Schritt hat eine Prüfung; schlägt sie fehl, hilft der nächste Schritt nicht. Alles läuft als Benutzer `podman`, dessen Home `/home/podman` ist — `%h` in den Units löst dorthin auf. Die Datenbankverbindung steht bereits in `~/.config/wurzelwerk/database.env`. ## 0. Migrationen — erledigt ```sh psql "$DATABASE_URL" -c 'select name, angewandt_am from schema_migrationen order by name' ``` Sechs Zeilen, `p2-000` bis `p2-005`. Fehlt eine, siehe README, Abschnitt „Migrationen" — `p2-002` und `p2-003` brauchen einen Superuser. ## 1. Abbild bauen ```sh cd /pfad/zum/repo && git pull podman build -t wurzelwerk-p2 . ``` 2,12 GB, davon 568 MB spaCy-Modell und 196 MB torch. Prüfung: ```sh podman run --rm --entrypoint ls localhost/wurzelwerk-p2 /app/lexikon/ ``` Zwölf Dateien. Fehlen `landtage.json`, `regierung.json` oder `sondergesandte.json`, ist das Abbild älter als der Stand vom 08.09.2026. ## 2. Lexikon in die Tabellen laden `akteure.py` liest aus `akteure` und `amtsinhaber`, nicht aus den JSON-Dateien. Ohne diesen Schritt läuft Modul 4 durch und löst nichts auf, **ohne zu scheitern** — der teuerste stille Fehler der ganzen Einrichtung. ```sh podman run --rm --network=bebop-net \ --env-file ~/.config/wurzelwerk/database.env \ localhost/wurzelwerk-p2 lexikon.py --laden ``` Erwartet: `person 5631`, `organisation 2319`, `partei 682`, `staat 195`, `amt 29`, `amtsinhaber 418`. Die `Ueberschneidung:`-Zeilen sind die Betriebssicht auf Doppelspitzen und Bremer Bürgermeister, kein Fehler. Das Netz ist nicht schmückend: `DATABASE_URL` zeigt auf den Rechnernamen `postgres`, und den löst nur Podmans DNS innerhalb von `bebop-net` auf. Ohne `--network=bebop-net` scheitert die Namensauflösung, nicht die Anmeldung — die Fehlermeldung nennt dann den Namen, nicht das Passwort. ## 3. Modell-Volume anlegen und einmal füllen ```sh podman volume create wurzelwerk-modelle ``` Der erste Lauf braucht Netz und lädt 2,2 GB. Er muss **von Hand** laufen, nicht aus dem Timer: der Erstbestand über den gesamten Korpus dauert etwa 17 Minuten je 5000 Items, und `TimeoutStartSec=1800` ist dafür knapp. ```sh podman run --rm --network=bebop-net --memory=8g --shm-size=1g \ --volume wurzelwerk-modelle:/cache:U \ --env-file ~/.config/wurzelwerk/database.env \ --env OMP_NUM_THREADS=4 --env HF_HUB_OFFLINE=0 \ localhost/wurzelwerk-p2 phase2.py --backfill ``` Prüfung — vier Ebenen, je eine `coder_version`, alle gleich hoch: ```sh psql "$DATABASE_URL" -c \ 'select ebene, coder_version, count(*) from codings group by 1,2 order by 1' psql "$DATABASE_URL" -c 'select count(*) from item_embeddings' ``` Stehen für eine Ebene zwei Versionen nebeneinander, ist der Lauf unterbrochen worden und wurde mit geändertem Code fortgesetzt. Dann die ältere löschen und den Backfill wiederholen. ## 4. Umgebung für Phase 2 ```sh cp deploy/p2.env.beispiel ~/.config/wurzelwerk/p2.env chmod 600 ~/.config/wurzelwerk/p2.env $EDITOR ~/.config/wurzelwerk/p2.env # HF_HUB_OFFLINE=1 setzen ``` `HF_HUB_OFFLINE=1` erst jetzt, nach dem erfolgreichen Erstlauf. Danach geht das Modell nie wieder ins Netz und lädt in zwei Sekunden aus dem Volume. ## 5. Units einhängen ```sh cp deploy/wurzelwerk-p2.container ~/.config/containers/systemd/ cp deploy/wurzelwerk-p2.timer ~/.config/systemd/user/ systemctl --user daemon-reload # Was Quadlet daraus erzeugt hat, vor dem ersten Start ansehen: /usr/lib/systemd/user-generators/podman-user-generator --dryrun systemctl --user start wurzelwerk-p2.service # ein Lauf von Hand journalctl --user -u wurzelwerk-p2 -n 40 ``` Erst wenn dieser eine Lauf sauber durchgeht — er sollte `kein offener Slot` melden, weil der Backfill alles kodiert hat —, den Timer scharfstellen: ```sh systemctl --user enable --now wurzelwerk-p2.timer systemctl --user list-timers wurzelwerk-p2.timer ``` Feuert `*:03,18,33,48`, versetzt zum 15-Minuten-Raster von Phase 1. Damit die Units auch ohne angemeldete Sitzung laufen: ```sh loginctl enable-linger podman ``` ## 6. Nach der ersten Stunde ```sh psql "$DATABASE_URL" -c \ "select modul, coder_version, items_gesehen, items_kodiert, items_fehler, begonnen_am from p2_laeufe order by begonnen_am desc limit 12" ``` Vier Zeilen je Slot, `items_fehler = 0`. Ein Modul, das dauerhaft fehlt, ist abgestürzt, ohne die anderen mitzureissen — genau dafür ist die Schichtung da, aber es fällt dann eben auch nur hier auf. ## Was diese Phase nicht tut `raw_items` wird nie geschrieben. Der gesamte Phase-2-Bestand — `codings`, `item_embeddings`, `p2_laeufe` — ist verwerfbar und aus `raw_items` neu herstellbar. Ein Rückbau ist deshalb `truncate`, kein Wiederherstellen aus einem Backup.