# Intent: Builder-Deploy ohne Self-Termination und Doppelverarbeitung ## Ziel Ein vom Board ausgelöster Builder darf beim automatischen Deployment auf `blunix` weder seinen eigenen Prozess noch das Intent Board in einem halbaktivierten Zustand zurücklassen. Nach Commit und Push muss das Deployment außerhalb des Lebenszyklus von `agent-codex-builder.service` ausgeführt werden und bei Unterbrechungen idempotent fortsetzbar sein. ## Beobachteter Produktionsfehler Beim Lauf für `board-keep-cards-visible-during-builder-stash` trat am 07.09.2026 folgender Ablauf auf: 1. Um 12:49:29 startete der Board-Job. 2. Der Builder erzeugte und pushte Commit `62c9ce2`. 3. Um 12:50:56 startete `deploy_blunix()` synchron `nixos-rebuild switch` innerhalb von `agent-codex-builder.service`. 4. Während der Aktivierung stoppte systemd um 12:51:31 `board-agent-trigger.service` und anschließend den Builder-Prozess mit `SIGTERM` beziehungsweise Status 143. 5. Der SIGTERM-Handler legte denselben Job wieder in die Queue. 6. Der zweite Modelllauf erzeugte und pushte den zusätzlichen Commit `90ef0c8` und startete erneut ein Deployment. 7. Derselbe Self-Termination-Ablauf wiederholte sich um 12:53:27. 8. Erst ein dritter Lauf endete ohne weiteres Deployment, weil keine zusätzlichen Änderungen mehr entstanden waren. 9. `board-agent-trigger.service` blieb `inactive (dead)`, obwohl er deklarativ aktiviert und mit `Restart=always` konfiguriert ist. Das war kein Python-Crash des Board-Servers. Der Prozess wurde im Rahmen einer vom Builder selbst gestarteten, dann unterbrochenen NixOS-Aktivierung geordnet mit `SIGTERM` beendet. ## Belegte Ursache - `builder.py:deploy_blunix()` führt `sudo nixos-rebuild switch` synchron als Kindprozess des Builders aus. - `agent-codex-builder.service` ist die Unit, aus der dieser Rebuild gestartet wird. Sobald die neue Konfiguration diese Unit während der Aktivierung stoppt, werden Builder und dessen Rebuild-Prozess gemeinsam beendet. - `builder.py:_handle_sigterm()` requeued jeden aktiven Job unabhängig davon, ob Modelllauf, Commit und Push bereits erfolgreich abgeschlossen sind. - `apply_commit()` führt Commit, Push und Deployment als einen nicht persistent phasierten Ablauf aus. Nach dem Requeue ist nicht erkennbar, welche Schritte bereits dauerhaft erfolgt sind. - Der Retry startet deshalb erneut das Modell und kann einen zweiten Commit für denselben Intent erzeugen. - Das unterbrochene `nixos-rebuild switch` hatte den Board-Dienst bereits gestoppt, erreichte dessen anschließenden Start aber nicht mehr. ## Umfang / Auftrag an den Builder ### Deployment entkoppeln - `nixos-rebuild switch` nicht mehr synchron im Prozessbaum von `agent-codex-builder.service` ausführen. - Einen separaten, eng begrenzten Deployment-Runner mit eigenem systemd-Lebenszyklus definieren. Er erhält einen persistierten Auftrag mit Commit-Hash, Host und Flake-Pfad und führt ausschließlich den freigegebenen `blunix`-Switch aus. - Der Deployment-Runner muss eine Aktivierung seiner eigenen deklarativen Umgebung überleben. Seine Unit darf während des Switch nicht als Teil des Builder-Neustarts beendet werden. - Builder und Board dürfen ein erfolgreich gestartetes Deployment nicht mit einem erfolgreich abgeschlossenen Deployment verwechseln. ### Persistente Zustandsmaschine - Den Ablauf pro stabiler Board-Job-ID atomar mindestens in folgende Zustände aufteilen: `building -> committed -> deploying -> deployed` beziehungsweise `build_failed` oder `deploy_failed`. - Commit-Hash und Push-Ergebnis unmittelbar nach ihrem Erfolg persistent speichern, bevor ein Deployment gestartet wird. - Bei Neustart anhand dieses Zustands fortsetzen: Ein bereits committed Job darf weder erneut das Modell ausführen noch einen weiteren Commit erzeugen. - Requeue im SIGTERM-Handler nur zulassen, solange nachweislich noch kein Commit für diesen Job gespeichert wurde. Nach `committed` darf höchstens der separate Deployment-Schritt erneut beziehungsweise kontrolliert fortgesetzt werden. - Einen Idempotency-Key aus Board-Job-ID und Intent-Slug verwenden und doppelte Queue-/Completion-Einträge sicher deduplizieren. ### Fehler- und Retry-Verhalten - Zwischen Build-, Push- und Deployment-Fehlern unterscheiden und den Status dem Board explizit melden. - Ein gepushter Commit bei fehlgeschlagener oder partieller Aktivierung darf nicht als `deployed` gelten. - Deployment-Retries begrenzen und mit Backoff versehen. Wiederholte Self-Termination, SIGTERM oder Aktivierungsfehler müssen in einem sichtbaren `deploy_failed`-Zustand enden statt den Modelllauf erneut zu starten. - Der Completion-Watcher muss doppelte Records sowie bereits verarbeitete Commit-Hashes ignorieren. - Nach einem fehlgeschlagenen Deployment muss das Board den Commit-Hash, den Deployment-Status und einen manuellen Recovery-Hinweis anzeigen. ### Betriebsstabilität - Sicherstellen, dass `board-agent-trigger.service` nach erfolgreicher Aktivierung läuft. - Wird eine Aktivierung unterbrochen, muss der Deployment-Runner den tatsächlichen Unit-Zustand prüfen und einen klaren Fehler melden. Kein stilles Zurücklassen eines deklarativ aktivierten, aber faktisch gestoppten Boards. - Die Lösung darf keine allgemein nutzbare Root-Shell oder zusätzliche Modellberechtigung schaffen. Das Modell erzeugt weiterhin nur die Änderung; der eng definierte Wrapper führt den Deployment-Auftrag aus. ## Tests - Unit-Test: SIGTERM vor dem Commit requeued genau einmal. - Unit-Test: SIGTERM nach gespeichertem Commit startet das Modell nicht erneut und erzeugt keinen zweiten Commit. - Unit-Test: Doppelte Queue- und Completion-Einträge werden anhand der Job-ID dedupliziert. - Unit-Test: Ein gepushter Commit mit fehlgeschlagenem Switch endet in `deploy_failed`, nicht in `deployed` oder einem neuen Builder-Lauf. - Integrationstest mit Fake-Deployment-Runner: Builder übergibt den Auftrag und kann beendet werden, ohne den Runner zu terminieren. - Integrationstest: Der Runner meldet nach erfolgreicher Aktivierung den Zustand des Board-Dienstes und den aktivierten Systempfad zurück. - Bestehende Board-, Builder-, Rollen- und Nix-Evaluierungstests müssen grün bleiben. ## Ausgeschlossen - Kein erneuter Modelllauf als allgemeine Recovery-Strategie nach bereits erfolgreichem Commit. - Kein Zurücksetzen oder Umschreiben der bereits gepushten Commits `62c9ce2` und `90ef0c8`. - Keine Abschaffung der bestehenden Sandbox- und Rollenbegrenzungen. - Kein automatisches Deployment auf andere Hosts als `blunix`. - Keine imperative Aktivierung allein durch das Vorhandensein dieses Intent-Dokuments. ## Erfolgskriterien - Ein Board-Job erzeugt auch bei einem Dienstneustart höchstens einen fachlichen Commit. - Der Builder wird nicht durch seinen eigenen Deployment-Kindprozess in eine Requeue-Schleife gebracht. - Ein unterbrochener Switch hinterlässt keinen fälschlich erfolgreichen Board-Status. - Nach erfolgreichem Switch antwortet das Board wieder; nach einem Fehler zeigt es einen eindeutigen, persistenten Recovery-Status. - Commit-/Push- und Deployment-Ergebnisse sind getrennt nachvollziehbar. - Der aktuelle Produktionsfehler lässt sich mit einem Test reproduzieren, der vor der Änderung fehlschlägt und danach besteht. ## Aktueller Recovery-Hinweis Zum Zeitpunkt der Diagnose ist `board-agent-trigger.service` auf `blunix` seit 12:51:31 `inactive (dead)`. Eine manuelle Wiederinbetriebnahme oder ein vollständiger erneuter `nixos-rebuild switch` ist eine separate betriebliche Aktion und nicht durch diesen Intent freigegeben.