#!/usr/bin/env bash
# -----------------------------------------------------------------------------
# NodePilot - Bash Root Agent
# -----------------------------------------------------------------------------
# Aenderungslog
# 2026-04-02 | GPT-5.3-Codex | MariaDB-Query-Feldfallback im Agent
# korrigiert: Persistenzpfad nutzt bei leeren Primaerfeldern robuste
# Sekundaerfelder (z. B. typ/sql), damit nutzbare SQL-Queries im
# query_chunk nicht faelschlich als diagnose/leer klassifiziert werden.
# Agent-Laufversion auf 2.2.27 angehoben.
# 2026-03-24 | GPT-5.3-Codex | Self-Restart-Haertung fuer den Updatepfad: restart_requested-Marker unter /var/lib/nodepilot/state wird im
# Hauptloop deterministisch erkannt, klar geloggt, entfernt und mit
# kontrolliertem Exit fuer systemd-Restart beendet. Zusaetzlich
# Startpfad-Laerm beseitigt (kein tr|head Broken-Pipe mehr) und
# Agent-Laufversion auf 2.2.25 angehoben.
# 2026-03-24 | GPT-5.3-Codex | Queue-/Replay-Haertung nach HY093-Folgefaellen
# erweitert: veraltete Outbox-/MariaDB-Queue-Dateien mit identischer
# agent_uuid aber abweichender lokaler node_id werden jetzt deterministisch
# als veraltet markiert und nicht erneut retried. Telemetrie nutzt dafuer
# kontrolliertes HTTP-Deadletter, MariaDB-Transport deadlettert mit
# veraltete_identitaet. Zusaetzlich wurden strukturierte Entscheidungslogs
# (datei/payload_typ/transfer_id/agent_uuid/payload_node_id/lokale_node_id/
# entscheidung) eingebaut und ein sicherer CLI-Bereinigungsmodus fuer stale
# Queue-Artefakte ergaenzt. Agent-Laufversion auf 2.2.24 angehoben.
# 2026-03-22 | GPT-5.2-Codex | MariaDB-Queryquellen fachlich gehaertet:
# information_schema.processlist liefert jetzt bewusst nur
# snapshot-belastbare Felder; nicht verfuegbare Detailmetriken (Rows,
# Locks, Tmp, Digest, Index-Hinweise) bleiben NULL statt Scheinwerten.
# Zusaetzlich wird die Quellenkennzeichnung im query_json vereinheitlicht
# und explizit als mittlere Qualitaetsstufe/dauer_herkunft geschaetzt
# markiert.
# 2026-03-22 | GPT-5.2-Codex | MariaDB-Transport-Protokoll repariert:
# Snapshot-ACK snapshot_id wird jetzt im Transfer-Marker persistiert und
# vor Query-Chunk-Versand verbindlich in die Payload uebernommen. Dadurch
# entfallen 400-Fehler "snapshot_id fehlt oder ist ungueltig" bei validen
# Folge-Chunks. Ausserdem ist der Digest-Fallback fuer Live-Queries
# standardmaessig deaktiviert; fallback fuer aktuelle Queries laeuft auf
# processlist/snapshot_basiert.
# 2026-03-22 | GPT-5.2-Codex | Befehls-Fehlerdiagnose erweitert:
# Poll-Ergebnispayload schreibt fehler_text jetzt explizit (u.a. bei
# Script-Aufloesungsfehlern), damit Web/API/Laufhistorie fehlende
# Scriptdateien klar und ohne reine stderr-Auswertung anzeigen.
# 2026-03-22 | GPT-5.2-Codex | Agent-Laufversion auf 2.2.17 angehoben.
# MariaDB-Live-Query-Kennzahlen werden jetzt pro Poll direkt aus der
# PROCESSLIST aggregiert und in den Snapshot-Metadaten priorisiert
# uebernommen, damit die acht aktuelle-Query-Felder nicht mehr vom
# deduplizierten Query-Transport abhaengen.
# 2026-03-22 | GPT-5.2-Codex | Agent-Laufversion auf 2.2.16 angehoben,
# damit die gehaertete MariaDB-Snapshot-Feldzuordnung serverseitig als
# eindeutiges Agent-/Server-Release ausrollbar bleibt.
# 2026-03-22 | GPT-5.2-Codex | Latenz-Quelle fachlich auf den regulaeren
# Agent-Standard-API-Request umgestellt: Es wird keine separate Curl-/Ping-
# Sondermessung mehr ausgefuehrt. Erfolgreiche Request-Laufzeiten werden
# direkt beim HTTP-POST gemessen, lokal persistiert und als latenz_ms/
# latenz_status in Poll-Metriken sowie als Top-Level-Feld fuer die
# Snapshot-Persistenz bereitgestellt.
# 2026-03-21 | GPT-5.2-Codex | MariaDB-Poll-Reduktion fachlich erweitert:
# reduzierte Snapshot-Payload enthaelt jetzt wieder belastbare Live-
# Kennzahlen (aktuelle Queries inkl. SQL-Typen, langlaufend, Sleep) sowie
# zentrale Statuszaehler (Questions/Slow/Bytes/Threads), damit serverseitig
# nur vollstaendige Snapshots persistiert werden und keine mageren
# Zwischenzeilen entstehen.
# 2026-03-20 | GPT-5.2-Codex | Agent-Laufversion auf 2.2.11 angehoben.
# MariaDB-Transport-Queue gehaertet: keine Identitaets-Umschreibung mehr,
# stattdessen harte Identitaetspruefung (node_id/agent_uuid), begrenzte
# Retry-/Warte-Strategie mit Sidecar-Metadaten, Snapshot-Transfer-Marker fuer
# Query-Chunk-Ketten und kontrollierte Deadletter-Verschiebung inkl.
# transfer_id/agent_uuid/node_id/datei/grund. befehlsergebnisse-JSON
# dateibasiert validiert/zusammengefuehrt, damit defekte Teilpayloads und
# jq-ARG_MAX-Probleme keine Poll-Kette mehr zerlegen.
# 2026-03-20 | GPT-5.2-Codex | Agent-Laufversion auf 2.2.10 angehoben, damit
# finale MariaDB-Replay-Entkopplung und ARG_MAX-Fix eindeutig ausrollbar sind.
# 2026-03-20 | GPT-5.2-Codex | MariaDB-Replay-/Versandpfad final entkoppelt:
# keine snapshot_id-Wartekopplung mehr, Query-Chunk-Validierung ohne
# snapshot-Pflichtfelder, erweiterte Versand-/Antwortlogs mit http_status,
# api_erfolg und persistiert; Poll-Block installierte_programme komplett
# dateibasiert zusammengefuehrt, um jq-ARG_MAX bei Grosspayloads zu vermeiden.
# 2026-03-20 | GPT-5.2-Codex | MariaDB-Transport fachlich entkoppelt:
# Query-Chunks werden nicht mehr durch snapshot_id blockiert, Versandreihenfolge
# Snapshot/Chunk ist frei, snapshot_id ist im Chunk optional und kein
# Pflichtfeld mehr. Queue-/Versand-/Antwort-Logs wurden auf transfer_id,
# chunk_index/chunk_count, payload_bytes, query_anzahl, node_id, agent_uuid,
# server_response_code und persistiert erweitert.
# 2026-03-20 | GPT-5.2-Codex | MariaDB-Transport Ende-zu-Ende repariert:
# Snapshot-ACK kann zur Korrelation genutzt werden, ist fuer Query-Chunks aber
# kein Versandblocker mehr. Query-Chunks bleiben ohne snapshot_id versendbar;
# Validierung bleibt auf Transport-/Strukturfelder und query_anzahl-Konsistenz
# fokussiert. Zusaetzliches Transport-Logging fuer Build-Telemetrie (input/
# output-Datei, Bytes, Tool, argv_frei) bleibt aktiv.
# 2026-03-20 | GPT-5.2-Codex | Grosspayload-Pfade ohne argv-Limits gehaertet:
# Poll-Payload-Blockzusammenfuehrung arbeitet dateibasiert statt jq --argjson
# mit grossen JSON-Strings (z.B. installierte_programme), und JSON-Extraktion
# aus HTTP-Responses nutzt stdin statt python3-argv fuer grosse Antworten.
# Agent-Laufversion auf 2.2.8 angehoben.
# 2026-03-20 | GPT-5.2-Codex | Query-Chunk-Builder im separaten MariaDB-
# Transport korrigiert: grosse Querydaten werden nicht mehr per argv an
# python3 uebergeben, sondern strikt dateibasiert (builder_input_datei ->
# builder_output_temp_datei) verarbeitet; Builder-Ausgabe wird vor Queue-
# Erzeugung auf Existenz, Groesse, JSON-Gueltigkeit und Pflichtfelder
# validiert. Leere/ungueltige Build-Artefakte erzeugen keine Queue-Datei und
# keine kuenstlichen Deadletter-Placeholder; zusaetzliches Builder-Logging
# (tool/input/output/bytes/build_ok/fehlergrund/queue_datei_final) ergaenzt.
# 2026-03-20 | GPT-5.2-Codex | MariaDB-Query-Transport grundlegend
# gehaertet: Telemetrie bleibt direkt, Query-Chunks werden ausschliesslich
# dateibasiert atomar erzeugt (Temp -> Validierung -> Move), vor Queue und
# vor Versand strikt validiert (nicht leer, JSON, Pflichtfelder, node_id>0,
# agent_uuid, payload_typ, Query-Konsistenz) und bei Fehlern in Deadletter
# verschoben; verhindert leere Chunk-Dateien und Versand mit payload_bytes=0.
# 2026-03-20 | GPT-5.2-Codex | MariaDB-Transport-Identitaet gehaertet:
# Queue-Payloads werden direkt vor Versand strukturell validiert und bei
# fehlender/ungueltiger node_id bzw. leerer agent_uuid aus aktueller INI
# nachgezogen; zusaetzlich erfolgt Metadaten-Logging (root_keys, node_id,
# agent_uuid, payload_typ, payload_bytes, query_anzahl), ohne SQL-Inhalte
# im Klartext zu protokollieren.
# 2026-03-20 | GPT-5.2-Codex | Telemetrie-Transport vollstaendig gehaertet:
# Poll-Payloads laufen nun ueber eine dateibasierte Telemetrie-Outbox mit
# Replay/Retry bis Erfolg (payload gebaut/gesendet/bestaetigt + queue-groesse),
# wodurch Query-Transportprobleme Telemetrie fachlich/technisch nicht blockieren.
# 2026-03-20 | GPT-5.2-Codex | MariaDB-Transport grundlegend entkoppelt:
# Poll liefert fuer MariaDB nur noch Metadaten, Snapshot-/Querydaten werden
# als robuste Queue-Dateien (Snapshot + Query-Chunks) erzeugt und separat per
# curl --data-binary @datei an einen dedizierten API-Endpunkt uebertragen.
# Inklusive Chunking, Retry/Replay, Locking, Payload-Bytes-Logging und
# persistenter Wiederholung ohne Poll-Gesamtabbruch.
# 2026-03-20 | GPT-5.2-Codex | Agent-Laufversion auf 2.2.5 angehoben, damit
# der neue dateibasierte MariaDB-Transportpfad als eigenstaendiges Release
# eindeutig ausrollbar ist.
# 2026-03-20 | GPT-5.2-Codex | MariaDB-JSON-Bau auf dateibasierte jq-
# Zufuehrung umgestellt (keine grossen --arg/--argjson-Argumente mehr),
# harte Feld-/Anzahl-Limits fuer sql_abfragen eingefuehrt, priorisierte
# Query-Uebernahme (fehlerhaft/langlaufend zuerst) ergaenzt und Payload-
# Diagnosefelder fuer kontrollierte Kuerzung/Deckelung implementiert.
# 2026-03-20 | GPT-5.2-Codex | Agent-Laufversion auf 2.2.3 angehoben,
# damit der P1-Fix fuer "jq: Die Argumentliste ist zu lang" eindeutig als
# neues Agent-Release ausrollbar ist.
# 2026-03-20 | GPT-5.2-Codex | MariaDB-JSON-Bau robuster gemacht:
# jq-Fehlerursache wird jetzt aus stderr mitgeschnitten und als konkreter
# Detailtext in fehler_text uebergeben, damit kein leeres "Detail=" mehr
# entsteht; Fallback-Fehlermeldung nutzt immer einen nicht-leeren
# Ursachentext.
# 2026-03-20 | GPT-5.2-Codex | Agent-Laufversion auf 2.2.1 angehoben,
# damit die gehaertete MariaDB-JSON-Resilienz mit Teilfehler-Isolation und
# konkreter Fehlerursachenmeldung konsistent als neues Agent-Release
# ausgerollt wird.
# 2026-03-20 | GPT-5.2-Codex | Agent-Laufversion auf 2.2.0 angehoben
# (ohne Debian-Revision), damit der Major/Minor-Versionssprung fuer das
# Agent-Release konsistent zur neuen apt-ausrollbaren Paketversion 2.2.0-1
# gemeldet wird.
# 2026-03-20 | GPT-5.2-Codex | MariaDB-Querylog erweitert um zusaetzlichen
# Fallback auf performance_schema.events_statements_summary_by_digest sowie
# transparentes Pipeline-Logging (roh/gefiltert/neu/verworfen mit Gruenden)
# inkl. konsistenter Metadatenbelegung fuer Quelle, Thread, Event/Digest und
# normalisierten SQL-Text.
# 2026-03-20 | GPT-5.2-Codex | Agent-Version auf 2.1.24 angehoben, da die
# Direktbefehl-/Resolver-Haertung als Agent-Aenderung eindeutig ausrollbar
# versioniert werden muss.
# 2026-03-20 | GPT-5.2-Codex | Agent-Version auf 2.1.23 angehoben, damit
# die erweiterte MariaDB-Query-Priorisierung/-Klassifikation als neues
# Agent-Release eindeutig ausrollbar ist.
# 2026-03-20 | GPT-5.2-Codex | Direktbefehl-/Resolver-Haertung ergaenzt:
# node_direkt_befehl kann fehlende lauf_befehl_text-Werte aus
# parameter_json.direkt_befehl_text (+ direkt_parameter_text) herstellen und
# Script-Aufloesungsfehler dokumentieren jetzt alle geprueften Kandidaten
# (exakter Name + optionale .sh-Variante) fuer praezise Betriebsdiagnosen.
# -----------------------------------------------------------------------------
# 2026-03-20 | GPT-5.2-Codex | MariaDB-Query-Erhebung deutlich
# vertieft: Prioritaetskette auf performance_schema -> processlist ->
# diagnose umgestellt, processlist-Klassifikation (Connect/Sleep/System)
# fachlich getrennt, Query-JSON um Qualitaets-/Fallback-Metadaten
# erweitert und SQL-Normalisierung inkl. Kommentar-/UUID-/IN-Listen-
# Bereinigung fuer stabile Gruppierung gehaertet.
# -----------------------------------------------------------------------------
# 2026-03-20 | GPT-5.2-Codex | MariaDB-Query-Erhebung fachlich repariert:
# echte Quellenpriorisierung mit expliziten Diagnosegruenden je Quelle,
# stabile SQL-Normalisierung (Literal-/Datum-/Whitespace-Bereinigung),
# SHA-256-Hex-Hash statt Base64-Fragmenten, Query-JSON mit Modus/Quelle/
# Kontext sowie robustere Prozesslisten-Diagnosen und deduplizierter
# Cursor-Fortschritt ohne Snapshot-Log-Falschbehauptungen.
# 2026-03-20 | GPT-5.2-Codex | Telemetrie-Netzwerkmessung fachlich
# gehaertet: robuste Statussignale fuer unlesbare Counter, Erstlauf,
# Interface-Wechsel, Counter-Reset und fehlerhafte Delta-Zeit; JSON-
# Fallbacks liefern jetzt auch bei jq-Fehlern weiterhin verwertbare
# Statusobjekte statt leerem Objekt.
# 2026-03-08 | GPT-5.2-Codex | Operative Weiterentwicklung fuer api-test:
# robuste URL-Normalisierung (server_url + api_basis_pfad + Endpunkte),
# massiv erweitertes strukturiertes Fehler-Diagnoselogging inkl. Header/
# Payload/Response/Retry/Curl-Kontext mit Secret-Maskierung und schneller
# Start-Datenfluss (Sofort-Poll in Stufen nach Register und beim Service-Start).
# 2026-03-08 | GPT-5.2-Codex | Datei neu erstellt. Vollstaendige Migration
# des Agent-Laufprozesses von PHP-CLI auf Bash inkl. Register/Poll,
# Retry/Backoff, Telemetrie, Befehlsausfuehrung, Ergebnisqueue und
# API-kompatibler Payloads fuer den operativen Root-Betrieb.
# 2026-03-08 | GPT-5.2-Codex | Operative Haertung fuer E2E-Betrieb gegen
# api-test umgesetzt: atomare Queue-Speicherung mit Sperrdatei,
# Pfadvalidierung fuer Script-Namen, robustere Timeout-/stderr-Dateien,
# strict umask und auswertbarer Update-Check inkl. Versionsmeldung.
# 2026-03-08 | GPT-5.2-Codex | IP-Erfassung fachlich korrigiert: lokale
# IPv4-/IPv6-Listen werden strikt auf global routbare/public Adressen
# gefiltert (Loopback, RFC1918, Link-Local, ULA, Docker-Bridge etc. raus).
# 2026-03-08 | GPT-5.2-Codex | Produktive APT-Onboarding-Ergaenzung:
# lokale Statusdatei inkl. Register/Poll-Zeitpunkten, neue CLI-Modi
# `status` und `pairing-code` fuer schnelle Sichtbarkeit der NP-Nummer
# sowie klare Betriebsdiagnose ohne lokale CLI-Freischaltung.
# 2026-03-10 | GPT-5.2-Codex | Remote-Konfig-Haertung ergaenzt: lokale
# API-Zielparameter bleiben immer fuehrend; verbotene Remote-Serverfelder
# werden erkannt, ignoriert und im Log explizit dokumentiert.
# 2026-03-10 | GPT-5.2-Codex | Befehlsausfuehrung gehaertet:
# Timeout wird als eigener Status gemeldet, stdout/stderr werden
# groessenbegrenzt und Timeouts auf sichere Grenzwerte normiert.
# 2026-03-10 | GPT-5.2-Codex | Poll-Stabilisierung umgesetzt:
# jq-Whitespace-Trim in Dienste-Pipeline auf jq-kompatiblen Regex umgestellt,
# JSON-Bau durch zentrale Validierungs-/Fallback-Helfer gehaertet (keine
# kaputten --argjson Inputs mehr), klare Fehlerlogs fuer Parserursachen
# ergaenzt und Single-Loop-Sperre per flock gegen Mehrfachinstanzen gebaut.
# 2026-03-10 | GPT-5.2-Codex | Operative Produktionshaertung erweitert:
# sporadische jq-Fehler im poll-normal werden pro Collector isoliert,
# Rohinputs bei Parserfehlern lokal persistiert, numerische Normalisierung
# zentralisiert und Schrottdaten (z. B. "●"/Einzelzeichen-Muell) in Dienste-,
# Docker- und Programmlisten bereits Agent-seitig verworfen.
# 2026-03-12 | GPT-5.2-Codex | HTTP-/Response-Haertung fuer Agent-Client:
# saubere Trennung von Transportfehlern, HTTP-Fehlern und Response-Format,
# gezieltes Retry nur bei technischen/5xx-Fehlern, robustere Update-Check-
# Validierung (leer/HTML/Nicht-JSON) sowie globaler Singleton-Lauf-Lock.
# 2026-03-12 | GPT-5.2-Codex | Lokale Outbox-Statusueberwachung ergaenzt:
# strukturierte Statusfelder fuer HTTP-Outbox/Deadletter inkl. aeltestem/
# juengstem Eintrag, Replay-Erfolg/-Fehler, Queue-Vorgang und kompakter
# Replay-Zusammenfassung im Log ohne zusaetzliche API-/Infra-Aenderungen.
# 2026-03-13 | GPT-5.2-Codex | Response-JSON-Haertung fuer poll/update-check
# erweitert: bei gemischten Text+JSON-Bodies wird ein valider JSON-Block
# extrahiert, kompakt normalisiert und nur als saubere JSON-Antwort in den
# Agent-Datenfluss uebernommen; verhindert jq-Fehler durch Diagnosereste.
# 2026-03-13 | GPT-5.2-Codex | STDOUT-Datenkanal gehaertet: nodepilot_log
# schreibt optionale Laufzeit-Logs jetzt auf STDERR statt STDOUT, damit
# Kommando-Substitutionen (z. B. poll/update-check Antwortverarbeitung)
# ausschliesslich den echten JSON-Body erhalten und keine Logzeilen mehr
# jq-Parserfehler oder Typ-Fehlklassifikationen ausloesen.
# 2026-03-13 | GPT-5.2-Codex | Update-Check-JSON-Pfad mit Poll vereinheitlicht:
# gemeinsame API-Antwort-Normalisierung extrahiert/kompaktiert JSON defensiv,
# protokolliert Typ-/Body-Ursachen sauber und verhindert, dass gueltige JSON-
# Antworten im update-check als Typ text verworfen werden.
# 2026-03-14 | GPT-5.2-Codex | Zentrale API-Host-/URL-Normalisierung fuer
# Defaults und Altbestaende eingefuehrt: Standardziel api.nodepilot.de,
# Erhalt aller *.nodepilot.de Hosts (inkl. api-test.nodepilot.de), Migration
# nicht-NodePilot Hosts auf Standardwert und konsistente INI-Synchronisierung.
# 2026-03-14 | GPT-5.2-Codex | Agent-Version auf 2.0.1 angehoben,
# damit Rollout/Update-Check die neue Zielversion konsistent melden.
# 2026-03-17 | GPT-5.2-Codex | Effektive Agent-Config aus Serverquelle inkl.
# lokaler Persistenz, zyklischem Pull (Default 600s), dynamischem Scheduler
# fuer automatische Befehle und robuster Script-Aufloesung implementiert.
# Fehlerserie "Script fehlt oder ist nicht ausfuehrbar" wird jetzt technisch
# sauber behandelt (Name/Pfad/.sh/Interpreter/Existenz/Executable).
# 2026-03-17 | GPT-5.2-Codex | Laufzeit-Haertung fuer Agent-Config-Metadaten:
# Temp-/Zieldatei-Pfade werden vor mktemp/mv robust vorbereitet, damit keine
# Fehler mehr bei fehlendem /run/nodepilot oder Meta-Verzeichnis auftreten.
# 2026-03-17 | GPT-5.2-Codex | Direktbefehl-Ausfuehrung E2E geschlossen:
# Poll-Laeufe mit lauf_befehl_text/lauf_parameter_text werden direkt per Shell
# ausgefuehrt, Ergebnis-Payload liefert stdout_text zusaetzlich zu stdout_json
# und JSON-Ausgaben werden nur bei gueltigem Objekt/Array als stdout_json gesetzt.
# 2026-03-17 | GPT-5.2-Codex | Agent-Versionsnummer auf 2.1.1 angehoben,
# damit Update-Check, Build und Laufzeitmeldung konsistent die neue
# Release-Version melden.
# 2026-03-18 | GPT-5.2-Codex | Register-Entscheidungslogging erweitert:
# Agent protokolliert nun die lokal gefundene Identitaetskette und den
# konkreten Grund fuer Register (Erstinstallation/Identitaetsverlust) oder
# Wiederverwendung (normaler Wiederanlauf ohne Neu-Registrierung).
# 2026-03-18 | GPT-5.2-Codex | Poll-Payload konsequent config-gesteuert
# gehaertet: Collector-Bloecke werden nur bei gueltiger Block-Config,
# Faelligkeit und optionaler Aenderung geliefert; lokale fachliche
# Default-Intervalle entfallen. Ergaenzt: robuster Collector-State
# (Faelligkeit/Hashes/Rate-Vorwerte), MariaDB-Raten via Delta+Zeitdifferenz,
# Netzwerk-kbit via Delta auf Hauptinterface, verbesserte Dienstefelder
# (active_state/sub_state/description) und Docker-Zusatzfelder.
# 2026-03-18 | GPT-5.2-Codex | Agent-Version auf 2.1.5 angehoben, damit
# der aktualisierte nodepilot_install_update-Standardbefehl (apt-get update
# + apt-get install -y nodepilot) im Rollout eindeutig versioniert ist.
# 2026-03-19 | GPT-5.2-Codex | Netzwerk-/Latenz-Messung fachlich korrigiert:
# primaeres Interface robust via Default-Route + Fallback auf erstes aktives
# nicht-virtuelles Interface (lo/docker*/br-*/veth*/virbr*/tun*/tap* raus),
# Delta-State mit Interface-Bindung/Reset-Logik gehaertet, net_in_kbit/
# net_out_kbit in kbit/s stabilisiert und latenz_ms per kurzem HTTP(S)-RTT
# gegen API-Basis gemessen. Fehler liefern null statt Fantasiewerten und
# blockieren Poll nicht; Logging nennt Interface/Initialisierung/Ursachen.
# 2026-03-19 | GPT-5.2-Codex | Agent-Version auf 2.1.6 angehoben fuer
# das Rollout der korrigierten Netzwerk-/Latenz-Telemetrie.
# 2026-03-19 | GPT-5.2-Codex | Update-Check fachlich gehaertet: HTTP 404
# mit AGENT_UNBEKANNT wird jetzt explizit als neutraler Fachstatus statt
# technischer Fehler klassifiziert (kein Retry, kein Crash, kein Restart-
# Trigger). Antwort wird gezielt ausgewertet und klar als Identitaetsfall
# geloggt. Zusaetzlich sendet der Agent machine_id/hardware_uuid im
# Update-Check, damit serverseitige Identitaetsauflosung mit Poll harmoniert.
# Agent-Version fuer diesen Fix auf 2.1.8 angehoben.
# 2026-03-19 | GPT-5.2-Codex | Agent-Version auf 2.1.9 angehoben, damit
# die Bereitstellung der neu aufgenommenen Globalbefehls-Skripte als
# eindeutiges Update ausgerollt werden kann.
# 2026-03-19 | GPT-5.2-Codex | Befehls-Skriptkonsistenz fuer kritische
# Globalbefehle gehaertet: Paket-/Installpfad liefert die Varianten mit
# und ohne .sh aus; Agent-Version fuer das Rollout auf 2.1.10 angehoben.
# 2026-03-19 | GPT-5.2-Codex | MariaDB-CLI-Erkennung und Fachstatus fuer
# MariaDB-Statuslaeufe gehaertet: CLI-Auswahl zentral auf `mariadb` vor
# `mysql` umgestellt (Register + Collector) und fachlicher Negativbefund
# (`verfuegbar!=true`) fuer MariaDB-Statusbefehle erzwingt jetzt Fehler-
# Exit statt Erfolg. Agent-Version fuer Rollout auf 2.1.11 angehoben.
# 2026-03-19 | GPT-5.2-Codex | Identitaets-Neuinitialisierung fuer geklonte
# Systeme ergänzt: wenn lokale technische Fingerprints (machine_id/
# hardware_uuid) von einer bestehenden INI-Identitaet mit gesetzter node_id
# abweichen, wird auth.node_id auf 0 gesetzt, Kopplungsdaten bereinigt,
# agent_uuid neu erzeugt und eine erzwungene Neu-Registrierung ausgeloest.
# Die lokale Fingerprint-Historie wird separat persistiert.
# 2026-03-19 | GPT-5.2-Codex | Agent-Version auf 2.1.13 angehoben, damit
# der serverseitig gestartete nodepilot_install_update-Lauf mit
# --only-upgrade und geordnetem Agent-Restart als eindeutiges Rollout
# verfuegbar ist.
# 2026-03-19 | GPT-5.2-Codex | Agent-Versionsermittlung nach Updates
# gehaertet: installierte Paketversion wird jetzt robust aus dpkg-query
# (Fallback VERSION-Datei/Default) gelesen, lokal synchronisiert und vor
# Register/Poll/Update-Check garantiert mitgesendet. Nach erfolgreichem
# nodepilot_install_update-Lauf wird sofort ein Version-Refresh plus
# beschleunigter Poll/Update-Check angestossen, damit die UI den neuen
# Stand ohne langen Verzug sieht.
# 2026-03-19 | GPT-5.2-Codex | Netzwerk-/DB-Telemetrie fachlich erweitert:
# Netzwerkrate wird jetzt ueber die Summe aller relevanten aktiven
# Interfaces (ohne lo/docker/br/veth/virbr/tun/tap) als Delta gebildet
# und bei Interfacewechsel/Counterreset sauber reinitialisiert. MariaDB-
# Block liefert strukturierte Statusvariablen (Grundzustand, Workload,
# InnoDB, Replikation/Galera) inkl. sauberer Degradierung statt Defaults.
# 2026-03-19 | GPT-5.2-Codex | Hotfix fuer den Agent-/Poll-Flow:
# 409-Identitaetskonflikte werden als eigener Fachstatus verarbeitet,
# lokal mit klarer Diagnose behandelt und die stale lokale node_id
# gezielt verworfen. Netzwerk-kbit-Berechnung zudem awk-robust
# ohne -v-Zuweisung gehaertet.
# 2026-03-19 | GPT-5.2-Codex | Script-Aufloesung gehaertet: Agent fuehrt
# Befehlsdateien nur noch aus, wenn sie existieren und ausfuehrbar sind;
# Fehlertexte fuer fehlende bzw. nicht-ausfuehrbare Skripte vereinheitlicht.
# Agent-Version fuer das Rollout auf 2.1.16 angehoben.
# 2026-03-19 | GPT-5.2-Codex | Agent-Version auf 2.1.17 angehoben, damit
# die erweiterten Statusfelder fuer Soll-/Updatepfad, Netz/Latenz und
# MariaDB-Snapshot strukturiert serverseitig verarbeitet ausrollbar sind.
# 2026-03-19 | GPT-5.2-Codex | P1-Fix fuer Re-Register-Self-Healing:
# fachliches AGENT_UNBEKANNT (404) bei poll/update-check wird jetzt als
# eindeutiger Identitaetsverlust klassifiziert, lokale Identitaet
# deterministisch invalidiert (node_id/agent_key/kopplungsdaten) und im
# naechsten Zyklus ein erzwungener Register-Flow gestartet. Kein Retry-
# Sturm, kein Re-Register bei Transport-/Timeoutfehlern.
# 2026-03-19 | GPT-5.2-Codex | Agent-Version auf 2.1.18 angehoben, damit
# der AGENT_UNBEKANNT-Recovery-Flow als eindeutiges Rollout verfuegbar ist.
# 2026-03-20 | GPT-5.2-Codex | Recovery-Signal erweitert: poll/update-check
# erkennen nun auch fehlercode=unknown_agent + aktion=register_required als
# deterministischen Re-Register-Fall (auch bei HTTP-200), invalidieren lokale
# Zuordnungsreste und verhindern damit Endlosschleifen mit stale Auth-Daten.
# Agent-Version fuer dieses Rollout auf 2.1.19 angehoben.
# 2026-03-20 | GPT-5.2-Codex | awk-Syntaxfehler in der Dienste-Erfassung
# an der Ursache behoben: lokale Variable `sub` wurde auf `sub_status`
# umbenannt, damit die awk-Funktion `sub()` nicht ueberschrieben wird und
# keine Parserfehler "syntax error at or near = ," mehr auftreten.
# 2026-03-20 | GPT-5.2-Codex | Poll-State-Persistenz im Echtlauf gehaertet:
# Delta-Vorwerte (u. a. Netzwerk/MariaDB) werden jetzt direkt nach dem
# Collector-Lauf persistiert, auch wenn der nachfolgende HTTP-Poll scheitert.
# Damit bleibt der Delta-State ueber Prozess-Neustarts/Einmallaeufe stabil
# erhalten und `netz_status=kein_messpunkt` wiederholt sich nicht mehr
# fachlich falsch bei identischer Interface-Signatur.
# 2026-03-20 | GPT-5.2-Codex | Netz-/Latenzpfad fachlich finalisiert:
# Interface-Auswahl priorisiert jetzt serverseitig gesehene Agent-IP
# (aus API-Antwort), danach aktive Route zum API-Ziel (ip route get),
# danach defensiven aktiven Fallback. Delta-State ist an Interface+lokale
# Source-IP gebunden und meldet Statusfaelle sauber (kein_messpunkt,
# kein_passendes_interface, counter_reset, fehler) ohne Zufallsinterface.
# 2026-03-22 | GPT-5.2-Codex | Agent-Laufversion auf 2.2.19 angehoben,
# damit Script-Auslieferungs-Haertung (Manifest + defensive Poll-Checks)
# und Paketversionskette konsistent denselben freigegebenen Stand melden.
# 2026-03-22 | GPT-5.2-Codex | MariaDB-Live-Felder im echten Agent-Lauf
# gehaertet und lokales Lauf-/Befehls-/MariaDB-Debugging erweitert:
# Live-Kennzahlen werden vor Transport und vor Poll-Reduktion defensiv
# nachgezogen, damit Digest-/Fallback-Pfade valide Processlist-Werte nicht
# mehr entwerten. Zusaetzlich persistiert der Agent strukturierte Artefakte
# unter /var/log/nodepilot/{agent,commands,mariadb} (letzte Command-Response,
# letzter MariaDB-Rohstand, letzter reduzierter Transportstand, lesbare
# Debuglogs mit Quelle/Modus/Fallback/Transportentscheidung).
# Agent-Laufversion auf 2.2.21 angehoben.
# 2026-03-22 | GPT-5.2-Codex | Agent-Laufversion auf 2.2.23 angehoben,
# damit der gehaertete nodepilot_install_update-Befehl (APT-Repo-
# Klassifikation, Vorher/Nachher-Pruefung und False-Positive-Schutz)
# konsistent mit Paketversion und Update-Check ausgerollt wird.
# 2026-03-27 | GPT-5.3-Codex | Agent-Laufversion auf 2.2.26 angehoben,
# damit Laufzeitmeldung, Paketversion und Neuinstallationsdefault wieder
# konsistent dieselbe aktuelle Agent-Version liefern.
# -----------------------------------------------------------------------------

set -euo pipefail
umask 027

NODEPILOT_AGENT_VERSION="2.2.27"
NODEPILOT_INI_PATH="${NODEPILOT_INI_PATH:-/etc/nodepilot/nodepilot.ini}"
NODEPILOT_QUEUE_DIR="/var/lib/nodepilot/queue"
NODEPILOT_HTTP_OUTBOX_DIR="${NODEPILOT_QUEUE_DIR}/http-outbox"
NODEPILOT_HTTP_DEADLETTER_DIR="${NODEPILOT_QUEUE_DIR}/http-deadletter"
NODEPILOT_MARIADB_QUEUE_DIR="${NODEPILOT_QUEUE_DIR}/mariadb-transport"
NODEPILOT_MARIADB_DEADLETTER_DIR="${NODEPILOT_MARIADB_QUEUE_DIR}/deadletter"
NODEPILOT_MARIADB_QUEUE_LOCK="${NODEPILOT_MARIADB_QUEUE_DIR}/.queue.lock"
NODEPILOT_CACHE_DIR="/var/cache/nodepilot"
NODEPILOT_MARIADB_TRANSFER_STATE_DIR="${NODEPILOT_CACHE_DIR}/mariadb-transport"
NODEPILOT_TMP_DIR="/run/nodepilot"
NODEPILOT_STATUS_DATEI="/var/lib/nodepilot/status.json"
NODEPILOT_PARSER_LOG_DIR="${NODEPILOT_CACHE_DIR}/parser-fehler"
NODEPILOT_AGENT_CONFIG_DATEI="/var/lib/nodepilot/agent-config-effektiv.json"
NODEPILOT_AGENT_CONFIG_META_DATEI="/var/lib/nodepilot/agent-config-meta.json"
NODEPILOT_AGENT_STATE_DATEI="/var/lib/nodepilot/agent-poll-state.json"
NODEPILOT_STATE_DIR="/var/lib/nodepilot/state"
NODEPILOT_RESTART_MARKER_DATEI="${NODEPILOT_STATE_DIR}/restart_requested"
NODEPILOT_LOG_BASIS_DIR="/var/log/nodepilot"
NODEPILOT_LOG_AGENT_DIR="${NODEPILOT_LOG_BASIS_DIR}/agent"
NODEPILOT_LOG_COMMANDS_DIR="${NODEPILOT_LOG_BASIS_DIR}/commands"
NODEPILOT_LOG_MARIADB_DIR="${NODEPILOT_LOG_BASIS_DIR}/mariadb"

NODEPILOT_DEFAULT_API_HOST="api.nodepilot.de"
NODEPILOT_DEFAULT_SERVER_URL="https://api.nodepilot.de"
NODEPILOT_DEFAULT_API_SCHEME="https"
NODEPILOT_DEFAULT_API_PORT="443"
NODEPILOT_DEFAULT_API_BASIS_PFAD="/api/v1"

declare -A NODEPILOT_SCHEDULER_JOB_NEXT_RUN
declare -A NODEPILOT_SCHEDULER_JOB_RUNNING_PID
declare -A NODEPILOT_SCHEDULER_JOB_RUNNING_SEIT
declare -A NODEPILOT_SCHEDULER_JOB_PARALLEL
declare -A NODEPILOT_SCHEDULER_JOB_TIMEOUT
declare -A NODEPILOT_SCHEDULER_JOB_DEFINITION
NODEPILOT_SCHEDULER_JOB_IDS=()
NODEPILOT_AGENT_CONFIG_JSON='{"version":"lokal-default","hash":"","pull_interval_sekunden":600,"blocks":{},"befehle":[]}'
NODEPILOT_AGENT_CONFIG_VERSION="lokal-default"
NODEPILOT_AGENT_CONFIG_HASH=""
NODEPILOT_AGENT_PULL_INTERVAL=600
NODEPILOT_AGENT_NEXT_PULL_UNIX=0
NODEPILOT_AGENT_STATE_JSON='{}'

nodepilot_parser_fehler_loggen()
{
	local kontext="$1"
	local rohinput="$2"
	install -d -m 0750 "${NODEPILOT_PARSER_LOG_DIR}"
	local stamp datei
	stamp="$(date +%Y%m%d-%H%M%S)"
	datei="${NODEPILOT_PARSER_LOG_DIR}/${stamp}-${kontext//[^a-zA-Z0-9._-]/_}.log"
	printf '%s\n' "${rohinput}" > "${datei}"
	chmod 0640 "${datei}" 2>/dev/null || true
	nodepilot_log "warn" "${kontext}: jq/JSON-Parserfehler; Rohinput gesichert unter ${datei}."
}

nodepilot_json_oder_fallback()
{
	local json_roh="$1"
	local fallback_json="$2"
	local kontext="$3"
	local json_kompakt
	json_kompakt="$(printf '%s' "${json_roh}" | jq -c . 2>/dev/null || true)"
	if [ -n "${json_kompakt}" ]; then
		echo "${json_kompakt}"
		return
	fi
	nodepilot_log "warn" "${kontext}: ungueltiges JSON erkannt; nutze Fallback ${fallback_json}."
	if [ -n "${json_roh}" ]; then
		nodepilot_parser_fehler_loggen "${kontext}" "${json_roh}"
	fi
	echo "${fallback_json}"
}

nodepilot_zahl_oder_fallback()
{
	local rohwert="$1"
	local fallback="$2"
	local kontext="$3"
	if echo "${rohwert}" | grep -Eq '^-?[0-9]+([.][0-9]+)?$'; then
		echo "${rohwert}"
		return
	fi
	nodepilot_log "warn" "${kontext}: ungueltiger Zahlenwert '${rohwert}'; nutze ${fallback}."
	echo "${fallback}"
}

nodepilot_zeit_ms_jetzt()
{
	local zeit_ms
	zeit_ms="$(date +%s%3N 2>/dev/null || true)"
	if ! [[ "${zeit_ms}" =~ ^[0-9]+$ ]]; then
		local zeit_sekunden
		zeit_sekunden="$(date +%s 2>/dev/null || echo "0")"
		zeit_ms="$((zeit_sekunden * 1000))"
	fi
	echo "${zeit_ms}"
}

nodepilot_standard_api_latenz_speichern()
{
	local latenz_ms="$1"
	local context="$2"
	local http_status="$3"
	local zeit_utc
	if ! [[ "${latenz_ms}" =~ ^[0-9]+$ ]]; then
		return 0
	fi
	zeit_utc="$(date -Iseconds)"
	nodepilot_status_schreiben "standard_api_latenz_ms" "${latenz_ms}"
	nodepilot_status_schreiben "standard_api_latenz_status" "ok"
	nodepilot_status_schreiben "standard_api_latenz_zeit" "${zeit_utc}"
	nodepilot_status_schreiben "standard_api_latenz_context" "${context}"
	nodepilot_status_schreiben "standard_api_latenz_http_status" "${http_status}"
}

nodepilot_single_loop_sperre_setzen()
{
	install -d -m 0750 "${NODEPILOT_TMP_DIR}"
	local lock_datei="${NODEPILOT_TMP_DIR}/agent-loop.lock"
	exec 8>"${lock_datei}"
	if ! flock -n 8; then
		nodepilot_log "warn" "Loop-Sperre bereits aktiv (${lock_datei}); zweiter Loop wird beendet."
		nodepilot_agent_laufereignis_loggen "loop_lock" "abbruch" "loop_sperre_aktiv datei=${lock_datei}"
		exit 0
	fi
}

nodepilot_agent_singleton_sperre_setzen()
{
	install -d -m 0750 "${NODEPILOT_TMP_DIR}"
	local lock_datei="${NODEPILOT_TMP_DIR}/agent-run.lock"
	exec 7>"${lock_datei}"
	if ! flock -n 7; then
		nodepilot_log "warn" "Agent-Lauf bereits aktiv (${lock_datei}); paralleler Lauf wird beendet."
		nodepilot_agent_laufereignis_loggen "agent_lock" "abbruch" "singleton_sperre_aktiv datei=${lock_datei}"
		exit 0
	fi
}

nodepilot_status_schreiben()
{
	local schluessel="$1"
	local wert="$2"
	install -d -m 0750 "$(dirname "${NODEPILOT_STATUS_DATEI}")"
	if [ ! -f "${NODEPILOT_STATUS_DATEI}" ]; then
		echo '{}' > "${NODEPILOT_STATUS_DATEI}"
	fi
	local tmp_datei
	tmp_datei="$(mktemp "${NODEPILOT_STATUS_DATEI}.XXXX")"
	jq --arg key "${schluessel}" --arg value "${wert}" '.[$key]=$value' "${NODEPILOT_STATUS_DATEI}" > "${tmp_datei}" 2>/dev/null || echo '{}' > "${tmp_datei}"
	mv "${tmp_datei}" "${NODEPILOT_STATUS_DATEI}"
	chmod 0640 "${NODEPILOT_STATUS_DATEI}"
}

nodepilot_status_lesen()
{
	local schluessel="$1"
	if [ ! -f "${NODEPILOT_STATUS_DATEI}" ]; then
		echo ""
		return
	fi
	jq -r --arg key "${schluessel}" '.[$key] // ""' "${NODEPILOT_STATUS_DATEI}" 2>/dev/null || echo ""
}

nodepilot_status_json_schreiben()
{
	local schluessel="$1"
	local wert_json="$2"
	install -d -m 0750 "$(dirname "${NODEPILOT_STATUS_DATEI}")"
	if [ ! -f "${NODEPILOT_STATUS_DATEI}" ]; then
		echo '{}' > "${NODEPILOT_STATUS_DATEI}"
	fi
	local tmp_datei
	tmp_datei="$(mktemp "${NODEPILOT_STATUS_DATEI}.XXXX")"
	jq --arg key "${schluessel}" --argjson value "${wert_json}" '.[$key]=$value' "${NODEPILOT_STATUS_DATEI}" > "${tmp_datei}" 2>/dev/null || echo '{}' > "${tmp_datei}"
	mv "${tmp_datei}" "${NODEPILOT_STATUS_DATEI}"
	chmod 0640 "${NODEPILOT_STATUS_DATEI}"
}

nodepilot_outbox_status_feld_setzen()
{
	local feldname="$1"
	local feldwert="$2"
	local bestehend neuer_status
	bestehend="$(jq -c '.http_outbox // {}' "${NODEPILOT_STATUS_DATEI}" 2>/dev/null || echo '{}')"
	neuer_status="$(echo "${bestehend}" | jq -c --arg key "${feldname}" --arg value "${feldwert}" '.[$key]=$value' 2>/dev/null || echo '{}')"
	nodepilot_status_json_schreiben "http_outbox" "${neuer_status}"
}

nodepilot_outbox_zeitpunkt_zu_iso()
{
	local zeitstempel_unix="$1"
	if [ -z "${zeitstempel_unix}" ] || ! [[ "${zeitstempel_unix}" =~ ^[0-9]+$ ]]; then
		echo ""
		return
	fi
	date -Iseconds -d "@${zeitstempel_unix}" 2>/dev/null || echo ""
}

nodepilot_outbox_letzter_deadletter_grund_ermitteln()
{
	local letzter_dateiname
	letzter_dateiname="$(find "${NODEPILOT_HTTP_DEADLETTER_DIR}" -maxdepth 1 -type f -name '*.json' -printf '%T@|%f\n' 2>/dev/null | sort -n | tail -n 1 | cut -d'|' -f2-)"
	if [ -z "${letzter_dateiname}" ]; then
		echo ""
		return
	fi
	if [[ "${letzter_dateiname}" =~ grund-([^_]+) ]]; then
		echo "${BASH_REMATCH[1]//-/ }"
		return
	fi
	echo "unbekannt"
}

nodepilot_outbox_status_ermitteln()
{
	install -d -m 0750 "${NODEPILOT_HTTP_OUTBOX_DIR}" "${NODEPILOT_HTTP_DEADLETTER_DIR}"
	local outbox_anzahl deadletter_anzahl aeltester_unix juengster_unix
	outbox_anzahl="$(find "${NODEPILOT_HTTP_OUTBOX_DIR}" -maxdepth 1 -type f -name '*.json' 2>/dev/null | wc -l | tr -d ' ')"
	deadletter_anzahl="$(find "${NODEPILOT_HTTP_DEADLETTER_DIR}" -maxdepth 1 -type f -name '*.json' 2>/dev/null | wc -l | tr -d ' ')"
	aeltester_unix="$(find "${NODEPILOT_HTTP_OUTBOX_DIR}" -maxdepth 1 -type f -name '*.json' -printf '%T@\n' 2>/dev/null | sed 's/[.].*$//' | sort -n | head -n 1)"
	juengster_unix="$(find "${NODEPILOT_HTTP_OUTBOX_DIR}" -maxdepth 1 -type f -name '*.json' -printf '%T@\n' 2>/dev/null | sed 's/[.].*$//' | sort -n | tail -n 1)"

	local bestehend letzter_replay_erfolg letzter_replay_fehler letzter_deadletter_grund letzter_queue_vorgang
	bestehend="$(jq -c '.http_outbox // {}' "${NODEPILOT_STATUS_DATEI}" 2>/dev/null || echo '{}')"
	letzter_replay_erfolg="$(echo "${bestehend}" | jq -r '.letzter_replay_erfolg // ""' 2>/dev/null || echo "")"
	letzter_replay_fehler="$(echo "${bestehend}" | jq -r '.letzter_replay_fehler // ""' 2>/dev/null || echo "")"
	letzter_deadletter_grund="$(echo "${bestehend}" | jq -r '.letzter_deadletter_grund // ""' 2>/dev/null || echo "")"
	letzter_queue_vorgang="$(echo "${bestehend}" | jq -r '.letzter_queue_vorgang // ""' 2>/dev/null || echo "")"

	if [ -z "${letzter_deadletter_grund}" ] && [ "${deadletter_anzahl}" -gt 0 ]; then
		letzter_deadletter_grund="$(nodepilot_outbox_letzter_deadletter_grund_ermitteln)"
	fi

	local outbox_status_json
	outbox_status_json="$(jq -n \
		--argjson outbox_anzahl "${outbox_anzahl:-0}" \
		--argjson deadletter_anzahl "${deadletter_anzahl:-0}" \
		--arg outbox_aeltester_eintrag "$(nodepilot_outbox_zeitpunkt_zu_iso "${aeltester_unix}")" \
		--arg outbox_juengster_eintrag "$(nodepilot_outbox_zeitpunkt_zu_iso "${juengster_unix}")" \
		--arg letzter_replay_erfolg "${letzter_replay_erfolg}" \
		--arg letzter_replay_fehler "${letzter_replay_fehler}" \
		--arg letzter_deadletter_grund "${letzter_deadletter_grund}" \
		--arg letzter_queue_vorgang "${letzter_queue_vorgang}" \
		'{outbox_anzahl:$outbox_anzahl,deadletter_anzahl:$deadletter_anzahl,outbox_aeltester_eintrag:$outbox_aeltester_eintrag,outbox_juengster_eintrag:$outbox_juengster_eintrag,letzter_replay_erfolg:$letzter_replay_erfolg,letzter_replay_fehler:$letzter_replay_fehler,letzter_deadletter_grund:$letzter_deadletter_grund,letzter_queue_vorgang:$letzter_queue_vorgang}')"
	nodepilot_status_json_schreiben "http_outbox" "${outbox_status_json}"
	echo "${outbox_status_json}"
}

nodepilot_outbox_replay_zusammenfassung_loggen()
{
	local context="$1"
	local outbox_status_json
	outbox_status_json="$(nodepilot_outbox_status_ermitteln)"
	local outbox_anzahl deadletter_anzahl letzter_replay_erfolg letzter_replay_fehler
	outbox_anzahl="$(echo "${outbox_status_json}" | jq -r '.outbox_anzahl // 0' 2>/dev/null || echo "0")"
	deadletter_anzahl="$(echo "${outbox_status_json}" | jq -r '.deadletter_anzahl // 0' 2>/dev/null || echo "0")"
	letzter_replay_erfolg="$(echo "${outbox_status_json}" | jq -r '.letzter_replay_erfolg // ""' 2>/dev/null || echo "")"
	letzter_replay_fehler="$(echo "${outbox_status_json}" | jq -r '.letzter_replay_fehler // ""' 2>/dev/null || echo "")"
	nodepilot_log "info" "Outbox-Status nach Replay (${context}): outbox=${outbox_anzahl}, deadletter=${deadletter_anzahl}, letzter_ok=${letzter_replay_erfolg:-kein_wert}, letzter_fehler=${letzter_replay_fehler:-kein_wert}."
}

nodepilot_telemetrie_queue_einreihen()
{
	local payload="$1"
	local poll_modus="$2"
	local queue_datei queue_tmp payload_bytes
	install -d -m 0750 "${NODEPILOT_HTTP_OUTBOX_DIR}"
	queue_datei="${NODEPILOT_HTTP_OUTBOX_DIR}/$(date -u +%Y%m%dT%H%M%SZ)_poll_${poll_modus}_$(cat /proc/sys/kernel/random/uuid 2>/dev/null || uuidgen).json"
	queue_tmp="$(mktemp "${NODEPILOT_HTTP_OUTBOX_DIR}/.telemetrie.XXXXXX")"
	printf '%s' "${payload}" > "${queue_tmp}"
	mv "${queue_tmp}" "${queue_datei}"
	chmod 0640 "${queue_datei}" 2>/dev/null || true
	payload_bytes="$(wc -c < "${queue_datei}" 2>/dev/null | awk '{print $1+0}')"
	nodepilot_log "warn" "telemetrie_transport retry/noetig queue_datei=${queue_datei##*/} payload_bytes=${payload_bytes}."
	nodepilot_outbox_status_feld_setzen "letzter_queue_vorgang" "telemetrie_enqueue:${poll_modus}"
	nodepilot_outbox_status_ermitteln >/dev/null
}

nodepilot_telemetrie_queue_verarbeiten()
{
	local endpoint="$1"
	install -d -m 0750 "${NODEPILOT_HTTP_OUTBOX_DIR}"
	local datei
	for datei in "${NODEPILOT_HTTP_OUTBOX_DIR}"/*.json; do
		[ -e "${datei}" ] || break
		local context payload_bytes antwort payload_typ transfer_id payload_node_id payload_agent_uuid lokale_node_id lokale_agent_uuid entscheidung
		context="telemetrie-replay-${datei##*/}"
		payload_bytes="$(wc -c < "${datei}" 2>/dev/null | awk '{print $1+0}')"
		payload_typ="$(jq -r '.payload_typ // "telemetrie_poll"' "${datei}" 2>/dev/null || echo "telemetrie_poll")"
		transfer_id="$(jq -r '.transfer_id // ""' "${datei}" 2>/dev/null || echo "")"
		payload_node_id="$(nodepilot_zahl_oder_fallback "$(jq -r '.node_id // 0' "${datei}" 2>/dev/null || echo "0")" "0" "telemetrie_transport/payload_node_id")"
		payload_agent_uuid="$(jq -r '.agent_uuid // ""' "${datei}" 2>/dev/null || echo "")"
		lokale_node_id="$(nodepilot_zahl_oder_fallback "$(nodepilot_ini_wert "auth" "node_id" "0")" "0" "telemetrie_transport/lokale_node_id")"
		lokale_agent_uuid="$(nodepilot_ini_wert "agent" "agent_uuid" "")"
		entscheidung="retry"

		if [ -n "${payload_agent_uuid}" ] && [ "${payload_agent_uuid}" = "${lokale_agent_uuid}" ] && [ "${payload_node_id}" -ne "${lokale_node_id}" ]; then
			entscheidung="veraltet_verworfen"
			nodepilot_telemetrie_queue_deadletter_verschieben "${datei}" "veraltete_identitaet" "${payload_typ}" "${transfer_id}" "${payload_agent_uuid}" "${payload_node_id}" "${lokale_node_id}" "${entscheidung}"
			nodepilot_outbox_status_feld_setzen "letzter_deadletter_grund" "veraltete_identitaet"
			nodepilot_outbox_status_feld_setzen "letzter_queue_vorgang" "telemetrie_veraltet_verworfen:${datei##*/}"
			nodepilot_outbox_status_ermitteln >/dev/null
			continue
		fi

		if [ "${payload_node_id}" -ne "${lokale_node_id}" ] || [ "${payload_agent_uuid}" != "${lokale_agent_uuid}" ]; then
			entscheidung="deadletter"
			nodepilot_telemetrie_queue_deadletter_verschieben "${datei}" "identitaet_unpassend" "${payload_typ}" "${transfer_id}" "${payload_agent_uuid}" "${payload_node_id}" "${lokale_node_id}" "${entscheidung}"
			nodepilot_outbox_status_feld_setzen "letzter_deadletter_grund" "identitaet_unpassend"
			nodepilot_outbox_status_feld_setzen "letzter_queue_vorgang" "telemetrie_deadletter_identitaet:${datei##*/}"
			nodepilot_outbox_status_ermitteln >/dev/null
			continue
		fi

		nodepilot_log "info" "telemetrie_transport payload gesendet queue_datei=${datei##*/} payload_typ=${payload_typ} transfer_id=${transfer_id:-leer} agent_uuid=${payload_agent_uuid:-leer} payload_node_id=${payload_node_id} lokale_node_id=${lokale_node_id} entscheidung=${entscheidung} payload_bytes=${payload_bytes}."
		set +e
		antwort="$(nodepilot_retry_post_datei "${endpoint}" "${datei}" "${context}")"
		local rc=$?
		set -e
		if [ "${rc}" -ne 0 ]; then
			nodepilot_log "warn" "telemetrie_transport retry/noetig queue_datei=${datei##*/} payload_typ=${payload_typ} transfer_id=${transfer_id:-leer} agent_uuid=${payload_agent_uuid:-leer} payload_node_id=${payload_node_id} lokale_node_id=${lokale_node_id} entscheidung=retry rc=${rc} payload_bytes=${payload_bytes}."
			nodepilot_outbox_status_feld_setzen "letzter_replay_fehler" "$(date -Iseconds)"
			nodepilot_outbox_status_feld_setzen "letzter_queue_vorgang" "telemetrie_replay_fehler:${datei##*/}:rc=${rc}"
			nodepilot_outbox_status_ermitteln >/dev/null
			return 1
		fi
		rm -f "${datei}" 2>/dev/null || true
		nodepilot_log "info" "telemetrie_transport payload bestaetigt queue_datei=${datei##*/} payload_typ=${payload_typ} transfer_id=${transfer_id:-leer} agent_uuid=${payload_agent_uuid:-leer} payload_node_id=${payload_node_id} lokale_node_id=${lokale_node_id} entscheidung=versendet payload_bytes=${payload_bytes}."
		nodepilot_outbox_status_feld_setzen "letzter_replay_erfolg" "$(date -Iseconds)"
		nodepilot_outbox_status_feld_setzen "letzter_queue_vorgang" "telemetrie_replay_ok:${datei##*/}"
		nodepilot_outbox_status_ermitteln >/dev/null
		if [ -n "${antwort}" ]; then
			nodepilot_status_schreiben "letzte_telemetrie_antwort" "${antwort}"
		fi
	done
	return 0
}

nodepilot_telemetrie_queue_deadletter_verschieben()
{
	local payload_datei="$1"
	local grund="$2"
	local payload_typ="$3"
	local transfer_id="$4"
	local agent_uuid="$5"
	local payload_node_id="$6"
	local lokale_node_id="$7"
	local entscheidung="$8"
	if [ ! -f "${payload_datei}" ]; then
		return
	fi
	install -d -m 0750 "${NODEPILOT_HTTP_DEADLETTER_DIR}"
	local ziel
	ziel="${NODEPILOT_HTTP_DEADLETTER_DIR}/$(date -u +%Y%m%dT%H%M%SZ)_grund-${grund}_${payload_datei##*/}"
	mv "${payload_datei}" "${ziel}" 2>/dev/null || rm -f "${payload_datei}" 2>/dev/null || true
	nodepilot_log "warn" "telemetrie_transport queue_entscheidung datei=${payload_datei} deadletter_datei=${ziel} payload_typ=${payload_typ:-telemetrie_poll} transfer_id=${transfer_id:-leer} agent_uuid=${agent_uuid:-leer} payload_node_id=${payload_node_id} lokale_node_id=${lokale_node_id} entscheidung=${entscheidung} grund=${grund}."
}

nodepilot_status_ausgeben()
{
	nodepilot_defaults_sicherstellen
	local kopplungs_id node_id server_url register_status poll_status letzter_register letzter_poll
	kopplungs_id="$(nodepilot_ini_wert "auth" "kopplungs_id" "")"
	node_id="$(nodepilot_ini_wert "auth" "node_id" "0")"
	server_url="$(nodepilot_request_url_bauen "/")"
	register_status="$(nodepilot_status_lesen "register_status")"
	poll_status="$(nodepilot_status_lesen "poll_status")"
	letzter_register="$(nodepilot_status_lesen "letzter_register")"
	letzter_poll="$(nodepilot_status_lesen "letzter_poll")"

	if [ -z "${register_status}" ]; then
		register_status="unbekannt"
	fi

	if [ -z "${poll_status}" ]; then
		poll_status="unbekannt"
	fi

	local kopplungsstatus="wartet_auf_freischaltung"
	if [ "${node_id}" -gt 0 ]; then
		kopplungsstatus="aktiviert"
	fi

	echo "NodePilot Agent Status"
	echo "----------------------"
	echo "NP-Nummer        : ${kopplungs_id:-nicht gesetzt}"
	echo "Kopplungsstatus  : ${kopplungsstatus}"
	echo "Node-ID          : ${node_id}"
	echo "API-Endpunkt     : ${server_url%/}"
	echo "Register-Status  : ${register_status}"
	echo "Letztes Register : ${letzter_register:-unbekannt}"
	echo "Poll-Status      : ${poll_status}"
	echo "Letzter Poll     : ${letzter_poll:-unbekannt}"
	local outbox_status_json
	outbox_status_json="$(nodepilot_outbox_status_ermitteln)"
	echo "Outbox-Zustand   : outbox=$(echo "${outbox_status_json}" | jq -r '.outbox_anzahl // 0'), deadletter=$(echo "${outbox_status_json}" | jq -r '.deadletter_anzahl // 0')"
}

nodepilot_pairing_code_ausgeben()
{
	nodepilot_defaults_sicherstellen
	local kopplungs_id
	kopplungs_id="$(nodepilot_ini_wert "auth" "kopplungs_id" "")"
	echo "${kopplungs_id}"
}

nodepilot_maskiere_geheimnis()
{
	local wert="$1"
	if [ -z "${wert}" ]; then
		echo ""
		return
	fi
	local laenge
	laenge="${#wert}"
	if [ "${laenge}" -le 6 ]; then
		echo "***"
		return
	fi
	echo "${wert:0:3}***${wert: -2}"
}

nodepilot_pfad_normalisieren()
{
	local pfad="$1"
	pfad="${pfad//$'\r'/}"
	pfad="${pfad//$'\n'/}"
	pfad="$(echo "${pfad}" | sed -E 's#/{2,}#/#g')"
	if [ -n "${pfad}" ] && [[ "${pfad}" != /* ]]; then
		pfad="/${pfad}"
	fi
	if [ "${pfad}" != "/" ]; then
		pfad="${pfad%/}"
	fi
	echo "${pfad}"
}

nodepilot_endpoint_kombinieren()
{
	local basis_pfad="$1"
	local endpoint_pfad="$2"
	basis_pfad="$(nodepilot_pfad_normalisieren "${basis_pfad}")"
	endpoint_pfad="$(nodepilot_pfad_normalisieren "${endpoint_pfad}")"
	if [ -z "${basis_pfad}" ] || [ "${basis_pfad}" = "/" ]; then
		echo "${endpoint_pfad}"
		return
	fi
	if [ -z "${endpoint_pfad}" ] || [ "${endpoint_pfad}" = "/" ]; then
		echo "${basis_pfad}"
		return
	fi
	if [ "${endpoint_pfad}" = "${basis_pfad}" ] || [[ "${endpoint_pfad}" = "${basis_pfad}/"* ]]; then
		echo "${endpoint_pfad}"
		return
	fi
	echo "$(nodepilot_pfad_normalisieren "${basis_pfad}/${endpoint_pfad#/}")"
}

nodepilot_host_aus_url_lesen()
{
	local wert="$1"
	wert="${wert//$'\r'/}"
	wert="${wert//$'\n'/}"
	wert="${wert#http://}"
	wert="${wert#https://}"
	wert="${wert%%/*}"
	wert="${wert%%:*}"
	echo "$(echo "${wert}" | tr '[:upper:]' '[:lower:]')"
}

nodepilot_host_ist_nodepilot_domain()
{
	local host
	host="$(echo "$1" | tr '[:upper:]' '[:lower:]')"
	if [ -z "${host}" ]; then
		return 1
	fi
	if [ "${host}" = "api-test.nodepilot.de" ]; then
		return 0
	fi
	if [[ "${host}" == *"nodepilot.de"* ]]; then
		return 0
	fi
	return 1
}

nodepilot_api_host_normalisieren()
{
	local host_raw="$1"
	local host
	host="$(nodepilot_host_aus_url_lesen "${host_raw}")"
	if [ -z "${host}" ]; then
		echo "${NODEPILOT_DEFAULT_API_HOST}"
		return
	fi
	if nodepilot_host_ist_nodepilot_domain "${host}"; then
		echo "${host}"
		return
	fi
	echo "${NODEPILOT_DEFAULT_API_HOST}"
}

nodepilot_server_url_normalisieren()
{
	local server_url_raw="$1"
	local host
	host="$(nodepilot_api_host_normalisieren "${server_url_raw}")"
	echo "${NODEPILOT_DEFAULT_API_SCHEME}://${host}"
}

nodepilot_server_konfiguration_normieren_und_migrieren()
{
	local server_url_raw api_host_raw api_basis_raw
	server_url_raw="$(nodepilot_ini_wert "server" "server_url" "")"
	api_host_raw="$(nodepilot_ini_wert "server" "api_host" "")"
	api_basis_raw="$(nodepilot_ini_wert "server" "api_basis_pfad" "${NODEPILOT_DEFAULT_API_BASIS_PFAD}")"

	local normalisierter_host normalisierte_server_url normalisiertes_basis
	if [ -n "${server_url_raw}" ]; then
		normalisierter_host="$(nodepilot_api_host_normalisieren "${server_url_raw}")"
	elif [ -n "${api_host_raw}" ]; then
		normalisierter_host="$(nodepilot_api_host_normalisieren "${api_host_raw}")"
	else
		normalisierter_host="${NODEPILOT_DEFAULT_API_HOST}"
	fi
	normalisierte_server_url="${NODEPILOT_DEFAULT_API_SCHEME}://${normalisierter_host}"
	normalisiertes_basis="$(nodepilot_pfad_normalisieren "${api_basis_raw}")"
	if [ -z "${normalisiertes_basis}" ]; then
		normalisiertes_basis="${NODEPILOT_DEFAULT_API_BASIS_PFAD}"
	fi

	nodepilot_ini_setzen "server" "server_url" "${normalisierte_server_url}"
	nodepilot_ini_setzen "server" "api_schema" "${NODEPILOT_DEFAULT_API_SCHEME}"
	nodepilot_ini_setzen "server" "api_host" "${normalisierter_host}"
	nodepilot_ini_setzen "server" "api_port" "${NODEPILOT_DEFAULT_API_PORT}"
	nodepilot_ini_setzen "server" "api_basis_pfad" "${normalisiertes_basis}"
}

nodepilot_request_url_bauen()
{
	local endpoint="$1"
	local server_url_raw api_host_raw api_basis
	server_url_raw="$(nodepilot_ini_wert "server" "server_url" "${NODEPILOT_DEFAULT_SERVER_URL}")"
	api_host_raw="$(nodepilot_ini_wert "server" "api_host" "${NODEPILOT_DEFAULT_API_HOST}")"
	api_basis="$(nodepilot_ini_wert "server" "api_basis_pfad" "${NODEPILOT_DEFAULT_API_BASIS_PFAD}")"

	local host server_ohne_slash endpoint_voll
	host="$(nodepilot_api_host_normalisieren "${server_url_raw}")"
	if [ -z "${host}" ]; then
		host="$(nodepilot_api_host_normalisieren "${api_host_raw}")"
	fi
	server_ohne_slash="${NODEPILOT_DEFAULT_API_SCHEME}://${host}"
	endpoint_voll="$(nodepilot_endpoint_kombinieren "${api_basis}" "${endpoint}")"
	echo "${server_ohne_slash}${endpoint_voll}"
}


nodepilot_diagnose_aktiv()
{
	if nodepilot_test_modus_aktiv; then
		return 0
	fi
	if [ "$(nodepilot_ini_wert "protokoll" "debug_payloads" "nein")" = "ja" ]; then
		return 0
	fi
	if [ "$(nodepilot_ini_wert "protokoll" "log_level" "info" | tr '[:upper:]' '[:lower:]')" = "debug" ]; then
		return 0
	fi
	return 1
}

nodepilot_json_felder_auflisten()
{
	local json_text="$1"
	echo "${json_text}" | jq -r 'if type=="object" then keys|join(",") else "-" end' 2>/dev/null || echo "-"
}

nodepilot_json_maskieren()
{
	local json_text="$1"
	echo "${json_text}" | jq -c '
		def mask: if (type == "string" and (length > 6)) then (.[:3] + "***" + .[-2:]) else "***" end;
		if type == "object" then
			with_entries(
				if (.key|ascii_downcase|test("password|passwort|token|secret|agent_key|api_key|authorization"))
				then .value = (.value|mask)
				else .
				end
			)
		else .
		end' 2>/dev/null || echo "{}"
}


nodepilot_body_kurz_auszug()
{
	local text="$1"
	if [ -z "${text}" ]; then
		echo "[leer]"
		return
	fi
	local normalisiert
	normalisiert="$(printf '%s' "${text}" | tr '\r\n' ' ' | sed -E 's/[[:space:]]+/ /g')"
	if [ "${#normalisiert}" -gt 220 ]; then
		normalisiert="${normalisiert:0:220}..."
	fi
	echo "${normalisiert}"
}

nodepilot_response_typ_ermitteln()
{
	local response_roh="$1"
	local response_header="$2"
	local body_trim content_type
	body_trim="$(printf '%s' "${response_roh}" | sed -E 's/^[[:space:]]+|[[:space:]]+$//g')"
	content_type="$(printf '%s' "${response_header}" | awk -F': *' 'tolower($1)=="content-type" {print tolower($2)}' | tr -d '\r' | tail -n 1)"

	if [ -z "${body_trim}" ]; then
		echo "leer"
		return
	fi

	if echo "${body_trim}" | jq -e . >/dev/null 2>&1; then
		echo "json"
		return
	fi

	if [[ "${content_type}" == *"text/html"* ]] || echo "${body_trim}" | grep -Eiq '^<!doctype[[:space:]]+html|^<html|<body'; then
		echo "html"
		return
	fi

	echo "text"
}

nodepilot_json_block_extrahieren()
{
	local response_roh="$1"
	local kandidat=""

	kandidat="$(printf '%s' "${response_roh}" | jq -c . 2>/dev/null || true)"
	if [ -n "${kandidat}" ]; then
		echo "${kandidat}"
		return
	fi

	if command -v python3 >/dev/null 2>&1; then
		kandidat="$(printf '%s' "${response_roh}" | python3 - <<'PYEOF' 2>/dev/null || true
import json
import sys

text = sys.stdin.read()
decoder = json.JSONDecoder()

for index, ch in enumerate(text):
	if ch not in "[{":
		continue
	try:
		obj, end_index = decoder.raw_decode(text[index:])
	except Exception:
		continue
	print(json.dumps(obj, separators=(",", ":"), ensure_ascii=False))
	sys.exit(0)

sys.exit(1)
PYEOF
)"
		if [ -n "${kandidat}" ]; then
			echo "${kandidat}"
			return
		fi
	fi

	echo ""
}

nodepilot_json_merge_block_aus_datei()
{
	local payload_json="$1"
	local block_name="$2"
	local block_datei="$3"
	local payload_tmp_datei merged_tmp_datei merged_json
	payload_tmp_datei="$(mktemp "${NODEPILOT_TMP_DIR}/payload_basis.XXXXXX.json")"
	merged_tmp_datei="$(mktemp "${NODEPILOT_TMP_DIR}/payload_merged.XXXXXX.json")"

	printf '%s' "${payload_json}" > "${payload_tmp_datei}"
	if ! jq -c --arg block "${block_name}" --slurpfile value "${block_datei}" '. + {($block):($value[0] // null)}' "${payload_tmp_datei}" > "${merged_tmp_datei}" 2>/dev/null; then
		rm -f "${payload_tmp_datei}" "${merged_tmp_datei}" 2>/dev/null || true
		echo "${payload_json}"
		return 1
	fi

	merged_json="$(cat "${merged_tmp_datei}" 2>/dev/null || echo "${payload_json}")"
	rm -f "${payload_tmp_datei}" "${merged_tmp_datei}" 2>/dev/null || true
	echo "${merged_json}"
	return 0
}

nodepilot_json_merge_block_dateibasiert()
{
	local payload_json="$1"
	local block_name="$2"
	local block_json="$3"
	local block_tmp_datei
	block_tmp_datei="$(mktemp "${NODEPILOT_TMP_DIR}/payload_block.XXXXXX.json")"
	printf '%s' "${block_json}" > "${block_tmp_datei}"
	nodepilot_json_merge_block_aus_datei "${payload_json}" "${block_name}" "${block_tmp_datei}"
	local rc=$?
	rm -f "${block_tmp_datei}" 2>/dev/null || true
	return ${rc}
}

nodepilot_api_antwort_json_normieren()
{
	local context="$1"
	local antwort_roh="$2"
	local antwort_json antwort_typ

	antwort_json="$(nodepilot_json_block_extrahieren "${antwort_roh}")"
	if [ -n "${antwort_json}" ]; then
		echo "${antwort_json}"
		return 0
	fi

	antwort_typ="$(nodepilot_response_typ_ermitteln "${antwort_roh}" "")"
	nodepilot_log "error" "${context}: API-Antwort konnte nicht als JSON normalisiert werden (Typ ${antwort_typ}). Auszug: $(nodepilot_body_kurz_auszug "${antwort_roh}")"
	return 1
}

nodepilot_request_diagnose_loggen()
{
	local stufe="$1"
	local context="$2"
	local methode="$3"
	local endpoint_pfad="$4"
	local server_url="$5"
	local api_basis_pfad="$6"
	local finale_url="$7"
	local request_header="$8"
	local payload_json="$9"
	local get_parameter="${10}"
	local post_parameter="${11}"
	local retry_nummer="${12}"
	local http_status="${13}"
	local response_header="${14}"
	local response_roh="${15}"
	local response_dekodiert="${16}"
	local technik_kontext="${17}"
	local curl_exit_code="${18}"
	local curl_stderr="${19}"
	local response_typ="${20:-unbekannt}"
	local json_parsebar="${21:-nein}"
	local body_auszug="${22:-[kein-auszug]}"

	local payload_ausgabe response_ausgabe
	payload_ausgabe="[live-modus-maskiert]"
	response_ausgabe="[live-modus-maskiert]"
	if nodepilot_diagnose_aktiv; then
		payload_ausgabe="$(nodepilot_json_maskieren "${payload_json}")"
		response_ausgabe="${response_roh}"
	fi

	nodepilot_log "${stufe}" "${context}: HTTP-Diagnose (Status ${http_status}, Retry ${retry_nummer}, Exit ${curl_exit_code}, Response-Typ ${response_typ}, JSON ${json_parsebar})."
	nodepilot_log "${stufe}" "${context}: Kontext='${context}' Methode='${methode}' Endpoint='${endpoint_pfad}' URL='${finale_url}'"
	nodepilot_log "${stufe}" "${context}: Body-Auszug='${body_auszug}'"

	if nodepilot_diagnose_aktiv; then
		nodepilot_log "${stufe}" "${context}: Diagnoseblock\n----------------------------------------\nKontext: ${context}\nHTTP-Methode: ${methode}\nEndpoint-Pfad: ${endpoint_pfad}\nserver_url: ${server_url}\napi_basis_pfad: ${api_basis_pfad}\nFinale-URL: ${finale_url}\nRequest-Header:\n${request_header}\nRequest-JSON (maskiert):\n${payload_ausgabe}\nPayload-Felder: $(nodepilot_json_felder_auflisten "${payload_json}")\nGET-Parameter: ${get_parameter}\nPOST-/JSON-Parameter: ${post_parameter}\nRetry-Nummer: ${retry_nummer}\nHTTP-Status: ${http_status}\nResponse-Typ: ${response_typ}\nJSON-parsebar: ${json_parsebar}\nBody-Auszug: ${body_auszug}\nResponse-Header:\n${response_header}\nResponse-Rohdaten:\n${response_ausgabe}\nResponse-dekodiert:\n${response_dekodiert}\nTechnischer Fehlerkontext: ${technik_kontext}\nCurl-Exit-Code: ${curl_exit_code}\nCurl-Stderr:\n${curl_stderr}\n----------------------------------------"
	fi
}

nodepilot_lockfile_pfad()
{
	local datei
	datei="$(nodepilot_ini_wert "lokal" "befehlsergebnisse_datei" "/var/lib/nodepilot/befehlsergebnisse.json")"
	echo "${datei}.lock"
}

nodepilot_log()
{
	local log_level="$1"
	local meldung="$2"
	local debug_text="${3:-}"
	local zeitstempel
	zeitstempel="$(date -Iseconds)"
	local log_datei
	log_datei="$(nodepilot_ini_wert "protokoll" "log_datei" "${NODEPILOT_LOG_AGENT_DIR}/agent.log")"
	mkdir -p "$(dirname "${log_datei}")"

	local effective_level
	effective_level="$(nodepilot_ini_wert "protokoll" "log_level" "info" | tr '[:upper:]' '[:lower:]')"
	local erlaubnis=0
	case "${effective_level}" in
		debug) erlaubnis=1 ;;
		info) [ "${log_level}" != "debug" ] && erlaubnis=1 ;;
		warn) [ "${log_level}" = "warn" ] || [ "${log_level}" = "error" ] && erlaubnis=1 ;;
		error) [ "${log_level}" = "error" ] && erlaubnis=1 ;;
		*) [ "${log_level}" != "debug" ] && erlaubnis=1 ;;
	esac

	if [ "${erlaubnis}" -eq 1 ]; then
		echo "${zeitstempel} [${log_level}] ${meldung}" >> "${log_datei}"
		if [ "$(nodepilot_ini_wert "protokoll" "stdout_logging" "ja")" = "ja" ]; then
			echo "${zeitstempel} [${log_level}] ${meldung}" >&2
		fi
	fi

	if [ -n "${debug_text}" ] && nodepilot_test_modus_aktiv; then
		echo "${zeitstempel} [debug] ${debug_text}" >> "${log_datei}"
	fi
}

nodepilot_dateiname_sicher()
{
	local wert="$1"
	echo "${wert}" | tr '[:upper:]' '[:lower:]' | tr -cs 'a-z0-9._-' '_'
}

nodepilot_json_datei_schreiben()
{
	local ziel_datei="$1"
	local json_inhalt="$2"
	local tmp_datei
	mkdir -p "$(dirname "${ziel_datei}")" 2>/dev/null || return 1
	tmp_datei="$(mktemp "${NODEPILOT_TMP_DIR}/log-json.XXXXXX" 2>/dev/null || true)"
	if [ -z "${tmp_datei}" ]; then
		return 1
	fi
	if ! printf '%s' "${json_inhalt}" | jq -c . > "${tmp_datei}" 2>/dev/null; then
		rm -f "${tmp_datei}" 2>/dev/null || true
		return 1
	fi
	if ! mv "${tmp_datei}" "${ziel_datei}" 2>/dev/null; then
		rm -f "${tmp_datei}" 2>/dev/null || true
		return 1
	fi
	chmod 0640 "${ziel_datei}" 2>/dev/null || true
	return 0
}

nodepilot_agent_laufereignis_loggen()
{
	local ereignis="$1"
	local status="$2"
	local detail="$3"
	local zeitstempel node_id agent_uuid payload_json
	zeitstempel="$(date -Iseconds)"
	node_id="$(nodepilot_ini_wert "auth" "node_id" "0")"
	agent_uuid="$(nodepilot_ini_wert "agent" "agent_uuid" "")"
	payload_json="$(jq -nc \
		--arg zeitstempel "${zeitstempel}" \
		--arg ereignis "${ereignis}" \
		--arg status "${status}" \
		--arg detail "${detail}" \
		--argjson node_id "$(nodepilot_zahl_oder_fallback "${node_id}" "0" "agent_lauf/node_id")" \
		--arg agent_uuid "${agent_uuid}" \
		'{zeitstempel:$zeitstempel,ereignis:$ereignis,status:$status,detail:$detail,node_id:$node_id,agent_uuid:$agent_uuid}')"
	printf '%s [%s] %s | status=%s | detail=%s | node_id=%s | agent_uuid=%s\n' "${zeitstempel}" "agent_lauf" "${ereignis}" "${status}" "${detail}" "${node_id}" "${agent_uuid}" >> "${NODEPILOT_LOG_AGENT_DIR}/lauf.log" 2>/dev/null || true
	nodepilot_json_datei_schreiben "${NODEPILOT_LOG_AGENT_DIR}/lauf.last.json" "${payload_json}" || true
}

nodepilot_befehl_letzte_response_schreiben()
{
	local befehl_key="$1"
	local payload_json="$2"
	local dateiname_sicher
	dateiname_sicher="$(nodepilot_dateiname_sicher "${befehl_key}")"
	if [ -z "${dateiname_sicher}" ]; then
		dateiname_sicher="unbekannt"
	fi
	nodepilot_json_datei_schreiben "${NODEPILOT_LOG_COMMANDS_DIR}/${dateiname_sicher}.last.json" "${payload_json}" || {
		nodepilot_log "warn" "command_logging: JSON-Artifact konnte nicht geschrieben werden (${NODEPILOT_LOG_COMMANDS_DIR}/${dateiname_sicher}.last.json)."
		return 1
	}
	printf '%s\n' "${payload_json}" | jq -r '
		[
			"zeitstempel=" + (.beendet // ""),
			"befehl_key=" + (.befehl_key // ""),
			"quelle=" + (.quelle // ""),
			"script_quelle=" + (.script_quelle // ""),
			"node_id=" + ((.node_id // 0)|tostring),
			"status=" + (.status // ""),
			"exit_code=" + ((.exit_code // 0)|tostring),
			"laufzeit_ms=" + ((.laufzeit_ms // 0)|tostring),
			"stdout_len=" + ((.stdout_text // "" | tostring | length)|tostring),
			"stderr_len=" + ((.stderr_text // "" | tostring | length)|tostring)
		] | join(" ")
	' >> "${NODEPILOT_LOG_COMMANDS_DIR}/${dateiname_sicher}.last.log" 2>/dev/null || true
	return 0
}

nodepilot_mariadb_debug_artefakt_schreiben()
{
	local dateiname="$1"
	local json_inhalt="$2"
	local debug_text="$3"
	nodepilot_json_datei_schreiben "${NODEPILOT_LOG_MARIADB_DIR}/${dateiname}" "${json_inhalt}" || {
		nodepilot_log "warn" "mariadb_logging: JSON-Artifact konnte nicht geschrieben werden (${NODEPILOT_LOG_MARIADB_DIR}/${dateiname})."
		return 1
	}
	printf '%s %s\n' "$(date -Iseconds)" "${debug_text}" >> "${NODEPILOT_LOG_MARIADB_DIR}/mariadb_debug.log" 2>/dev/null || true
	return 0
}

nodepilot_absichern_root()
{
	if [ "$(id -u)" -ne 0 ]; then
		echo "NodePilot-Agent muss als root laufen." >&2
		exit 10
	fi
}

nodepilot_ini_wert()
{
	local sektion="$1"
	local schluessel="$2"
	local standardwert="$3"
	awk -F'=' -v sec="${sektion}" -v key="${schluessel}" -v def="${standardwert}" '
		BEGIN{in_sec=0; out=""}
		$0 ~ /^\[/ {
			in_sec=($0=="[" sec "]")
			next
		}
		in_sec==1 {
			gsub(/^[ \t]+|[ \t]+$/, "", $1)
			if($1==key) {
				out=substr($0, index($0,"=")+1)
				gsub(/^[ \t]+|[ \t]+$/, "", out)
				print out
				exit
			}
		}
		END{if(out=="") print def}
	' "${NODEPILOT_INI_PATH}"
}

nodepilot_ini_setzen()
{
	local sektion="$1"
	local schluessel="$2"
	local wert="$3"
	python3 - <<PY
from pathlib import Path
p=Path("${NODEPILOT_INI_PATH}")
text=p.read_text(encoding="utf-8") if p.exists() else ""
lines=text.splitlines()
out=[]
in_sec=False
sec_found=False
key_done=False
for ln in lines:
	if ln.startswith("[") and ln.endswith("]"):
		if in_sec and not key_done:
			out.append("${schluessel}=${wert}")
			key_done=True
		in_sec = (ln == "[${sektion}]")
		if in_sec:
			sec_found=True
		out.append(ln)
		continue
	if in_sec and ln.split("=",1)[0].strip()=="${schluessel}":
		out.append("${schluessel}=${wert}")
		key_done=True
	else:
		out.append(ln)
if sec_found is False:
	out.extend(["", "[${sektion}]", "${schluessel}=${wert}"])
elif in_sec and not key_done:
	out.append("${schluessel}=${wert}")
p.write_text("\n".join(out)+"\n", encoding="utf-8")
PY
}


nodepilot_remote_server_parameter_guard_loggen()
{
	local antwort="$1"
	local verbotene_parameter
	verbotene_parameter="$(echo "${antwort}" | jq -c '{server_url,api_schema,api_host,api_port,api_basis_pfad} | to_entries | map(select((.value != null) and ((.value|tostring) != ""))) | map(.key)' 2>/dev/null || echo '[]')"
	if [ "${verbotene_parameter}" != "[]" ]; then
		nodepilot_log "warn" "Remote-Konfiguration liefert verbotene Serverparameter ${verbotene_parameter}; ignoriert. Lokale INI-Serverkonfiguration bleibt fuehrend."
	fi
}

nodepilot_defaults_sicherstellen()
{
	install -d -m 0750 /etc/nodepilot "${NODEPILOT_LOG_BASIS_DIR}" "${NODEPILOT_LOG_AGENT_DIR}" "${NODEPILOT_LOG_COMMANDS_DIR}" "${NODEPILOT_LOG_MARIADB_DIR}" /var/lib/nodepilot "${NODEPILOT_QUEUE_DIR}" "${NODEPILOT_HTTP_OUTBOX_DIR}" "${NODEPILOT_HTTP_DEADLETTER_DIR}" "${NODEPILOT_MARIADB_QUEUE_DIR}" "${NODEPILOT_CACHE_DIR}" "${NODEPILOT_MARIADB_TRANSFER_STATE_DIR}" "${NODEPILOT_TMP_DIR}" "$(dirname "${NODEPILOT_AGENT_CONFIG_DATEI}")"
	if [ ! -f "${NODEPILOT_INI_PATH}" ]; then
		cp /usr/lib/nodepilot/agent/config/nodepilot.ini.template "${NODEPILOT_INI_PATH}"
	fi
	if [ -x "/usr/lib/nodepilot/agent/scripts/bootstrap_ini.sh" ]; then
		/usr/lib/nodepilot/agent/scripts/bootstrap_ini.sh
	fi
	nodepilot_server_konfiguration_normieren_und_migrieren
	chmod 0640 "${NODEPILOT_INI_PATH}"
	local ergebnis_datei lock_datei
	ergebnis_datei="$(nodepilot_ini_wert "lokal" "befehlsergebnisse_datei" "/var/lib/nodepilot/befehlsergebnisse.json")"
	lock_datei="$(nodepilot_lockfile_pfad)"
	install -d -m 0750 "$(dirname "${ergebnis_datei}")" "$(dirname "${lock_datei}")"
	if [ ! -f "${ergebnis_datei}" ]; then
		echo '[]' > "${ergebnis_datei}"
	fi
	touch "${lock_datei}"
	chmod 0640 "${ergebnis_datei}" "${lock_datei}"
	if [ ! -f "${NODEPILOT_STATUS_DATEI}" ]; then
		echo '{}' > "${NODEPILOT_STATUS_DATEI}"
		chmod 0640 "${NODEPILOT_STATUS_DATEI}"
	fi
	if [ ! -f "${NODEPILOT_AGENT_CONFIG_DATEI}" ]; then
		echo "${NODEPILOT_AGENT_CONFIG_JSON}" > "${NODEPILOT_AGENT_CONFIG_DATEI}"
		chmod 0640 "${NODEPILOT_AGENT_CONFIG_DATEI}"
	fi
	if [ ! -f "${NODEPILOT_AGENT_CONFIG_META_DATEI}" ]; then
		echo "{}" > "${NODEPILOT_AGENT_CONFIG_META_DATEI}"
		chmod 0640 "${NODEPILOT_AGENT_CONFIG_META_DATEI}"
	fi
	nodepilot_outbox_status_ermitteln >/dev/null
}

nodepilot_agent_installierte_version_ermitteln()
{
	local version=""
	version="$(dpkg-query -W -f='${Version}' nodepilot-agent 2>/dev/null | head -n 1 | tr -d '\r' | tr -d '\n')"

	if [ -z "${version}" ] && [ -f "/usr/lib/nodepilot/agent/VERSION" ]; then
		version="$(awk 'NF>0 && $1 !~ /^#/ {print $1; exit}' /usr/lib/nodepilot/agent/VERSION 2>/dev/null | tr -d '\r' | tr -d '\n')"
	fi

	if [ -z "${version}" ]; then
		version="${NODEPILOT_AGENT_VERSION}"
	fi

	echo "${version}"
}

nodepilot_agent_version_synchronisieren()
{
	local installierte_version bisherige_version
	installierte_version="$(nodepilot_agent_installierte_version_ermitteln)"
	bisherige_version="$(nodepilot_ini_wert "agent" "agent_version" "")"

	if [ -z "${installierte_version}" ]; then
		installierte_version="${NODEPILOT_AGENT_VERSION}"
	fi

	if [ "${bisherige_version}" != "${installierte_version}" ]; then
		nodepilot_log "info" "Agent-Version lokal synchronisiert: alt=${bisherige_version:-leer} neu=${installierte_version}"
	fi

	nodepilot_ini_setzen "agent" "agent_version" "${installierte_version}"
	echo "${installierte_version}"
}

nodepilot_test_modus_aktiv()
{
	local hostname fqdn
	hostname="$(nodepilot_ini_wert "lokal" "hostname" "$(hostname -s 2>/dev/null || hostname)")"
	fqdn="$(nodepilot_ini_wert "lokal" "fqdn" "$(hostname -f 2>/dev/null || hostname)")"
	local muster='(test|staging|stage|dev|local|lab|sandbox|qa)'
	if echo "${hostname}.${fqdn}" | tr '[:upper:]' '[:lower:]' | grep -Eq "${muster}"; then
		return 0
	fi
	if [ "$(nodepilot_ini_wert "protokoll" "test_modus" "nein")" = "ja" ]; then
		return 0
	fi
	return 1
}

nodepilot_ip_format_gueltig()
{
	local ip_wert="$1"
	if [ -z "${ip_wert}" ]; then
		return 1
	fi
	if [[ "${ip_wert}" =~ ^([0-9]{1,3}\.){3}[0-9]{1,3}$ ]]; then
		return 0
	fi
	if [[ "${ip_wert}" =~ : ]]; then
		return 0
	fi
	return 1
}

nodepilot_server_sicht_agent_ip_uebernehmen()
{
	local antwort_json="$1"
	if [ -z "${antwort_json}" ]; then
		return 0
	fi

	local server_sicht_ip
	server_sicht_ip="$(printf '%s' "${antwort_json}" | jq -r '.server_sicht.agent_quell_ip // .daten.server_sicht.agent_quell_ip // empty' 2>/dev/null || true)"
	server_sicht_ip="$(printf '%s\n' "${server_sicht_ip}" | awk 'NR==1 {gsub(/\r/, "", $0); print; exit}')"
	if ! nodepilot_ip_format_gueltig "${server_sicht_ip}"; then
		return 0
	fi

	local bisher
	bisher="$(nodepilot_ini_wert "server" "letzte_agent_quell_ip" "")"
	if [ "${bisher}" != "${server_sicht_ip}" ]; then
		nodepilot_ini_setzen "server" "letzte_agent_quell_ip" "${server_sicht_ip}"
		nodepilot_log "info" "server_sicht: Agent-Quell-IP vom Server aktualisiert (${bisher:-leer} -> ${server_sicht_ip})."
	fi
}

nodepilot_json_post()
{
	local endpoint="$1"
	local payload="$2"
	local context="$3"
	local retry_nummer="${4:-0}"
	local timeout
	timeout="$(nodepilot_ini_wert "server" "http_timeout_sekunden" "15")"
	local verify_flag=""
	if [ "$(nodepilot_ini_wert "server" "verify_tls" "ja")" = "nein" ]; then
		verify_flag="-k"
	fi
	local agent_key request_header server_url api_basis url curl_stderr_datei response_header_datei response_file
	local request_start_ms request_ende_ms request_dauer_ms
	agent_key="$(nodepilot_ini_wert "auth" "agent_key" "")"
	server_url="$(nodepilot_ini_wert "server" "server_url" "")"
	api_basis="$(nodepilot_ini_wert "server" "api_basis_pfad" "/api/v1")"
	url="$(nodepilot_request_url_bauen "${endpoint}")"
	request_header="Content-Type: application/json"
	local header_key=()
	if [ -n "${agent_key}" ]; then
		header_key=(-H "X-NodePilot-Agent-Key: ${agent_key}")
		request_header="${request_header}
X-NodePilot-Agent-Key: $(nodepilot_maskiere_geheimnis "${agent_key}")"
	fi
	response_file="$(mktemp)"
	response_header_datei="$(mktemp)"
	curl_stderr_datei="$(mktemp)"
	request_start_ms="$(nodepilot_zeit_ms_jetzt)"

	set +e
	local status curl_exit_code
	status="$(curl -sS ${verify_flag} -D "${response_header_datei}" -o "${response_file}" -w "%{http_code}" -m "${timeout}" -H "Content-Type: application/json" "${header_key[@]}" -X POST "${url}" --data "${payload}" 2>"${curl_stderr_datei}")"
	curl_exit_code=$?
	set -e
	local response_roh response_header curl_stderr response_dekodiert response_typ json_parsebar body_auszug response_json_saeuberlich
	response_roh="$(cat "${response_file}" 2>/dev/null || true)"
	response_header="$(cat "${response_header_datei}" 2>/dev/null || true)"
	curl_stderr="$(cat "${curl_stderr_datei}" 2>/dev/null || true)"
	response_json_saeuberlich="$(nodepilot_json_block_extrahieren "${response_roh}")"
	if [ -n "${response_json_saeuberlich}" ]; then
		response_dekodiert="${response_json_saeuberlich}"
	else
		response_dekodiert="[ungueltiges_json]"
	fi
	response_typ="$(nodepilot_response_typ_ermitteln "${response_roh}" "${response_header}")"
	if [ -n "${response_json_saeuberlich}" ] && [ "${response_typ}" != "json" ]; then
		response_typ="json_gereinigt"
	fi
	body_auszug="$(nodepilot_body_kurz_auszug "${response_roh}")"
	json_parsebar="nein"
	if [ "${response_typ}" = "json" ] || [ "${response_typ}" = "json_gereinigt" ]; then
		json_parsebar="ja"
	fi
	if [ "${curl_exit_code}" -ne 0 ]; then
		status="000"
	fi

	if [ "${status}" = "000" ] || [ "${curl_exit_code}" -ne 0 ]; then
		nodepilot_request_diagnose_loggen "warn" "${context}" "POST" "${endpoint}" "${server_url}" "${api_basis}" "${url}" "${request_header}" "${payload}" "-" "$(nodepilot_json_felder_auflisten "${payload}")" "${retry_nummer}" "${status}" "${response_header}" "${response_roh}" "${response_dekodiert}" "transportfehler" "${curl_exit_code}" "${curl_stderr}" "${response_typ}" "${json_parsebar}" "${body_auszug}"
		nodepilot_log "warn" "${context}: Retry wegen Transportfehler (curl_exit=${curl_exit_code}, status=${status})."
		echo "${response_roh}"
		rm -f "${response_file}" "${response_header_datei}" "${curl_stderr_datei}"
		return 75
	fi

	if [ "${status}" -ge 500 ]; then
		nodepilot_request_diagnose_loggen "warn" "${context}" "POST" "${endpoint}" "${server_url}" "${api_basis}" "${url}" "${request_header}" "${payload}" "-" "$(nodepilot_json_felder_auflisten "${payload}")" "${retry_nummer}" "${status}" "${response_header}" "${response_roh}" "${response_dekodiert}" "http-5xx" "${curl_exit_code}" "${curl_stderr}" "${response_typ}" "${json_parsebar}" "${body_auszug}"
		nodepilot_log "warn" "${context}: Retry wegen HTTP-${status} (temporaer/Serverseite)."
		echo "${response_roh}"
		rm -f "${response_file}" "${response_header_datei}" "${curl_stderr_datei}"
		return 75
	fi

	if [ "${status}" -lt 200 ] || [ "${status}" -ge 300 ]; then
		local fehlercode_4xx
		fehlercode_4xx="$(printf '%s' "${response_json_saeuberlich:-}" | jq -r '.fehler.code // ""' 2>/dev/null || true)"
		if [[ "${context}" == poll-* || "${context}" == "update-check" ]] && [ "${status}" = "404" ] && [ "${fehlercode_4xx}" = "AGENT_UNBEKANNT" ]; then
			nodepilot_request_diagnose_loggen "warn" "${context}" "POST" "${endpoint}" "${server_url}" "${api_basis}" "${url}" "${request_header}" "${payload}" "-" "$(nodepilot_json_felder_auflisten "${payload}")" "${retry_nummer}" "${status}" "${response_header}" "${response_roh}" "${response_dekodiert}" "http-404-agent-unbekannt-recovery" "${curl_exit_code}" "${curl_stderr}" "${response_typ}" "${json_parsebar}" "${body_auszug}"
			nodepilot_log "warn" "${context}: AGENT_UNBEKANNT (HTTP-404) fachlich erkannt; lokale Identitaet wird fuer Re-Register invalidiert."
			rm -f "${response_file}" "${response_header_datei}" "${curl_stderr_datei}"
			return 44
		fi

		if [ "${status}" = "409" ] && [ "${fehlercode_4xx}" = "IDENTITAETSKONFLIKT" ]; then
			nodepilot_request_diagnose_loggen "warn" "${context}" "POST" "${endpoint}" "${server_url}" "${api_basis}" "${url}" "${request_header}" "${payload}" "-" "$(nodepilot_json_felder_auflisten "${payload}")" "${retry_nummer}" "${status}" "${response_header}" "${response_roh}" "${response_dekodiert}" "http-409-identitaetskonflikt" "${curl_exit_code}" "${curl_stderr}" "${response_typ}" "${json_parsebar}" "${body_auszug}"
			nodepilot_log "warn" "${context}: IDENTITAETSKONFLIKT (HTTP-409) fachlich erkannt; lokale Node-Zuordnung wird neu initialisiert."
			rm -f "${response_file}" "${response_header_datei}" "${curl_stderr_datei}"
			return 43
		fi

		nodepilot_request_diagnose_loggen "error" "${context}" "POST" "${endpoint}" "${server_url}" "${api_basis}" "${url}" "${request_header}" "${payload}" "-" "$(nodepilot_json_felder_auflisten "${payload}")" "${retry_nummer}" "${status}" "${response_header}" "${response_roh}" "${response_dekodiert}" "http-4xx-fachfehler" "${curl_exit_code}" "${curl_stderr}" "${response_typ}" "${json_parsebar}" "${body_auszug}"
		nodepilot_log "error" "${context}: Kein Retry bei HTTP-${status} (fachlicher HTTP-Fehler)."
		rm -f "${response_file}" "${response_header_datei}" "${curl_stderr_datei}"
		return 1
	fi

	if [ "${response_typ}" != "json" ] && [ "${response_typ}" != "json_gereinigt" ]; then
		nodepilot_request_diagnose_loggen "error" "${context}" "POST" "${endpoint}" "${server_url}" "${api_basis}" "${url}" "${request_header}" "${payload}" "-" "$(nodepilot_json_felder_auflisten "${payload}")" "${retry_nummer}" "${status}" "${response_header}" "${response_roh}" "${response_dekodiert}" "antwort-ist-kein-json" "${curl_exit_code}" "${curl_stderr}" "${response_typ}" "${json_parsebar}" "${body_auszug}"
		nodepilot_log "error" "${context}: Kein Retry, da HTTP-${status} aber Response nicht als JSON parsebar (Typ ${response_typ})."
		rm -f "${response_file}" "${response_header_datei}" "${curl_stderr_datei}"
		return 1
	fi

	if nodepilot_diagnose_aktiv; then
		nodepilot_log "debug" "${context}: HTTP ${status} erfolgreich fuer ${url}."
	fi

	nodepilot_server_sicht_agent_ip_uebernehmen "${response_json_saeuberlich:-${response_dekodiert}}"
	request_ende_ms="$(nodepilot_zeit_ms_jetzt)"
	request_dauer_ms="$((request_ende_ms - request_start_ms))"
	if [ "${request_dauer_ms}" -ge 0 ]; then
		nodepilot_standard_api_latenz_speichern "${request_dauer_ms}" "${context}" "${status}"
	fi
	if [ "${response_typ}" = "json_gereinigt" ]; then
		nodepilot_log "warn" "${context}: Antwort enthielt Zusatztext; JSON-Block wurde extrahiert und normalisiert uebernommen."
		echo "${response_json_saeuberlich}"
	else
		echo "${response_dekodiert}"
	fi
	rm -f "${response_file}" "${response_header_datei}" "${curl_stderr_datei}"
}

nodepilot_json_post_datei()
{
	local endpoint="$1"
	local payload_datei="$2"
	local context="$3"
	local retry_nummer="${4:-0}"
	local timeout
	timeout="$(nodepilot_ini_wert "server" "http_timeout_sekunden" "15")"
	local verify_flag=""

	if [ ! -f "${payload_datei}" ]; then
		nodepilot_log "error" "${context}: Payload-Datei fehlt: ${payload_datei}"
		return 1
	fi

	if [ "$(nodepilot_ini_wert "server" "verify_tls" "ja")" = "nein" ]; then
		verify_flag="-k"
	fi

	local payload payload_bytes
	payload="$(cat "${payload_datei}" 2>/dev/null || echo "")"
	payload_bytes="$(wc -c < "${payload_datei}" 2>/dev/null | awk '{print $1+0}')"

	local agent_key request_header server_url api_basis url curl_stderr_datei response_header_datei response_file
	local request_start_ms request_ende_ms request_dauer_ms
	agent_key="$(nodepilot_ini_wert "auth" "agent_key" "")"
	server_url="$(nodepilot_ini_wert "server" "server_url" "")"
	api_basis="$(nodepilot_ini_wert "server" "api_basis_pfad" "/api/v1")"
	url="$(nodepilot_request_url_bauen "${endpoint}")"
	request_header="Content-Type: application/json"
	local header_key=()
	if [ -n "${agent_key}" ]; then
		header_key=(-H "X-NodePilot-Agent-Key: ${agent_key}")
		request_header="${request_header}
X-NodePilot-Agent-Key: $(nodepilot_maskiere_geheimnis "${agent_key}")"
	fi
	response_file="$(mktemp)"
	response_header_datei="$(mktemp)"
	curl_stderr_datei="$(mktemp)"
	request_start_ms="$(nodepilot_zeit_ms_jetzt)"

	set +e
	local status curl_exit_code
	status="$(curl -sS ${verify_flag} -D "${response_header_datei}" -o "${response_file}" -w "%{http_code}" -m "${timeout}" -H "Content-Type: application/json" "${header_key[@]}" -X POST "${url}" --data-binary "@${payload_datei}" 2>"${curl_stderr_datei}")"
	curl_exit_code=$?
	set -e

	local response_roh response_header curl_stderr response_dekodiert response_typ json_parsebar body_auszug response_json_saeuberlich
	response_roh="$(cat "${response_file}" 2>/dev/null || true)"
	response_header="$(cat "${response_header_datei}" 2>/dev/null || true)"
	curl_stderr="$(cat "${curl_stderr_datei}" 2>/dev/null || true)"
	response_json_saeuberlich="$(nodepilot_json_block_extrahieren "${response_roh}")"
	if [ -n "${response_json_saeuberlich}" ]; then
		response_dekodiert="${response_json_saeuberlich}"
	else
		response_dekodiert="[ungueltiges_json]"
	fi
	response_typ="$(nodepilot_response_typ_ermitteln "${response_roh}" "${response_header}")"
	if [ -n "${response_json_saeuberlich}" ] && [ "${response_typ}" != "json" ]; then
		response_typ="json_gereinigt"
	fi
	body_auszug="$(nodepilot_body_kurz_auszug "${response_roh}")"
	json_parsebar="nein"
	if [ "${response_typ}" = "json" ] || [ "${response_typ}" = "json_gereinigt" ]; then
		json_parsebar="ja"
	fi
	if [ "${curl_exit_code}" -ne 0 ]; then
		status="000"
	fi

	if [ "${status}" = "000" ] || [ "${curl_exit_code}" -ne 0 ]; then
		nodepilot_request_diagnose_loggen "warn" "${context}" "POST" "${endpoint}" "${server_url}" "${api_basis}" "${url}" "${request_header}" "${payload}" "${payload_datei}" "$(nodepilot_json_felder_auflisten "${payload}")" "${retry_nummer}" "${status}" "${response_header}" "${response_roh}" "${response_dekodiert}" "transportfehler" "${curl_exit_code}" "${curl_stderr}" "${response_typ}" "${json_parsebar}" "${body_auszug}"
		nodepilot_log "warn" "${context}: Retry wegen Transportfehler (curl_exit=${curl_exit_code}, status=${status}, payload_bytes=${payload_bytes})."
		echo "${response_roh}"
		rm -f "${response_file}" "${response_header_datei}" "${curl_stderr_datei}"
		return 75
	fi

	if [ "${status}" -ge 500 ]; then
		nodepilot_request_diagnose_loggen "warn" "${context}" "POST" "${endpoint}" "${server_url}" "${api_basis}" "${url}" "${request_header}" "${payload}" "${payload_datei}" "$(nodepilot_json_felder_auflisten "${payload}")" "${retry_nummer}" "${status}" "${response_header}" "${response_roh}" "${response_dekodiert}" "http-5xx" "${curl_exit_code}" "${curl_stderr}" "${response_typ}" "${json_parsebar}" "${body_auszug}"
		nodepilot_log "warn" "${context}: Retry wegen HTTP-${status} (payload_bytes=${payload_bytes})."
		echo "${response_roh}"
		rm -f "${response_file}" "${response_header_datei}" "${curl_stderr_datei}"
		return 75
	fi

	if [ "${status}" -lt 200 ] || [ "${status}" -ge 300 ]; then
		nodepilot_request_diagnose_loggen "error" "${context}" "POST" "${endpoint}" "${server_url}" "${api_basis}" "${url}" "${request_header}" "${payload}" "${payload_datei}" "$(nodepilot_json_felder_auflisten "${payload}")" "${retry_nummer}" "${status}" "${response_header}" "${response_roh}" "${response_dekodiert}" "http-4xx-fachfehler" "${curl_exit_code}" "${curl_stderr}" "${response_typ}" "${json_parsebar}" "${body_auszug}"
		rm -f "${response_file}" "${response_header_datei}" "${curl_stderr_datei}"
		return 1
	fi

	if [ "${response_typ}" != "json" ] && [ "${response_typ}" != "json_gereinigt" ]; then
		nodepilot_request_diagnose_loggen "error" "${context}" "POST" "${endpoint}" "${server_url}" "${api_basis}" "${url}" "${request_header}" "${payload}" "${payload_datei}" "$(nodepilot_json_felder_auflisten "${payload}")" "${retry_nummer}" "${status}" "${response_header}" "${response_roh}" "${response_dekodiert}" "antwort-ist-kein-json" "${curl_exit_code}" "${curl_stderr}" "${response_typ}" "${json_parsebar}" "${body_auszug}"
		rm -f "${response_file}" "${response_header_datei}" "${curl_stderr_datei}"
		return 1
	fi

	nodepilot_server_sicht_agent_ip_uebernehmen "${response_json_saeuberlich:-${response_dekodiert}}"
	request_ende_ms="$(nodepilot_zeit_ms_jetzt)"
	request_dauer_ms="$((request_ende_ms - request_start_ms))"
	if [ "${request_dauer_ms}" -ge 0 ]; then
		nodepilot_standard_api_latenz_speichern "${request_dauer_ms}" "${context}" "${status}"
	fi
	if [ "${response_typ}" = "json_gereinigt" ]; then
		echo "${response_json_saeuberlich}"
	else
		echo "${response_dekodiert}"
	fi
	rm -f "${response_file}" "${response_header_datei}" "${curl_stderr_datei}"
}

nodepilot_retry_post_datei()
{
	local endpoint="$1"
	local payload_datei="$2"
	local context="$3"
	local retry_json max_retry
	retry_json="$(nodepilot_ini_wert "poll" "retry_intervalle_json" "[2,5,10]")"
	max_retry="$(nodepilot_ini_wert "poll" "max_retry_anzahl" "3")"
	local intervalle
	intervalle="$(echo "${retry_json}" | jq -r '.[]' 2>/dev/null || echo "2\n5\n10")"
	local versuch=0
	while true; do
		set +e
		local antwort
		antwort="$(nodepilot_json_post_datei "${endpoint}" "${payload_datei}" "${context}" "$((versuch+1))")"
		local rc=$?
		set -e
		if [ "${rc}" -eq 0 ]; then
			echo "${antwort}"
			return 0
		fi
		if [ "${rc}" -ne 75 ] || [ "${versuch}" -ge "${max_retry}" ]; then
			return "${rc}"
		fi
		local wait_sec
		wait_sec="$(echo "${intervalle}" | sed -n "$((versuch+1))p")"
		[ -z "${wait_sec}" ] && wait_sec=10
		nodepilot_log "warn" "${context}: Retry $((versuch+1)) in ${wait_sec}s."
		sleep "${wait_sec}"
		versuch=$((versuch+1))
	done
}

nodepilot_identitaetskonflikt_lokal_behandeln()
{
	local kontext="$1"
	local gemeldete_node_id
	gemeldete_node_id="$(nodepilot_ini_wert "auth" "node_id" "0")"

	nodepilot_ini_setzen "auth" "node_id" "0"
	nodepilot_status_schreiben "register_status" "identitaetskonflikt"
	nodepilot_status_schreiben "poll_status" "identitaetskonflikt"
	nodepilot_log "warn" "${kontext}: Lokale Node-Zuordnung wegen IDENTITAETSKONFLIKT verworfen (alte_node_id=${gemeldete_node_id}). Naechster Zyklus startet Register-Neuaufloesung."
}

nodepilot_lokale_identitaet_invalidieren()
{
	local kontext="$1"
	local grund="$2"
	local gemeldete_node_id gemeldete_agent_uuid
	gemeldete_node_id="$(nodepilot_ini_wert "auth" "node_id" "0")"
	gemeldete_agent_uuid="$(nodepilot_ini_wert "agent" "agent_uuid" "")"

	nodepilot_ini_setzen "auth" "node_id" "0"
	nodepilot_ini_setzen "auth" "agent_key" ""
	nodepilot_ini_setzen "auth" "kopplungs_id" ""
	nodepilot_ini_setzen "auth" "kopplungs_id_gueltig_bis" ""
	nodepilot_status_schreiben "register_status" "recovery_erforderlich"
	nodepilot_status_schreiben "poll_status" "agent_unbekannt"
	nodepilot_status_schreiben "identity_recovery_grund" "${grund}"
	nodepilot_status_schreiben "identity_recovery_zeit" "$(date -Iseconds)"
	nodepilot_log "warn" "${kontext}: Lokale Identitaet invalidiert (grund=${grund}, alte_node_id=${gemeldete_node_id}, agent_uuid=${gemeldete_agent_uuid:-leer}). Naechster Zyklus erzwingt Re-Register."
}

nodepilot_agent_unbekannt_lokal_behandeln()
{
	local kontext="$1"
	local fachdetail="$2"
	nodepilot_lokale_identitaet_invalidieren "${kontext}" "agent_unbekannt"
	nodepilot_log "warn" "${kontext}: Recovery-Entscheidung=register_erzwungen, fachdetail='${fachdetail}'."
}

nodepilot_retry_post()
{
	local endpoint="$1"
	local payload="$2"
	local context="$3"
	local retry_json max_retry
	retry_json="$(nodepilot_ini_wert "poll" "retry_intervalle_json" "[2,5,10]")"
	max_retry="$(nodepilot_ini_wert "poll" "max_retry_anzahl" "3")"
	local intervalle
	intervalle="$(echo "${retry_json}" | jq -r '.[]' 2>/dev/null || echo "2\n5\n10")"
	local versuch=0
	while true; do
		set +e
		local antwort
		antwort="$(nodepilot_json_post "${endpoint}" "${payload}" "${context}" "$((versuch+1))")"
		local rc=$?
		set -e
		if [ "${rc}" -eq 0 ]; then
			nodepilot_outbox_status_feld_setzen "letzter_replay_erfolg" "$(date -Iseconds)"
			nodepilot_outbox_status_feld_setzen "letzter_queue_vorgang" "replay_erfolg:${context}"
			nodepilot_outbox_replay_zusammenfassung_loggen "${context}"
			echo "${antwort}"
			return 0
		fi
		if [ "${rc}" -ne 75 ] || [ "${versuch}" -ge "${max_retry}" ]; then
			nodepilot_outbox_status_feld_setzen "letzter_replay_fehler" "$(date -Iseconds)"
			nodepilot_outbox_status_feld_setzen "letzter_queue_vorgang" "replay_fehler:${context}:rc=${rc}"
			if [ "${rc}" -ne 75 ]; then
				nodepilot_outbox_status_feld_setzen "letzter_deadletter_grund" "nicht_temporaerer_http_fehler"
			fi
			nodepilot_outbox_replay_zusammenfassung_loggen "${context}"
			if [ "${rc}" -ne 75 ]; then
				nodepilot_log "warn" "${context}: Retry beendet, da Fehler als nicht-temporär klassifiziert (rc=${rc})."
			else
				nodepilot_log "warn" "${context}: Retry-Limit erreicht (max=${max_retry})."
			fi
			return "${rc}"
		fi
		local wait_sec
		wait_sec="$(echo "${intervalle}" | sed -n "$((versuch+1))p")"
		[ -z "${wait_sec}" ] && wait_sec=10
		nodepilot_log "warn" "${context}: Retry $((versuch+1)) in ${wait_sec}s."
		sleep "${wait_sec}"
		versuch=$((versuch+1))
	done
}

nodepilot_identitaetsfingerprint_bilden()
{
	local machine_id="$1"
	local hardware_uuid="$2"
	echo "${machine_id}|${hardware_uuid}"
}

nodepilot_identitaet_klonkonflikt_pruefen_und_beheben()
{
	local bisherige_machine_id="$1"
	local bisherige_hardware_uuid="$2"
	local aktuelle_machine_id="$3"
	local aktuelle_hardware_uuid="$4"

	local aktuelle_node_id
	aktuelle_node_id="$(nodepilot_ini_wert "auth" "node_id" "0")"

	if [ "${aktuelle_node_id}" -le 0 ]; then
		return 0
	fi

	local machine_konflikt="nein"
	local hardware_konflikt="nein"

	if [ -n "${bisherige_machine_id}" ] && [ -n "${aktuelle_machine_id}" ] && [ "${bisherige_machine_id}" != "${aktuelle_machine_id}" ]; then
		machine_konflikt="ja"
	fi

	if [ -n "${bisherige_hardware_uuid}" ] && [ -n "${aktuelle_hardware_uuid}" ] && [ "${bisherige_hardware_uuid}" != "${aktuelle_hardware_uuid}" ]; then
		hardware_konflikt="ja"
	fi

	if [ "${machine_konflikt}" != "ja" ] && [ "${hardware_konflikt}" != "ja" ]; then
		return 0
	fi

	local alte_agent_uuid neue_agent_uuid
	alte_agent_uuid="$(nodepilot_ini_wert "agent" "agent_uuid" "")"
	neue_agent_uuid="$(cat /proc/sys/kernel/random/uuid 2>/dev/null || uuidgen)"

	nodepilot_ini_setzen "auth" "node_id" "0"
	nodepilot_ini_setzen "auth" "kopplungs_id" ""
	nodepilot_ini_setzen "auth" "kopplungs_id_gueltig_bis" ""
	nodepilot_ini_setzen "agent" "agent_uuid" "${neue_agent_uuid}"

	nodepilot_log "warn" "Identitaets-Neuinitialisierung: offenkundig geklonte oder stale INI erkannt. Alte node_id wurde verworfen und Neu-Registrierung wird erzwungen. alt_agent_uuid=${alte_agent_uuid:-leer} neu_agent_uuid=${neue_agent_uuid} alt_machine_id=${bisherige_machine_id:-leer} neu_machine_id=${aktuelle_machine_id:-leer} alt_hardware_uuid=${bisherige_hardware_uuid:-leer} neu_hardware_uuid=${aktuelle_hardware_uuid:-leer}"
}

nodepilot_lokaldaten_ermitteln()
{
	local agent_uuid machine_id hardware_uuid host_short fqdn
	local bisherige_machine_id bisherige_hardware_uuid
	agent_uuid="$(nodepilot_ini_wert "agent" "agent_uuid" "")"
	if [ -z "${agent_uuid}" ]; then
		agent_uuid="$(cat /proc/sys/kernel/random/uuid 2>/dev/null || uuidgen)"
		nodepilot_ini_setzen "agent" "agent_uuid" "${agent_uuid}"
	fi
	bisherige_machine_id="$(nodepilot_ini_wert "lokal" "machine_id" "")"
	bisherige_hardware_uuid="$(nodepilot_ini_wert "lokal" "hardware_uuid" "")"
	machine_id="$(cat /etc/machine-id 2>/dev/null || echo "")"
	hardware_uuid="$(cat /sys/class/dmi/id/product_uuid 2>/dev/null || echo "")"
	nodepilot_identitaet_klonkonflikt_pruefen_und_beheben "${bisherige_machine_id}" "${bisherige_hardware_uuid}" "${machine_id}" "${hardware_uuid}"
	host_short="$(hostname -s 2>/dev/null || hostname)"
	fqdn="$(hostname -f 2>/dev/null || hostname)"
	nodepilot_ini_setzen "lokal" "machine_id" "${machine_id}"
	nodepilot_ini_setzen "lokal" "hardware_uuid" "${hardware_uuid}"
	nodepilot_ini_setzen "lokal" "identitaet_fingerprint" "$(nodepilot_identitaetsfingerprint_bilden "${machine_id}" "${hardware_uuid}")"
	nodepilot_ini_setzen "lokal" "hostname" "${host_short}"
	nodepilot_ini_setzen "lokal" "fqdn" "${fqdn}"
}

nodepilot_ipv4_global_public()
{
	local ip_wert="$1"

	if ! [[ "${ip_wert}" =~ ^([0-9]{1,3}\.){3}[0-9]{1,3}$ ]]; then
		return 1
	fi

	if [[ "${ip_wert}" =~ ^127\. ]]; then
		return 1
	fi

	if [[ "${ip_wert}" =~ ^10\. ]]; then
		return 1
	fi

	if [[ "${ip_wert}" =~ ^192\.168\. ]]; then
		return 1
	fi

	if [[ "${ip_wert}" =~ ^169\.254\. ]]; then
		return 1
	fi

	local o1 o2
	o1="$(echo "${ip_wert}" | awk -F'.' '{print $1}')"
	o2="$(echo "${ip_wert}" | awk -F'.' '{print $2}')"

	if [ "${o1}" -eq 172 ] && [ "${o2}" -ge 16 ] && [ "${o2}" -le 31 ]; then
		return 1
	fi

	return 0
}

nodepilot_ipv6_global_public()
{
	local ip_wert="$1"
	local ip_klein
	ip_klein="$(echo "${ip_wert}" | tr '[:upper:]' '[:lower:]')"

	if [ -z "${ip_klein}" ]; then
		return 1
	fi

	if [ "${ip_klein}" = "::1" ]; then
		return 1
	fi

	if [[ "${ip_klein}" =~ ^fe[89ab][0-9a-f]: ]]; then
		return 1
	fi

	if [[ "${ip_klein}" =~ ^f[cdef][0-9a-f]: ]]; then
		return 1
	fi

	if [[ "${ip_klein}" =~ ^:: ]]; then
		return 1
	fi

	if command -v python3 >/dev/null 2>&1; then
		if python3 - "${ip_wert}" <<'PYEOF' >/dev/null 2>&1
import ipaddress
import sys
try:
	ip = ipaddress.ip_address(sys.argv[1])
except Exception:
	sys.exit(1)
sys.exit(0 if ip.is_global else 1)
PYEOF
		then
			return 0
		fi
		return 1
	fi

	return 0
}

nodepilot_ip_liste_json()
{
	local family="$1"
	local ip_liste=""
	local ip_wert=""
	local gesammelt=""

	if [ "${family}" = "4" ]; then
		ip_liste="$(ip -4 -o addr show scope global 2>/dev/null | awk '{print $4}' | cut -d'/' -f1)"
		while IFS= read -r ip_wert; do
			if [ -z "${ip_wert}" ]; then
				continue
			fi

			if nodepilot_ipv4_global_public "${ip_wert}"; then
				gesammelt="${gesammelt}${ip_wert}"$'\n'
			fi
		done <<< "${ip_liste}"
	else
		ip_liste="$(ip -6 -o addr show scope global 2>/dev/null | awk '{print $4}' | cut -d'/' -f1)"
		while IFS= read -r ip_wert; do
			if [ -z "${ip_wert}" ]; then
				continue
			fi

			if nodepilot_ipv6_global_public "${ip_wert}"; then
				gesammelt="${gesammelt}${ip_wert}"$'\n'
			fi
		done <<< "${ip_liste}"
	fi

	echo "${gesammelt}" | jq -R -s 'split("\n")|map(select(length>0))|unique'
}

nodepilot_netzwerk_kbit_json()
{
	local interface_json
	interface_json="$(nodepilot_netzwerk_relevantes_interface_json_ermitteln)"
	local iface local_ip quelle rx tx
	iface="$(echo "${interface_json}" | jq -r '.interface // ""' 2>/dev/null || echo "")"
	local_ip="$(echo "${interface_json}" | jq -r '.lokale_ip // ""' 2>/dev/null || echo "")"
	quelle="$(echo "${interface_json}" | jq -r '.quelle // "unbekannt"' 2>/dev/null || echo "unbekannt")"
	rx="$(echo "${interface_json}" | jq -r '.rx_bytes // ""' 2>/dev/null || echo "")"
	tx="$(echo "${interface_json}" | jq -r '.tx_bytes // ""' 2>/dev/null || echo "")"
	if [ -z "${iface}" ]; then
		local netz_status_fallback="kein_passendes_interface"
		if [ "${NODEPILOT_NETZWERK_INTERFACE_SCAN_STATUS:-ok}" = "counter_unlesbar" ]; then
			netz_status_fallback="fehler"
		fi
		nodepilot_log "warn" "netzwerk_kbit: Kein belastbares Interface ermittelbar (status=${NODEPILOT_NETZWERK_INTERFACE_SCAN_STATUS:-ok})."
		jq -n --arg status "${netz_status_fallback}" '{net_in_kbit:null,net_out_kbit:null,netz_status:$status}' 2>/dev/null || echo '{"net_in_kbit":null,"net_out_kbit":null,"netz_status":"fehler"}'
		return
	fi

	local signatur jetzt
	signatur="${iface}|${local_ip}"
	jetzt="$(date +%s)"
	if ! [[ "${rx}" =~ ^[0-9]+$ ]] || ! [[ "${tx}" =~ ^[0-9]+$ ]]; then
		nodepilot_log "warn" "netzwerk_kbit: RX/TX fuer ${iface} nicht lesbar; setze Werte auf null."
		jq -n '{net_in_kbit:null,net_out_kbit:null,netz_status:"fehler"}' 2>/dev/null || echo '{"net_in_kbit":null,"net_out_kbit":null,"netz_status":"fehler"}'
		return
	fi

	local prev_signatur prev_rx prev_tx prev_time
	prev_signatur="$(nodepilot_agent_state_wert_lesen '.rate_vorwerte.netz.interface_signatur')"
	prev_rx="$(nodepilot_agent_state_wert_lesen '.rate_vorwerte.netz.rx_bytes')"
	prev_tx="$(nodepilot_agent_state_wert_lesen '.rate_vorwerte.netz.tx_bytes')"
	prev_time="$(nodepilot_agent_state_wert_lesen '.rate_vorwerte.netz.zeit_unix')"

	NODEPILOT_AGENT_STATE_JSON="$(echo "${NODEPILOT_AGENT_STATE_JSON}" | jq -c --arg signatur "${signatur}" --arg interface "${iface}" --arg lokale_ip "${local_ip}" --arg quelle "${quelle}" --argjson rx "${rx}" --argjson tx "${tx}" --argjson zeit "${jetzt}" '.rate_vorwerte = (.rate_vorwerte // {}) | .rate_vorwerte.netz = {interface_signatur:$signatur,interface:$interface,lokale_ip:$lokale_ip,quelle:$quelle,rx_bytes:$rx,tx_bytes:$tx,zeit_unix:$zeit}' 2>/dev/null || echo "${NODEPILOT_AGENT_STATE_JSON}")"
	nodepilot_agent_state_speichern || true

	if [ -z "${prev_signatur}" ] || ! [[ "${prev_rx}" =~ ^[0-9]+$ ]] || ! [[ "${prev_tx}" =~ ^[0-9]+$ ]] || ! [[ "${prev_time}" =~ ^[0-9]+$ ]]; then
		nodepilot_log "info" "netzwerk_kbit: Initialisierung fuer Interface ${iface} (quelle=${quelle}, lokale_ip=${local_ip:-leer}); Delta-Werte erst ab naechster Messung verfuegbar."
		jq -n '{net_in_kbit:null,net_out_kbit:null,netz_status:"kein_messpunkt"}' 2>/dev/null || echo '{"net_in_kbit":null,"net_out_kbit":null,"netz_status":"kein_messpunkt"}'
		return
	fi

	if [ "${prev_signatur}" != "${signatur}" ]; then
		nodepilot_log "warn" "netzwerk_kbit: Interface-Wechsel erkannt (${prev_signatur} -> ${signatur}); Delta-State wird neu initialisiert."
		jq -n '{net_in_kbit:null,net_out_kbit:null,netz_status:"kein_messpunkt"}' 2>/dev/null || echo '{"net_in_kbit":null,"net_out_kbit":null,"netz_status":"kein_messpunkt"}'
		return
	fi

	local delta_t delta_rx delta_tx
	delta_t="$((jetzt - prev_time))"
	delta_rx="$((rx - prev_rx))"
	delta_tx="$((tx - prev_tx))"
	if [ "${delta_t}" -le 0 ]; then
		nodepilot_log "warn" "netzwerk_kbit: Ungueltige Zeitdifferenz (${delta_t}s) auf ${signatur}; Delta-State neu initialisiert."
		jq -n '{net_in_kbit:null,net_out_kbit:null,netz_status:"fehler"}' 2>/dev/null || echo '{"net_in_kbit":null,"net_out_kbit":null,"netz_status":"fehler"}'
		return
	fi
	if [ "${delta_rx}" -lt 0 ] || [ "${delta_tx}" -lt 0 ]; then
		nodepilot_log "warn" "netzwerk_kbit: Counter-Reset erkannt auf ${signatur} (delta_rx=${delta_rx}, delta_tx=${delta_tx}); Delta-State neu initialisiert."
		jq -n '{net_in_kbit:null,net_out_kbit:null,netz_status:"counter_reset"}' 2>/dev/null || echo '{"net_in_kbit":null,"net_out_kbit":null,"netz_status":"counter_reset"}'
		return
	fi

	local in_kbit out_kbit
	in_kbit="$(printf '%s %s
' "${delta_rx}" "${delta_t}" | awk '{if($2<=0){printf "%.2f", 0}else{printf "%.2f", ($1*8)/($2*1000)}}')"
	out_kbit="$(printf '%s %s
' "${delta_tx}" "${delta_t}" | awk '{if($2<=0){printf "%.2f", 0}else{printf "%.2f", ($1*8)/($2*1000)}}')"
	nodepilot_log "debug" "netzwerk_kbit: Delta ${delta_t}s, RX ${delta_rx}B, TX ${delta_tx}B, In ${in_kbit}kbit/s, Out ${out_kbit}kbit/s."
	jq -n --argjson in_kbit "${in_kbit}" --argjson out_kbit "${out_kbit}" '{net_in_kbit:$in_kbit,net_out_kbit:$out_kbit,netz_status:"ok"}' 2>/dev/null || echo '{}'
}

nodepilot_netzwerk_interface_mit_ip_ermitteln()
{
	local gesuchte_ip="$1"
	if ! nodepilot_ip_format_gueltig "${gesuchte_ip}"; then
		return 1
	fi

	local family_opt="-4"
	if [[ "${gesuchte_ip}" =~ : ]]; then
		family_opt="-6"
	fi

	local zeile kandidat cidr ip_clean
	while IFS= read -r zeile; do
		kandidat="$(echo "${zeile}" | awk '{print $2}' | awk -F'@' '{print $1}')"
		cidr="$(echo "${zeile}" | awk '{print $4}')"
		ip_clean="${cidr%%/*}"
		if [ "${ip_clean}" != "${gesuchte_ip}" ]; then
			continue
		fi
		if [ -z "${kandidat}" ]; then
			continue
		fi
		echo "${kandidat}"
		return 0
	done < <(ip ${family_opt} -o addr show scope global 2>/dev/null)

	return 1
}

nodepilot_api_ziel_ip_ermitteln()
{
	local server_url host
	server_url="$(nodepilot_ini_wert "server" "server_url" "")"
	host="$(echo "${server_url}" | awk -F/ '{print $3}' | awk -F: '{print $1}')"
	if [ -z "${host}" ]; then
		host="$(nodepilot_ini_wert "server" "api_host" "${NODEPILOT_DEFAULT_API_HOST}")"
	fi
	if [ -z "${host}" ]; then
		return 1
	fi

	if nodepilot_ip_format_gueltig "${host}"; then
		echo "${host}"
		return 0
	fi

	local ziel_ip
	ziel_ip="$(getent ahosts "${host}" 2>/dev/null | awk 'NR==1{print $1}')"
	if ! nodepilot_ip_format_gueltig "${ziel_ip}"; then
		return 1
	fi
	echo "${ziel_ip}"
}

nodepilot_netzwerk_route_interface_json_ermitteln()
{
	local ziel_ip="$1"
	if ! nodepilot_ip_format_gueltig "${ziel_ip}"; then
		echo '{}'
		return 1
	fi

	local route_text iface src
	route_text="$(ip route get "${ziel_ip}" 2>/dev/null | head -n 1)"
	iface="$(echo "${route_text}" | awk '{for(i=1;i<=NF;i++){if($i=="dev" && (i+1)<=NF){print $(i+1); exit}}}')"
	src="$(echo "${route_text}" | awk '{for(i=1;i<=NF;i++){if($i=="src" && (i+1)<=NF){print $(i+1); exit}}}')"
	if [ -z "${iface}" ]; then
		echo '{}'
		return 1
	fi
	jq -nc --arg interface "${iface}" --arg lokale_ip "${src}" --arg ziel_ip "${ziel_ip}" --arg quelle "route_api_ziel" '{"interface":$interface,"lokale_ip":$lokale_ip,"ziel_ip":$ziel_ip,"quelle":$quelle}'
}

nodepilot_netzwerk_kandidat_json_bauen()
{
	local iface="$1"
	local lokale_ip="$2"
	local quelle="$3"
	local rx tx

	rx="$(cat "/sys/class/net/${iface}/statistics/rx_bytes" 2>/dev/null || echo "")"
	tx="$(cat "/sys/class/net/${iface}/statistics/tx_bytes" 2>/dev/null || echo "")"
	if ! [[ "${rx}" =~ ^[0-9]+$ ]] || ! [[ "${tx}" =~ ^[0-9]+$ ]]; then
		NODEPILOT_NETZWERK_INTERFACE_SCAN_STATUS="counter_unlesbar"
		echo '{}'
		return 1
	fi

	jq -nc --arg interface "${iface}" --arg lokale_ip "${lokale_ip}" --arg quelle "${quelle}" --argjson rx "${rx}" --argjson tx "${tx}" '{"interface":$interface,"lokale_ip":$lokale_ip,"quelle":$quelle,"rx_bytes":$rx,"tx_bytes":$tx}'
}

nodepilot_netzwerk_interface_ausgeschlossen()
{
	local iface="$1"
	if [ -z "${iface}" ]; then
		return 0
	fi
	if [[ "${iface}" =~ ^(lo|docker.*|br-.*|veth.*|virbr.*|tun.*|tap.*)$ ]]; then
		return 0
	fi
	return 1
}

nodepilot_netzwerk_interface_ermitteln()
{
	local iface=""
	iface="$(ip route show default 2>/dev/null | awk '{for(i=1;i<=NF;i++){if($i=="dev" && (i+1)<=NF){print $(i+1); exit}}}' | head -n 1)"
	if [ -n "${iface}" ] && nodepilot_netzwerk_interface_ausgeschlossen "${iface}"; then
		nodepilot_log "warn" "netzwerk_kbit: Default-Route-Interface ${iface} ist ausgeschlossen; nutze Fallback-Suche."
		iface=""
	fi
	if [ -n "${iface}" ]; then
		echo "${iface}"
		return
	fi

	local kandidat operstate
	while IFS= read -r kandidat; do
		if [ -z "${kandidat}" ]; then
			continue
		fi
		if nodepilot_netzwerk_interface_ausgeschlossen "${kandidat}"; then
			continue
		fi
		operstate="$(cat "/sys/class/net/${kandidat}/operstate" 2>/dev/null || echo "down")"
		if [ "${operstate}" != "up" ] && [ "${operstate}" != "unknown" ]; then
			continue
		fi
		if [ ! -r "/sys/class/net/${kandidat}/statistics/rx_bytes" ] || [ ! -r "/sys/class/net/${kandidat}/statistics/tx_bytes" ]; then
			continue
		fi
		echo "${kandidat}"
		return
	done < <(ip -o link show 2>/dev/null | awk -F': ' '{print $2}' | awk -F'@' '{print $1}')
}

nodepilot_netzwerk_relevantes_interface_json_ermitteln()
{
	NODEPILOT_NETZWERK_INTERFACE_SCAN_STATUS="ok"
	local server_sicht_ip iface route_json ziel_ip operstate lokale_ip
	server_sicht_ip="$(nodepilot_ini_wert "server" "letzte_agent_quell_ip" "")"
	if nodepilot_ip_format_gueltig "${server_sicht_ip}" && iface="$(nodepilot_netzwerk_interface_mit_ip_ermitteln "${server_sicht_ip}")"; then
		if [ -r "/sys/class/net/${iface}/operstate" ]; then
			operstate="$(cat "/sys/class/net/${iface}/operstate" 2>/dev/null || echo "down")"
			if [ "${operstate}" = "up" ] || [ "${operstate}" = "unknown" ]; then
				nodepilot_log "info" "netzwerk_kbit: Interface ueber serverseitig gesehene Agent-IP erkannt (ip=${server_sicht_ip}, interface=${iface})."
				nodepilot_netzwerk_kandidat_json_bauen "${iface}" "${server_sicht_ip}" "server_sicht_ip" && return 0
			fi
		fi
	fi

	ziel_ip="$(nodepilot_api_ziel_ip_ermitteln || true)"
	route_json="$(nodepilot_netzwerk_route_interface_json_ermitteln "${ziel_ip}")"
	iface="$(echo "${route_json}" | jq -r '.interface // ""' 2>/dev/null || echo "")"
	lokale_ip="$(echo "${route_json}" | jq -r '.lokale_ip // ""' 2>/dev/null || echo "")"
	if [ -n "${iface}" ]; then
		if nodepilot_netzwerk_interface_ausgeschlossen "${iface}"; then
			nodepilot_log "warn" "netzwerk_kbit: Route zeigt auf Interface ${iface}, das in generischer Ausschlussliste liegt; Nutzung wird dennoch erzwungen."
		fi
		nodepilot_netzwerk_kandidat_json_bauen "${iface}" "${lokale_ip}" "route_api_ziel" && return 0
	fi

	local kandidat
	while IFS= read -r kandidat; do
		if [ -z "${kandidat}" ]; then
			continue
		fi
		if nodepilot_netzwerk_interface_ausgeschlossen "${kandidat}"; then
			continue
		fi
		operstate="$(cat "/sys/class/net/${kandidat}/operstate" 2>/dev/null || echo "down")"
		if [ "${operstate}" != "up" ] && [ "${operstate}" != "unknown" ]; then
			continue
		fi
		nodepilot_netzwerk_kandidat_json_bauen "${kandidat}" "" "fallback_aktiv" && return 0
	done < <(ip -o link show 2>/dev/null | awk -F': ' '{print $2}' | awk -F'@' '{print $1}')

	echo '{}'
}

nodepilot_netzwerk_interfaces_json_ermitteln()
{
	local einzel_json
	einzel_json="$(nodepilot_netzwerk_relevantes_interface_json_ermitteln)"
	local iface
	iface="$(echo "${einzel_json}" | jq -r '.interface // ""' 2>/dev/null || echo "")"
	if [ -z "${iface}" ]; then
		echo "[]"
		return 0
	fi
	echo "${einzel_json}" | jq -c '[{interface:.interface,rx_bytes:(.rx_bytes // 0),tx_bytes:(.tx_bytes // 0)}]' 2>/dev/null || echo '[]'
}

nodepilot_latenz_ms_json()
{
	local latenz_ms_raw latenz_status
	latenz_ms_raw="$(nodepilot_status_lesen "standard_api_latenz_ms")"
	latenz_status="$(nodepilot_status_lesen "standard_api_latenz_status")"
	if [ -z "${latenz_status}" ]; then
		latenz_status="kein_messpunkt"
	fi
	if ! [[ "${latenz_ms_raw}" =~ ^[0-9]+$ ]]; then
		jq -n --arg status "${latenz_status}" '{latenz_ms:null,latenz_status:$status}' 2>/dev/null || echo '{}'
		return
	fi
	jq -n --argjson latenz "${latenz_ms_raw}" --arg status "${latenz_status}" '{latenz_ms:$latenz,latenz_status:$status}' 2>/dev/null || echo '{}'
}

nodepilot_metriken_json()
{
	local cpu ram_total ram_available swap_total swap_free
	cpu="$(nodepilot_zahl_oder_fallback "$(awk '{print $1}' /proc/loadavg 2>/dev/null || echo "0")" "0" "metriken/cpu")"
	ram_total="$(nodepilot_zahl_oder_fallback "$(awk '/MemTotal/ {print int($2/1024)}' /proc/meminfo 2>/dev/null || echo "0")" "0" "metriken/ram_total")"
	ram_available="$(nodepilot_zahl_oder_fallback "$(awk '/MemAvailable/ {print int($2/1024)}' /proc/meminfo 2>/dev/null || echo "0")" "0" "metriken/ram_available")"
	swap_total="$(nodepilot_zahl_oder_fallback "$(awk '/SwapTotal/ {print int($2/1024)}' /proc/meminfo 2>/dev/null || echo "0")" "0" "metriken/swap_total")"
	swap_free="$(nodepilot_zahl_oder_fallback "$(awk '/SwapFree/ {print int($2/1024)}' /proc/meminfo 2>/dev/null || echo "0")" "0" "metriken/swap_free")"
	local ram_used=$((ram_total-ram_available))
	[ "${ram_total}" -le 0 ] && ram_total=1
	local ram_pct
	ram_pct="$(awk -v u="${ram_used}" -v t="${ram_total}" 'BEGIN{printf "%.2f", (u*100)/t}')"
	local swap_used=$((swap_total-swap_free))
	[ "${swap_total}" -le 0 ] && swap_total=1
	local swap_pct
	swap_pct="$(awk -v u="${swap_used}" -v t="${swap_total}" 'BEGIN{printf "%.2f", (u*100)/t}')"
	local disk_pct disk_free
	disk_pct="$(nodepilot_zahl_oder_fallback "$(df -Pm / | awk 'NR==2 {print $5}' | tr -d '%')" "0" "metriken/disk_pct")"
	disk_free="$(nodepilot_zahl_oder_fallback "$(df -Pm / | awk 'NR==2 {print $4}')" "0" "metriken/disk_free")"
	local load1 load5 load15
	load1="$(nodepilot_zahl_oder_fallback "$(awk '{print $1}' /proc/loadavg 2>/dev/null || echo 0)" "0" "metriken/load1")"
	load5="$(nodepilot_zahl_oder_fallback "$(awk '{print $2}' /proc/loadavg 2>/dev/null || echo 0)" "0" "metriken/load5")"
	load15="$(nodepilot_zahl_oder_fallback "$(awk '{print $3}' /proc/loadavg 2>/dev/null || echo 0)" "0" "metriken/load15")"
	local net_json
	net_json="$(nodepilot_netzwerk_kbit_json)"
	local latenz_json
	latenz_json="$(nodepilot_latenz_ms_json)"

	if ! jq -n \
		--argjson cpu "${cpu}" \
		--argjson ram_pct "${ram_pct}" \
		--argjson ram_used "${ram_used}" \
		--argjson ram_total "${ram_total}" \
		--argjson swap_pct "${swap_pct}" \
		--argjson load1 "${load1}" \
		--argjson load5 "${load5}" \
		--argjson load15 "${load15}" \
		--argjson disk_pct "${disk_pct:-0}" \
		--argjson disk_free "${disk_free:-0}" \
		--argjson net "${net_json}" \
		--argjson latenz "${latenz_json}" \
		'{cpu_auslastung_prozent:$cpu, ram_belegt_prozent:$ram_pct, ram_belegt_mb:$ram_used, ram_gesamt_mb:$ram_total, swap_belegt_prozent:$swap_pct, load_1:$load1, load_5:$load5, load_15:$load15, disk_root_belegt_prozent:$disk_pct, disk_root_frei_mb:$disk_free, netz_status:(($net.netz_status // "fehler")), latenz_status:(($latenz.latenz_status // "fehler"))} + $net + $latenz'; then
		nodepilot_log "warn" "metriken_json: jq-Fehler beim JSON-Bau; nutze Fallback {}."
		echo '{}'
	fi
}

nodepilot_dienste_json()
{
	local roh_liste roh_json
	roh_liste="$(systemctl list-units --type=service --all --no-legend --no-pager --plain 2>/dev/null | head -n 200 | awk '{name=$1;load=$2;active=$3;sub_status=$4;$1="";$2="";$3="";$4="";desc=$0;sub(/^[[:space:]]+/,"",desc);printf "%s|%s|%s|%s\n",name,active,sub_status,desc}')"
	roh_json="$(echo "${roh_liste}" | jq -R -s 'split("\n")|map(select(length>0))|map(split("|"))|map({name:(.[0] // ""),status:(.[1] // ""),active_state:(.[1] // ""),sub_status:(.[2] // ""),sub_state:(.[2] // ""),description:(.[3] // "")})|map(select((.name|type)=="string" and (.name|length)>=2 and (.name|test("[A-Za-z0-9]"))))' 2>/dev/null || true)"
	if ! echo "${roh_json}" | jq -c . >/dev/null 2>&1; then
		nodepilot_parser_fehler_loggen "dienste_json" "${roh_liste}"
		echo '[]'
		return
	fi
	echo "${roh_json}" | jq -c '[ .[] | .name |= (tostring|gsub("^[[:space:]]+|[[:space:]]+$";"")) | select(.name|length>=2) ]'
}

nodepilot_docker_json()
{
	if ! command -v docker >/dev/null 2>&1; then
		echo '[]'
		return
	fi
	local zeilen_json='[]'
	local zeile
	while IFS= read -r zeile; do
		if [ -z "${zeile}" ]; then
			continue
		fi
		local cid name image status state restart_count health_status
		cid="$(echo "${zeile}" | cut -d'|' -f1)"
		name="$(echo "${zeile}" | cut -d'|' -f2)"
		image="$(echo "${zeile}" | cut -d'|' -f3)"
		status="$(echo "${zeile}" | cut -d'|' -f4)"
		state="$(echo "${zeile}" | cut -d'|' -f5)"
		restart_count="$(docker inspect --format '{{.RestartCount}}' "${cid}" 2>/dev/null | head -n1)"
		health_status="$(docker inspect --format '{{if .State.Health}}{{.State.Health.Status}}{{end}}' "${cid}" 2>/dev/null | head -n1)"
		if ! [[ "${restart_count}" =~ ^[0-9]+$ ]]; then
			restart_count=""
		fi
		zeilen_json="$(echo "${zeilen_json}" | jq -c --arg cid "${cid}" --arg name "${name}" --arg image "${image}" --arg status "${status}" --arg state "${state}" --arg health "${health_status}" --arg restart_count "${restart_count}" '. + [{container_id:$cid,name:$name,image:$image,status:$status,state:$state,health_status:$health,restart_count:($restart_count|if .=="" then null else tonumber end)}]')"
	done < <(docker ps -a --format '{{.ID}}|{{.Names}}|{{.Image}}|{{.Status}}|{{.State}}' 2>/dev/null || true)
	echo "${zeilen_json}" | jq -c 'map(select((.container_id|length)>0 and (.name|length)>=1))' 2>/dev/null || echo '[]'
}

nodepilot_mariadb_status_oder_null()
{
	local status_json="$1"
	local key="$2"
	echo "${status_json}" | jq -r --arg key "${key}" '(.[$key] // "") | tostring' 2>/dev/null || echo ""
}

nodepilot_mariadb_query_cursor_key()
{
	local quelle="$1"
	echo ".mariadb_query_cursor.\"${quelle}\""
}

nodepilot_mariadb_query_cursor_wert()
{
	local quelle="$1"
	local feld="$2"
	local basis
	basis="$(nodepilot_mariadb_query_cursor_key "${quelle}")"
	nodepilot_agent_state_wert_lesen "${basis}.${feld}"
}

nodepilot_mariadb_query_cursor_merken()
{
	local quelle="$1"
	local marker="$2"
	local fingerprints_json="$3"
	local basis
	basis="$(nodepilot_mariadb_query_cursor_key "${quelle}")"
	NODEPILOT_AGENT_STATE_JSON="$(echo "${NODEPILOT_AGENT_STATE_JSON}" | jq -c --argjson fingerprints "${fingerprints_json}" --arg quelle "${quelle}" --arg marker "${marker}" --arg zeit "$(date -u +%Y-%m-%dT%H:%M:%SZ)" '
		.mariadb_query_cursor = (.mariadb_query_cursor // {})
		| .mariadb_query_cursor[$quelle] = ((.mariadb_query_cursor[$quelle] // {}) + {marker:$marker,letzte_aktualisierung:$zeit,fingerprints:$fingerprints})
	' 2>/dev/null || echo "${NODEPILOT_AGENT_STATE_JSON}")"
}

nodepilot_mariadb_query_dedupe_mit_cursor()
{
	local quelle="$1"
	local eintraege_json="$2"
	local bekannte_fingerprints
	bekannte_fingerprints="$(nodepilot_mariadb_query_cursor_wert "${quelle}" "fingerprints")"
	if [ -z "${bekannte_fingerprints}" ]; then
		bekannte_fingerprints='[]'
	fi
	local dedupe_json
	dedupe_json="$(jq -c --arg quelle "${quelle}" --argjson bekannt "${bekannte_fingerprints}" '
		($bekannt | map(tostring)) as $known
		| reduce .[] as $item (
			{neu:[],alle:$known};
			($item.query_sql_normalisiert // $item.query_sql_roh // $item.sql_normalisiert // $item.sql // "") as $sql
			| ($item.query_zeitstempel // $item.zeitstempel // "") as $zeit
			| ($item.query_user // $item.benutzer // "") as $usr
			| ($item.query_datenbank // $item.datenbank // "") as $db
			| ($item.query_dauer_ms // $item.dauer_ms // 0 | tostring) as $dauer
			| ($quelle + "|" + $zeit + "|" + $usr + "|" + $db + "|" + $dauer + "|" + $sql) as $fingerprint_roh
			| ($fingerprint_roh | @base64) as $fp
			| if ($sql == "") and (($item.typ // "") != "diagnose") then .
			  elif (.alle | index($fp)) != null then .
			  else .neu += [($item + {np_fingerprint:$fp})] | .alle += [$fp]
			  end
		)
		| {neu:.neu,alle:(.alle | unique | reverse | .[0:300])}
	' <<< "${eintraege_json}" 2>/dev/null || echo '{"neu":[],"alle":[]}')"
	echo "${dedupe_json}"
}

nodepilot_mariadb_sql_normalisieren()
{
	local sql_roh="$1"
	printf '%s' "${sql_roh}" | perl -0777 -pe "
		s/\\/\\*![0-9]+.*?\\*\\///gs;
		s/\\/\\*.*?\\*\\///gs;
		s/--[^\n\r]*/ /g;
		s/#[^\n\r]*/ /g;
		s/[\\r\\n\\t]+/ /g;
		s/'([^'\\\\]|\\\\.)*'/?/g;
		s/\"([^\"\\\\]|\\\\.)*\"/?/g;
		s/\\b[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-5][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}\\b/?/g;
		s/\\b\\d{4}-\\d{2}-\\d{2}[ T]\\d{2}:\\d{2}:\\d{2}(?:\\.\\d+)?\\b/?/g;
		s/\\b\\d{4}-\\d{2}-\\d{2}\\b/?/g;
		s/\\b\\d{2}:\\d{2}:\\d{2}(?:\\.\\d+)?\\b/?/g;
		s/\\b0x[0-9a-fA-F]+\\b/?/g;
		s/\\b\\d+(?:\\.\\d+)?\\b/?/g;
		s/\\bIN\\s*\\((?:\\s*\\?\\s*,?)+\\)/IN (?)/ig;
		s/\\s+/ /g;
		s/^\\s+|\\s+$//g;
	" 2>/dev/null || printf '%s' "${sql_roh}" | tr '\n\r\t' '   ' | sed 's/[[:space:]][[:space:]]*/ /g; s/^ //; s/ $//'
}

nodepilot_mariadb_norm_text_oder_null()
{
	local wert="$1"
	local norm
	norm="$(printf '%s' "${wert}" | sed -E 's/^[[:space:]]+//; s/[[:space:]]+$//')"
	if [ -z "${norm}" ]; then
		echo ""
		return
	fi
	case "${norm,,}" in
		null|"(null)"|"\"null\""|n/a|na|-)
			echo ""
			;;
		*)
			echo "${norm}"
			;;
	esac
}

nodepilot_mariadb_processlist_command_klassifizieren()
{
	local cmd_raw="$1"
	local cmd_norm
	cmd_norm="$(nodepilot_mariadb_norm_text_oder_null "${cmd_raw}")"
	case "${cmd_norm,,}" in
		query)
			echo "Query"
			;;
		execute)
			echo "Execute"
			;;
		prepare)
			echo "Prepare"
			;;
		connect)
			echo "Connect"
			;;
		sleep)
			echo "Sleep"
			;;
		binlog\ dump*)
			echo "Binlog Dump"
			;;
		daemon)
			echo "Daemon"
			;;
		*)
			if [ -z "${cmd_norm}" ]; then
				echo "Sonstige"
			else
				echo "${cmd_norm}"
			fi
			;;
	esac
}

nodepilot_mariadb_processlist_typ_ableiten()
{
	local cmd="$1"
	local user="$2"
	case "${cmd}" in
		Connect)
			echo "processlist_connect"
			;;
		Sleep)
			echo "processlist_sleep"
			;;
		Daemon|Binlog\ Dump)
			echo "processlist_system"
			;;
		Query|Execute|Prepare)
			echo "processlist_snapshot"
			;;
		*)
			if printf '%s' "${user,,}" | grep -Eq '^(system user|event_scheduler|mysql\.sys)$'; then
				echo "processlist_system"
			else
				echo "processlist_snapshot"
			fi
			;;
	esac
}

nodepilot_sha256_hex()
{
	local text="$1"
	if command -v sha256sum >/dev/null 2>&1; then
		printf '%s' "${text}" | sha256sum | awk '{print $1}'
		return
	fi
	printf '%s' "${text}" | openssl dgst -sha256 2>/dev/null | awk '{print $NF}'
}

nodepilot_mariadb_sql_abfragen_json()
{
	local mariadb_cli="$1"
	local status_json="$2"
	local variablen_json="$3"
	local general_log_aktiv log_output perf_schema_aktiv
	local query_quelle="diagnose"
	local query_modus="diagnose"
	local query_limit=100
	local query_diag='[]'
	local sql_abfragen='[]'
	local verwerfungen='[]'
	local gruende_hoeher='[]'
	local query_marker=""
	local query_total_roh=0
	local query_nach_filterung=0
	local dedupe_json='{"neu":[],"alle":[]}'
	local dedupe_neu=0
	local dedupe_verworfen=0
	local verworfen_gesamt=0
	local verworfen_gruende='{}'

	general_log_aktiv="$(echo "${variablen_json}" | jq -r '.general_log // ""' 2>/dev/null || echo "")"
	log_output="$(echo "${variablen_json}" | jq -r '.log_output // ""' 2>/dev/null || echo "")"
	perf_schema_aktiv="$(echo "${variablen_json}" | jq -r '.performance_schema // ""' 2>/dev/null || echo "")"

	nodepilot_mariadb_query_ausquelle()
	{
		local quelle="$1"
		local sql="$2"
		local raw_file err_file raw_text err_text
		raw_file="$(mktemp)"
		err_file="$(mktemp)"
		"${mariadb_cli}" --connect-timeout=4 -NBe "${sql}" >"${raw_file}" 2>"${err_file}" || true
		raw_text="$(cat "${raw_file}" 2>/dev/null || true)"
		err_text="$(cat "${err_file}" 2>/dev/null || true)"
		rm -f "${raw_file}" "${err_file}" 2>/dev/null || true
		printf '%s\n---NP_ERR---\n%s' "${raw_text}" "${err_text}"
	}

	if [ "${sql_abfragen}" = "[]" ] && { [ "${perf_schema_aktiv,,}" = "on" ] || [ "${perf_schema_aktiv}" = "1" ]; }; then
		local perf_quelle perf_sql perf_resp perf_raw perf_err
		for perf_quelle in "performance_schema.events_statements_history_long" "performance_schema.events_statements_history"; do
			local perf_cursor
			perf_cursor="$(nodepilot_mariadb_query_cursor_wert "${perf_quelle}" "marker")"
			perf_sql="SELECT EVENT_ID, DATE_FORMAT(NOW(),'%Y-%m-%d %H:%i:%s'), THREAD_ID, CURRENT_SCHEMA, LEFT(SQL_TEXT,4000), LEFT(DIGEST_TEXT,1000), TIMER_WAIT, LOCK_TIME, ROWS_EXAMINED, ROWS_SENT, ROWS_AFFECTED, CREATED_TMP_TABLES, CREATED_TMP_DISK_TABLES, NO_INDEX_USED, NO_GOOD_INDEX_USED FROM ${perf_quelle} WHERE SQL_TEXT IS NOT NULL AND LENGTH(TRIM(SQL_TEXT))>0"
			if [ -n "${perf_cursor}" ]; then
				perf_sql="${perf_sql} AND EVENT_ID > ${perf_cursor}"
			fi
			perf_sql="${perf_sql} ORDER BY EVENT_ID DESC LIMIT ${query_limit}"
			perf_resp="$(nodepilot_mariadb_query_ausquelle "${perf_quelle}" "${perf_sql}")"
			perf_raw="$(printf '%s' "${perf_resp}" | awk 'BEGIN{p=1} /---NP_ERR---/{p=0;next} p==1{print}')"
			perf_err="$(printf '%s' "${perf_resp}" | awk 'BEGIN{p=0} /---NP_ERR---/{p=1;next} p==1{print}')"
			if [ -n "${perf_raw}" ]; then
				sql_abfragen="$(echo "${perf_raw}" | jq -R -s --arg quelle "${perf_quelle}" '
					split("\n") | map(select(length>0)) | map(split("\t")) | map({
						typ:"performance_schema_statement",
						query_quelle:$quelle,
						query_modus:"performance_schema",
						query_event_id:((.[0]|tonumber?) // null),
						query_zeitstempel:(.[1] // ""),
						query_schema_name:(.[3] // null),
						query_datenbank:(.[3] // null),
						query_sql_roh:(.[4] // ""),
						query_sql_normalisiert:null,
						query_digest_text:(.[5] // null),
						query_command_type:(((.[4] // "") | ascii_downcase | gsub("^[[:space:]]+";"") | split(" ")[0]) // "Query"),
						query_typ:"performance_schema_statement",
						query_dauer_ms:((.[6]|tonumber?) // null | if .==null then null else ./1000000000 end),
						query_lock_dauer_ms:((.[7]|tonumber?) // null | if .==null then null else ./1000000000 end),
						query_rows_examined:((.[8]|tonumber?) // null),
						query_rows_sent:((.[9]|tonumber?) // null),
						query_rows_affected:((.[10]|tonumber?) // null),
						query_tmp_tables:((.[11]|tonumber?) // null),
						query_tmp_disk_tables:((.[12]|tonumber?) // null),
						query_no_index_used:(if (.[13] // "")=="" then null elif ((.[13]|ascii_downcase)=="yes" or (.[13]=="1")) then true else false end),
						query_no_good_index_used:(if (.[14] // "")=="" then null elif ((.[14]|ascii_downcase)=="yes" or (.[14]=="1")) then true else false end)
					}) | map(select((.query_sql_roh|length)>0))
				' 2>/dev/null || echo '[]')"
				query_quelle="${perf_quelle}"
				query_modus="performance_schema"
				query_marker="$(echo "${perf_raw}" | awk -F'	' 'NF>=1 {print $1+0}' | sort -n | tail -n 1)"
				break
			else
				gruende_hoeher="$(jq -c --arg quelle "${perf_quelle}" --arg grund "$(echo "${perf_quelle}" | awk -F'.' '{print $2}')_nicht_verfuegbar" --arg detail "${perf_err}" '. + [{quelle:$quelle,grund:$grund,detail:$detail}]' <<< "${gruende_hoeher}" 2>/dev/null || echo "${gruende_hoeher}")"
			fi
		done
	elif [ "${sql_abfragen}" = "[]" ]; then
		gruende_hoeher="$(jq -c '. + [{quelle:"performance_schema",grund:"performance_schema_deaktiviert",detail:"performance_schema ist nicht aktiv"}]' <<< "${gruende_hoeher}" 2>/dev/null || echo "${gruende_hoeher}")"
	fi

	if [ "${sql_abfragen}" = "[]" ] && { [ "${perf_schema_aktiv,,}" = "on" ] || [ "${perf_schema_aktiv}" = "1" ]; } && [ "${NODEPILOT_MARIADB_ALLOW_DIGEST_FALLBACK:-0}" = "1" ]; then
		local digest_quelle digest_sql digest_resp digest_raw digest_err
		digest_quelle="performance_schema.events_statements_summary_by_digest"
		digest_sql="SELECT DATE_FORMAT(NOW(),'%Y-%m-%d %H:%i:%s'),SCHEMA_NAME,LEFT(DIGEST,128),LEFT(DIGEST_TEXT,1000),COUNT_STAR,SUM_TIMER_WAIT,SUM_LOCK_TIME,SUM_ROWS_EXAMINED,SUM_ROWS_SENT,SUM_ROWS_AFFECTED,SUM_CREATED_TMP_TABLES,SUM_CREATED_TMP_DISK_TABLES,SUM_NO_INDEX_USED,SUM_NO_GOOD_INDEX_USED,FIRST_SEEN,LAST_SEEN FROM ${digest_quelle} WHERE DIGEST_TEXT IS NOT NULL AND LENGTH(TRIM(DIGEST_TEXT))>0 ORDER BY SUM_TIMER_WAIT DESC LIMIT ${query_limit}"
		digest_resp="$(nodepilot_mariadb_query_ausquelle "${digest_quelle}" "${digest_sql}")"
		digest_raw="$(printf '%s' "${digest_resp}" | awk 'BEGIN{p=1} /---NP_ERR---/{p=0;next} p==1{print}')"
		digest_err="$(printf '%s' "${digest_resp}" | awk 'BEGIN{p=0} /---NP_ERR---/{p=1;next} p==1{print}')"
		if [ -n "${digest_raw}" ]; then
			sql_abfragen="$(echo "${digest_raw}" | jq -R -s --arg quelle "${digest_quelle}" '
				split("\n") | map(select(length>0)) | map(split("\t")) | map({
					typ:"performance_schema_digest_summary",
					query_quelle:$quelle,
					query_modus:"performance_schema_digest",
					query_event_id:null,
					query_zeitstempel:(.[0] // ""),
					query_schema_name:(.[1] // null),
					query_datenbank:(.[1] // null),
					query_sql_roh:(.[3] // ""),
					query_sql_normalisiert:(.[3] // ""),
					query_digest:(.[2] // null),
					query_digest_text:(.[3] // null),
					query_command_type:(((.[3] // "") | ascii_downcase | gsub("^[[:space:]]+";"") | split(" ")[0]) // "Query"),
					query_typ:"performance_schema_digest_summary",
					query_dauer_ms:((.[5]|tonumber?) // null | if .==null then null else ./1000000000 end),
					query_lock_dauer_ms:((.[6]|tonumber?) // null | if .==null then null else ./1000000000 end),
					query_rows_examined:((.[7]|tonumber?) // null),
					query_rows_sent:((.[8]|tonumber?) // null),
					query_rows_affected:((.[9]|tonumber?) // null),
					query_tmp_tables:((.[10]|tonumber?) // null),
					query_tmp_disk_tables:((.[11]|tonumber?) // null),
					query_no_index_used:(if (.[12] // "")=="" then null elif ((.[12]|ascii_downcase)=="yes" or (.[12]=="1")) then true else false end),
					query_no_good_index_used:(if (.[13] // "")=="" then null elif ((.[13]|ascii_downcase)=="yes" or (.[13]=="1")) then true else false end),
					query_json:{digest_count_star:((.[4]|tonumber?) // null),first_seen:(.[14] // null),last_seen:(.[15] // null),quelle_detail:"digest_summary"}
				}) | map(select((.query_sql_roh|length)>0))
			' 2>/dev/null || echo '[]')"
			query_quelle="${digest_quelle}"
			query_modus="performance_schema_digest"
			query_marker="$(date -u +%Y-%m-%dT%H:%M:%SZ)"
		else
			gruende_hoeher="$(jq -c --arg quelle "${digest_quelle}" --arg grund "events_statements_summary_by_digest_nicht_verfuegbar" --arg detail "${digest_err}" '. + [{quelle:$quelle,grund:$grund,detail:$detail}]' <<< "${gruende_hoeher}" 2>/dev/null || echo "${gruende_hoeher}")"
		fi
	elif [ "${sql_abfragen}" = "[]" ] && { [ "${perf_schema_aktiv,,}" = "on" ] || [ "${perf_schema_aktiv}" = "1" ]; }; then
		gruende_hoeher="$(jq -c '. + [{quelle:"performance_schema.events_statements_summary_by_digest",grund:"digest_fallback_deaktiviert",detail:"NODEPILOT_MARIADB_ALLOW_DIGEST_FALLBACK ist nicht gesetzt"}]' <<< "${gruende_hoeher}" 2>/dev/null || echo "${gruende_hoeher}")"
	fi

	if [ "${sql_abfragen}" = "[]" ]; then
		local processlist_resp processlist_raw processlist_err
		processlist_resp="$(nodepilot_mariadb_query_ausquelle "information_schema.processlist" "SELECT DATE_FORMAT(NOW(),'%Y-%m-%d %H:%i:%s'),ID,USER,DB,COMMAND,TIME,STATE,LEFT(INFO,4000) FROM INFORMATION_SCHEMA.PROCESSLIST WHERE ID <> CONNECTION_ID() ORDER BY TIME DESC LIMIT 80")"
		processlist_raw="$(printf '%s' "${processlist_resp}" | awk 'BEGIN{p=1} /---NP_ERR---/{p=0;next} p==1{print}')"
		processlist_err="$(printf '%s' "${processlist_resp}" | awk 'BEGIN{p=0} /---NP_ERR---/{p=1;next} p==1{print}')"
		if [ -n "${processlist_raw}" ]; then
			sql_abfragen="$(echo "${processlist_raw}" | jq -R -s '
				split("\n") | map(select(length>0)) | map(split("\t")) | map({
					typ:"processlist",
					query_quelle:"information_schema.processlist",
					query_modus:"snapshot_basiert",
					query_zeitstempel:(.[0] // ""),
					query_user:(.[2] // ""),
					query_datenbank:(.[3] // ""),
					query_schema_name:(.[3] // ""),
					query_command_type:(.[4] // ""),
					query_dauer_ms:((.[5]|tonumber?) // null | if .==null then null else . * 1000 end),
					query_sql_roh:(.[7] // ""),
					query_sql_normalisiert:null,
					query_lock_dauer_ms:null,
					query_rows_examined:null,
					query_rows_sent:null,
					query_rows_affected:null,
					query_tmp_tables:null,
					query_tmp_disk_tables:null,
					query_full_scan:null,
					query_no_index_used:null,
					query_no_good_index_used:null,
					query_digest:null,
					query_digest_text:null,
					query_typ:"processlist_snapshot",
					thread_id:((.[1]|tonumber?) // null),
					prozess_state:(.[6] // ""),
					query_json:{
						quelle:"information_schema.processlist",
						modus:"snapshot_basiert",
						qualitaetsstufe:"mittel",
						dauer_herkunft:"geschaetzt_aus_processlist",
						normalisierungsstatus:"ausstehend",
						fallback_gruende:[]
					}
				})
			' 2>/dev/null || echo '[]')"
			query_quelle="information_schema.processlist"
			query_modus="snapshot_basiert"
			query_marker="$(date -u +%Y-%m-%dT%H:%M:%SZ)"
		else
			if [ -n "${processlist_err}" ]; then
				gruende_hoeher="$(jq -c --arg detail "${processlist_err}" '. + [{quelle:"information_schema.processlist",grund:"processlist_nicht_lesbar",detail:$detail}]' <<< "${gruende_hoeher}" 2>/dev/null || echo "${gruende_hoeher}")"
			else
				gruende_hoeher="$(jq -c '. + [{quelle:"information_schema.processlist",grund:"processlist_leer",detail:"Keine aktiven Prozesse mit SQL-Text."}]' <<< "${gruende_hoeher}" 2>/dev/null || echo "${gruende_hoeher}")"
			fi
			query_diag="$(jq -n --argjson gruende "${gruende_hoeher}" '[{typ:"diagnose",diagnose_status:"keine_query_quelle_verfuegbar",fehler_text:"Keine Query-Quelle lieferte Daten.",quelle:"agent",query_quelle:"diagnose",query_modus:"diagnose",query_json:{gruende:$gruende}}]')"
		fi
	fi

	if [ "${general_log_aktiv,,}" = "on" ] || [ "${general_log_aktiv}" = "1" ]; then
		if ! echo ",${log_output}," | grep -Eiq ",(TABLE|table)," ; then
			gruende_hoeher="$(jq -c '. + [{quelle:"mysql.general_log",grund:"general_log_nicht_auf_table",detail:"log_output enthaelt nicht TABLE"}]' <<< "${gruende_hoeher}" 2>/dev/null || echo "${gruende_hoeher}")"
		fi
	else
		gruende_hoeher="$(jq -c '. + [{quelle:"mysql.general_log",grund:"general_log_deaktiviert",detail:"general_log ist nicht aktiv"}]' <<< "${gruende_hoeher}" 2>/dev/null || echo "${gruende_hoeher}")"
	fi

	if [ "${sql_abfragen}" != "[]" ]; then
		query_total_roh="$(echo "${sql_abfragen}" | jq -r 'length' 2>/dev/null || echo "0")"
		sql_abfragen="$(echo "${sql_abfragen}" | jq -c '
			map(
				.query_user = (.query_user // "" | tostring | gsub("^[[:space:]]+|[[:space:]]+$";""))
				| .query_datenbank = (.query_datenbank // "" | tostring | gsub("^[[:space:]]+|[[:space:]]+$";""))
				| .query_schema_name = (.query_schema_name // "" | tostring | gsub("^[[:space:]]+|[[:space:]]+$";""))
				| .prozess_state = (.prozess_state // "" | tostring | gsub("^[[:space:]]+|[[:space:]]+$";""))
				| .query_sql_roh = (.query_sql_roh // "" | tostring)
				| .query_user = (if (.query_user|ascii_downcase)=="null" then "" else .query_user end)
				| .query_datenbank = (if (.query_datenbank|ascii_downcase)=="null" then "" else .query_datenbank end)
				| .query_schema_name = (if (.query_schema_name|ascii_downcase)=="null" then "" else .query_schema_name end)
				| .prozess_state = (if (.prozess_state|ascii_downcase)=="null" then "" else .prozess_state end)
			)
		' 2>/dev/null || echo "${sql_abfragen}")"
		if [ "${query_quelle}" = "information_schema.processlist" ]; then
			local query_vor_processlist_filter
			query_vor_processlist_filter="$(echo "${sql_abfragen}" | jq -r 'length' 2>/dev/null || echo "0")"
			sql_abfragen="$(echo "${sql_abfragen}" | jq -c '
				map(
					.query_command_type = (
						(.query_command_type // "" | ascii_downcase) as $cmd
						| if $cmd == "query" then "Query"
						  elif $cmd == "execute" then "Execute"
						  elif $cmd == "prepare" then "Prepare"
						  elif $cmd == "connect" then "Connect"
						  elif $cmd == "sleep" then "Sleep"
						  elif ($cmd | startswith("binlog dump")) then "Binlog Dump"
						  elif $cmd == "daemon" then "Daemon"
						  elif $cmd == "" then "Sonstige"
						  else .query_command_type
						  end
					)
					| .query_typ = (
						if .query_command_type == "Connect" then "processlist_connect"
						elif .query_command_type == "Sleep" then "processlist_sleep"
						elif (.query_command_type == "Daemon" or .query_command_type == "Binlog Dump") then "processlist_system"
						elif ((.query_user // "" | ascii_downcase) == "system user") then "processlist_system"
						else "processlist_snapshot"
						end
					)
					| .prozess_state_norm = (
						(.prozess_state // "" | ascii_downcase) as $s
						| if $s == "" then null
						  elif ($s | test("sending data")) then "sending_data"
						  elif ($s | test("sorting result")) then "sorting_result"
						  elif ($s | test("statistics")) then "statistics"
						  elif ($s | test("copying to tmp table")) then "copying_to_tmp_table"
						  elif ($s | test("creating sort index")) then "creating_sort_index"
						  else ($s | gsub("[^a-z0-9]+";"_"))
						  end
					)
					| .query_dauer_ms = (if .query_dauer_ms == null then null else .query_dauer_ms end)
				)
				| map(select((.query_typ != "processlist_sleep" and .query_typ != "processlist_system") or ((.query_sql_roh // "") != "")))
			' 2>/dev/null || echo "${sql_abfragen}")"
			query_nach_filterung="$(echo "${sql_abfragen}" | jq -r 'length' 2>/dev/null || echo "0")"
			verworfen_gruende="$(jq -c --argjson anzahl "$((query_vor_processlist_filter - query_nach_filterung))" '. + {processlist_sleep_und_system_ohne_sql:($anzahl|if . < 0 then 0 else . end)}' <<< "${verworfen_gruende}" 2>/dev/null || echo "${verworfen_gruende}")"
		else
			query_nach_filterung="$(echo "${sql_abfragen}" | jq -r 'length' 2>/dev/null || echo "0")"
		fi
		dedupe_json="$(nodepilot_mariadb_query_dedupe_mit_cursor "${query_quelle}" "${sql_abfragen}")"
		sql_abfragen="$(echo "${dedupe_json}" | jq -c '.neu' 2>/dev/null || echo '[]')"
		sql_abfragen="$(echo "${sql_abfragen}" | jq -c 'map(. + {query_sql_normalisiert:(.query_sql_normalisiert // "")})' 2>/dev/null || echo '[]')"
		sql_abfragen="$(echo "${sql_abfragen}" | jq -c '.[]' 2>/dev/null | while IFS= read -r zeile; do
			[ -z "${zeile}" ] && continue
			sql_roh="$(echo "${zeile}" | jq -r '.query_sql_roh // .sql // ""' 2>/dev/null || echo "")"
			sql_norm_alt="$(echo "${zeile}" | jq -r '.query_sql_normalisiert // .sql_normalisiert // ""' 2>/dev/null || echo "")"
			if [ -n "${sql_norm_alt}" ]; then
				sql_norm="$(nodepilot_mariadb_sql_normalisieren "${sql_norm_alt}")"
			else
				sql_norm="$(nodepilot_mariadb_sql_normalisieren "${sql_roh}")"
			fi
			if [ -z "${sql_norm}" ]; then
				sql_norm="${sql_norm_alt}"
			fi
			diagnose_status="$(echo "${zeile}" | jq -r '.diagnose_status // ""' 2>/dev/null || echo "")"
			if [ -n "${diagnose_status}" ] && [ "${diagnose_status}" != "ok" ]; then
				diagnose_typ="$(echo "${zeile}" | jq -r '.typ // "diagnose"' 2>/dev/null || echo "diagnose")"
				diagnose_fehler="$(echo "${zeile}" | jq -r '.fehler_text // ""' 2>/dev/null || echo "")"
				hash_basis="diagnose|${diagnose_typ}|${diagnose_status}|${diagnose_fehler}"
			else
				hash_basis="${sql_norm}"
				if [ -z "${hash_basis}" ]; then
					hash_basis="${sql_roh}"
				fi
			fi
			hash_wert="$(nodepilot_sha256_hex "${hash_basis}")"
			echo "${zeile}" | jq -c --arg query_hash "${hash_wert}" --arg sql_normalisiert "${sql_norm}" --argjson gruende "${gruende_hoeher}" --arg herkunft "$(if [ "${query_quelle}" = "information_schema.processlist" ]; then echo "geschaetzt_aus_processlist"; elif [ "${query_quelle}" = "diagnose" ]; then echo "nicht_verfuegbar"; else echo "exakt"; fi)" '
				.query_hash = ($query_hash | ascii_downcase)
				| .query_sql_normalisiert = $sql_normalisiert
				| .query_json = ((.query_json // {}) + {
					quelle:(.query_quelle // null),
					modus:(.query_modus // null),
					diagnose_status:(.diagnose_status // null),
					thread_id:(.thread_id // null),
					event_id:(.query_event_id // .event_id // null),
					digest:(.query_digest // .digest // null),
					digest_text:(.query_digest_text // .digest_text // null),
					prozess_state:(.prozess_state // null),
					prozess_state_norm:(.prozess_state_norm // null),
					fingerprint:(.query_hash // $query_hash),
					normalisierungsstatus:(if $sql_normalisiert == "" then "leer" else "normalisiert" end),
					dauer_herkunft:$herkunft,
					qualitaetsstufe:(if (.query_quelle // "" | startswith("performance_schema")) then "hoch" elif (.query_quelle // "")=="information_schema.processlist" then "mittel" else "niedrig" end),
					fallback_gruende:$gruende
				})
				| if (.query_quelle // "") == "information_schema.processlist" then
					.query_lock_dauer_ms = null
					| .query_rows_examined = null
					| .query_rows_sent = null
					| .query_rows_affected = null
					| .query_tmp_tables = null
					| .query_tmp_disk_tables = null
					| .query_full_scan = null
					| .query_no_index_used = null
					| .query_no_good_index_used = null
					| .query_digest = null
					| .query_digest_text = null
				else .
				end
				| .diagnose_status = (.diagnose_status // "ok")
			' 2>/dev/null || echo "${zeile}"
		done | jq -s -c '.' 2>/dev/null || echo '[]')"
		local sql_abfragen_begrenzung_json
		sql_abfragen_begrenzung_json="$(echo "${sql_abfragen}" | jq -c --argjson query_limit "${query_limit}" '
			def cut($n): (if . == null then "" else tostring end) | if (length > $n) then .[:$n] else . end;
			map(
				. as $original
				| .__feldlimit = (
					(($original.fehler_text // "" | tostring | length) > 2048)
					or (($original.query_sql_roh // "" | tostring | length) > 4096)
					or (($original.query_sql_normalisiert // "" | tostring | length) > 4096)
					or (($original.query_digest_text // "" | tostring | length) > 2048)
					or (($original.query_json // null) != null and (($original.query_json | tostring | length) > 8192))
				)
				| .fehler_text = ($original.fehler_text | cut(2048))
				| .query_sql_roh = ($original.query_sql_roh | cut(4096))
				| .query_sql_normalisiert = ($original.query_sql_normalisiert | cut(4096))
				| .query_digest_text = (if ($original.query_digest_text // null) == null then null else ($original.query_digest_text | cut(2048)) end)
				| .query_digest = (if ($original.query_digest // null) == null then null else ($original.query_digest | cut(512)) end)
				| .query_json = (
					if ($original.query_json // null) == null then null
					elif (($original.query_json | tostring | length) > 8192) then
						{"gekuerzt":true,"grund":"feld_limit","vorschau":($original.query_json | tostring | cut(2048))}
					else
						$original.query_json
					end
				)
			) as $normalisiert
			| ($normalisiert | sort_by((if ((.diagnose_status // "ok") != "ok" or (.fehler_text // "") != "") then 1 else 0 end), (.query_dauer_ms // 0)) | reverse) as $priorisiert
			| {
				original_anzahl: ($priorisiert | length),
				uebernommen_anzahl: (($priorisiert | .[:$query_limit]) | length),
				query_limit_aktiv: (($priorisiert | length) > $query_limit),
				feld_limit_aktiv: (($priorisiert | map(.__feldlimit) | any) // false),
				sql_abfragen: (($priorisiert | .[:$query_limit]) | map(del(.__feldlimit)))
			}
		' 2>/dev/null || echo '{"original_anzahl":0,"uebernommen_anzahl":0,"query_limit_aktiv":false,"feld_limit_aktiv":false,"sql_abfragen":[]}')"
		sql_abfragen="$(echo "${sql_abfragen_begrenzung_json}" | jq -c '.sql_abfragen // []' 2>/dev/null || echo '[]')"
		dedupe_neu="$(echo "${sql_abfragen}" | jq -r 'length' 2>/dev/null || echo "0")"
		dedupe_verworfen="$((query_total_roh - dedupe_neu))"
		verworfen_gesamt="$((query_total_roh - dedupe_neu))"
		verworfen_gruende="$(jq -c --argjson dedupe "${dedupe_verworfen}" --argjson gefiltert "$((query_total_roh - query_nach_filterung))" '. + {cursor_dedupe:($dedupe|if . < 0 then 0 else . end),vorfilterung:($gefiltert|if . < 0 then 0 else . end)}' <<< "${verworfen_gruende}" 2>/dev/null || echo "${verworfen_gruende}")"
		if [ "${dedupe_neu}" -le 0 ]; then
			sql_abfragen="$(jq -n --arg quelle "${query_quelle}" --arg modus "${query_modus}" --arg grund "cursor_dedupe_keine_neuen_daten" '[{typ:"diagnose",diagnose_status:$grund,fehler_text:"Es wurden nur bereits bekannte Query-Eintraege gefunden.",quelle:$quelle,query_quelle:$quelle,query_modus:$modus}]')"
		fi
		local fingerprints_json
		fingerprints_json="$(echo "${dedupe_json}" | jq -c '.alle' 2>/dev/null || echo '[]')"
		nodepilot_mariadb_query_cursor_merken "${query_quelle}" "${query_marker}" "${fingerprints_json}"
		nodepilot_log "info" "mariadb_querylog: quelle=${query_quelle} modus=${query_modus} roh_eingesammelt=${query_total_roh} nach_filterung=${query_nach_filterung} gespeichert=${dedupe_neu} verworfen=${verworfen_gesamt} verworfen_gruende=$(echo "${verworfen_gruende}" | jq -c . 2>/dev/null || echo '{}')"
		if [ "$(echo "${gruende_hoeher}" | jq -r 'length' 2>/dev/null || echo 0)" -gt 0 ]; then
			nodepilot_log "info" "mariadb_querylog: hoeher-priorisierte Quellen nicht genutzt: $(echo "${gruende_hoeher}" | jq -c . 2>/dev/null || echo '[]')"
		fi
		echo "${sql_abfragen}"
		return
	fi

	if [ "${query_diag}" = "[]" ]; then
		query_diag="$(jq -n --argjson gruende "${gruende_hoeher}" '[{typ:"diagnose",diagnose_status:"keine_query_quelle_verfuegbar",fehler_text:"Keine Query-Quelle lieferte Daten.",quelle:"agent",query_quelle:"diagnose",query_modus:"diagnose",query_json:{gruende:$gruende}}]')"
	fi
	nodepilot_log "warn" "mariadb_querylog: nur Diagnoseobjekte; gruende=$(echo "${gruende_hoeher}" | jq -c . 2>/dev/null || echo '[]')"
	echo "${query_diag}"
}

nodepilot_mariadb_json()
{
	local mariadb_cli
	mariadb_cli="$(nodepilot_mariadb_cli_ermitteln || true)"
	if [ -z "${mariadb_cli}" ]; then
		nodepilot_log "warn" "mariadb_json: Kein DB-Client gefunden (mariadb/mysql)."
		echo '{"verfuegbar":false,"erreichbar":false,"status":"fehler","diagnose_status":"kein_db_client","fehler_text":"Kein MariaDB/MySQL-Client gefunden (mariadb/mysql fehlt).","sql_abfragen":[{"typ":"diagnose","diagnose_status":"kein_db_client","fehler_text":"Kein MariaDB/MySQL-Client installiert.","quelle":"agent"}]}'
		return
	fi

	local status_raw variablen_raw globals_raw status_err_datei vars_err_datei global_err_datei
	status_err_datei="$(mktemp)"
	vars_err_datei="$(mktemp)"
	global_err_datei="$(mktemp)"

	status_raw="$("${mariadb_cli}" --connect-timeout=4 -NBe "SHOW GLOBAL STATUS" 2>"${status_err_datei}" || true)"
	variablen_raw="$("${mariadb_cli}" --connect-timeout=4 -NBe "SHOW GLOBAL VARIABLES" 2>"${vars_err_datei}" || true)"
	globals_raw="$("${mariadb_cli}" --connect-timeout=4 -NBe "SELECT @@version, @@version_comment, @@read_only, @@global.read_only, @@hostname, @@port, @@datadir, @@socket" 2>"${global_err_datei}" || true)"

	local fehlertext_gesamt diagnose_status="ok" fehler_text="" status_flag="ok" verfuegbar_json="true" erreichbar_json="true"
	fehlertext_gesamt="$(printf '%s
%s
%s' "$(cat "${status_err_datei}" 2>/dev/null || true)" "$(cat "${vars_err_datei}" 2>/dev/null || true)" "$(cat "${global_err_datei}" 2>/dev/null || true)" | awk 'NF>0' | head -n 8 | paste -sd ' | ' -)"

	if [ -z "${status_raw}" ] && [ -z "${variablen_raw}" ] && [ -z "${globals_raw}" ]; then
		verfuegbar_json="false"
		erreichbar_json="false"
		status_flag="fehler"
		if echo "${fehlertext_gesamt}" | grep -Eiq "Access denied|denied"; then
			diagnose_status="auth_problem"
			fehler_text="MariaDB-Verbindung fehlgeschlagen: Authentifizierung fehlgeschlagen."
		elif echo "${fehlertext_gesamt}" | grep -Eiq "Can't connect|No such file|refused|Unknown MySQL server host|timed out"; then
			diagnose_status="nicht_erreichbar"
			fehler_text="MariaDB-Verbindung fehlgeschlagen: Server nicht erreichbar."
		else
			diagnose_status="abfrage_fehler"
			fehler_text="MariaDB-Abfrage fehlgeschlagen."
		fi
		nodepilot_log "warn" "mariadb_json: ${diagnose_status}; ${fehler_text}; details=${fehlertext_gesamt}"
		echo "$(jq -n --arg diagnose_status "${diagnose_status}" --arg fehler_text "${fehler_text}" --arg details "${fehlertext_gesamt}" '{verfuegbar:false,erreichbar:false,status:"fehler",diagnose_status:$diagnose_status,fehler_text:($fehler_text + (if $details=="" then "" else " | " + $details end)),sql_abfragen:[{typ:"diagnose",diagnose_status:$diagnose_status,fehler_text:($fehler_text + (if $details=="" then "" else " | " + $details end)),quelle:"agent"}]}' 2>/dev/null || echo '{"verfuegbar":false,"erreichbar":false,"status":"fehler","diagnose_status":"json_fehler","fehler_text":"MariaDB-Fehlerpayload konnte nicht erstellt werden.","sql_abfragen":[{"typ":"diagnose","diagnose_status":"json_fehler","fehler_text":"MariaDB-Fehlerpayload konnte nicht erstellt werden.","quelle":"agent"}]}')"
		rm -f "${status_err_datei}" "${vars_err_datei}" "${global_err_datei}" 2>/dev/null || true
		return
	fi

	if [ -z "${status_raw}" ] || [ -z "${variablen_raw}" ] || [ -z "${globals_raw}" ]; then
		diagnose_status="teilweise_unvollstaendig"
		status_flag="eingeschraenkt"
		fehler_text="MariaDB-Status nur teilweise erhoben."
		nodepilot_log "warn" "mariadb_json: Teilpayload unvollstaendig; details=${fehlertext_gesamt}"
	fi

	rm -f "${status_err_datei}" "${vars_err_datei}" "${global_err_datei}" 2>/dev/null || true

	local status_json variablen_json
	status_json="$(echo "${status_raw}" | awk -F'	' 'NF>=2 {print $1"	"$2}' | jq -R -s 'split("\n")|map(select(length>0))|map(split("\t"))|map({(.[0]):(.[1])})|add' 2>/dev/null || echo '{}')"
	variablen_json="$(echo "${variablen_raw}" | awk -F'	' 'NF>=2 {print $1"	"$2}' | jq -R -s 'split("\n")|map(select(length>0))|map(split("\t"))|map({(.[0]):(.[1])})|add' 2>/dev/null || echo '{}')"

	if ! echo "${status_json}" | jq -e . >/dev/null 2>&1; then
		nodepilot_parser_fehler_loggen "mariadb_status_json" "${status_raw}"
		status_json='{}'
		diagnose_status="teilweise_unvollstaendig"
		status_flag="eingeschraenkt"
		fehler_text="MariaDB-Status konnte nicht vollstaendig geparst werden."
	fi

	if ! echo "${variablen_json}" | jq -e . >/dev/null 2>&1; then
		nodepilot_parser_fehler_loggen "mariadb_variablen_json" "${variablen_raw}"
		variablen_json='{}'
		diagnose_status="teilweise_unvollstaendig"
		status_flag="eingeschraenkt"
		fehler_text="MariaDB-Variablen konnten nicht vollstaendig geparst werden."
	fi

	local version version_comment read_only super_read_only hostname port datadir socket
	version="$(echo "${globals_raw}" | awk -F'	' 'NR==1{print $1}')"
	version_comment="$(echo "${globals_raw}" | awk -F'	' 'NR==1{print $2}')"
	read_only="$(echo "${variablen_json}" | jq -r '.read_only // .["@@read_only"] // ""' 2>/dev/null || echo "")"
	super_read_only="$(echo "${variablen_json}" | jq -r '.super_read_only // ""' 2>/dev/null || echo "")"
	hostname="$(echo "${globals_raw}" | awk -F'	' 'NR==1{print $5}')"
	port="$(echo "${globals_raw}" | awk -F'	' 'NR==1{print $6}')"
	datadir="$(echo "${globals_raw}" | awk -F'	' 'NR==1{print $7}')"
	socket="$(echo "${globals_raw}" | awk -F'	' 'NR==1{print $8}')"

	local flavor="mysql"
	if echo "${version_comment} ${version}" | grep -Eiq "mariadb"; then
		flavor="mariadb"
	fi

	local queries_counter slow_counter bytes_received bytes_sent threads_running threads_connected
	queries_counter="$(nodepilot_mariadb_status_oder_null "${status_json}" "Queries")"
	slow_counter="$(nodepilot_mariadb_status_oder_null "${status_json}" "Slow_queries")"
	bytes_received="$(nodepilot_mariadb_status_oder_null "${status_json}" "Bytes_received")"
	bytes_sent="$(nodepilot_mariadb_status_oder_null "${status_json}" "Bytes_sent")"
	threads_running="$(nodepilot_mariadb_status_oder_null "${status_json}" "Threads_running")"
	threads_connected="$(nodepilot_mariadb_status_oder_null "${status_json}" "Threads_connected")"

	local prev_queries prev_slow prev_recv prev_sent prev_time now delta_t
	prev_queries="$(nodepilot_agent_state_wert_lesen '.rate_vorwerte.mariadb.queries_counter')"
	prev_slow="$(nodepilot_agent_state_wert_lesen '.rate_vorwerte.mariadb.slow_counter')"
	prev_recv="$(nodepilot_agent_state_wert_lesen '.rate_vorwerte.mariadb.bytes_received')"
	prev_sent="$(nodepilot_agent_state_wert_lesen '.rate_vorwerte.mariadb.bytes_sent')"
	prev_time="$(nodepilot_agent_state_wert_lesen '.rate_vorwerte.mariadb.zeit_unix')"
	now="$(date +%s)"

	local queries_counter_json slow_counter_json bytes_received_json bytes_sent_json
	queries_counter_json="$(nodepilot_zahl_oder_fallback "${queries_counter}" "0" "mariadb/queries_counter")"
	slow_counter_json="$(nodepilot_zahl_oder_fallback "${slow_counter}" "0" "mariadb/slow_counter")"
	bytes_received_json="$(nodepilot_zahl_oder_fallback "${bytes_received}" "0" "mariadb/bytes_received")"
	bytes_sent_json="$(nodepilot_zahl_oder_fallback "${bytes_sent}" "0" "mariadb/bytes_sent")"

	NODEPILOT_AGENT_STATE_JSON="$(echo "${NODEPILOT_AGENT_STATE_JSON}" | jq -c --argjson queries "${queries_counter_json}" --argjson slow "${slow_counter_json}" --argjson recv "${bytes_received_json}" --argjson sent "${bytes_sent_json}" --argjson zeit "${now}" '.rate_vorwerte = (.rate_vorwerte // {}) | .rate_vorwerte.mariadb = {queries_counter:$queries,slow_counter:$slow,bytes_received:$recv,bytes_sent:$sent,zeit_unix:$zeit}' 2>/dev/null || echo "${NODEPILOT_AGENT_STATE_JSON}")"

	local qps_json='null' slow_min_json='null' bytes_recv_ps='null' bytes_sent_ps='null' threads_running_ratio='null'
	if [[ "${prev_queries}" =~ ^[0-9]+$ ]] && [[ "${prev_slow}" =~ ^[0-9]+$ ]] && [[ "${prev_recv}" =~ ^[0-9]+$ ]] && [[ "${prev_sent}" =~ ^[0-9]+$ ]] && [[ "${prev_time}" =~ ^[0-9]+$ ]]; then
		delta_t="$((now - prev_time))"
		if [ "${delta_t}" -gt 0 ] && [[ "${queries_counter_json}" =~ ^[0-9]+$ ]] && [[ "${slow_counter_json}" =~ ^[0-9]+$ ]] && [[ "${bytes_received_json}" =~ ^[0-9]+$ ]] && [[ "${bytes_sent_json}" =~ ^[0-9]+$ ]]; then
			if [ "${queries_counter_json}" -ge "${prev_queries}" ] && [ "${slow_counter_json}" -ge "${prev_slow}" ]; then
				qps_json="$(awk -v d="$((queries_counter_json - prev_queries))" -v t="${delta_t}" 'BEGIN{printf "%.4f", d/t}')"
				slow_min_json="$(awk -v d="$((slow_counter_json - prev_slow))" -v t="${delta_t}" 'BEGIN{printf "%.4f", (d*60)/t}')"
			fi
			if [ "${bytes_received_json}" -ge "${prev_recv}" ] && [ "${bytes_sent_json}" -ge "${prev_sent}" ]; then
				bytes_recv_ps="$(awk -v d="$((bytes_received_json - prev_recv))" -v t="${delta_t}" 'BEGIN{printf "%.4f", d/t}')"
				bytes_sent_ps="$(awk -v d="$((bytes_sent_json - prev_sent))" -v t="${delta_t}" 'BEGIN{printf "%.4f", d/t}')"
			fi
		fi
	fi

	if [[ "${threads_connected}" =~ ^[0-9]+$ ]] && [[ "${threads_running}" =~ ^[0-9]+$ ]] && [ "${threads_connected}" -gt 0 ]; then
		threads_running_ratio="$(awk -v r="${threads_running}" -v c="${threads_connected}" 'BEGIN{printf "%.4f", r/c}')"
	fi

	local sql_abfragen_json
	sql_abfragen_json="$(nodepilot_mariadb_sql_abfragen_json "${mariadb_cli}" "${status_json}" "${variablen_json}")"
	if ! echo "${sql_abfragen_json}" | jq -e . >/dev/null 2>&1; then
		nodepilot_parser_fehler_loggen "mariadb_sql_abfragen_json" "${sql_abfragen_json}"
		sql_abfragen_json='[{"typ":"diagnose","diagnose_status":"json_fehler","fehler_text":"Query-Details konnten nicht geparst werden.","quelle":"agent"}]'
	fi

	local sql_abfragen_original_anzahl sql_abfragen_uebernommen_anzahl
	local mariadb_payload_gekuerzt mariadb_payload_gekuerzt_grund
	local mariadb_json_bau_status mariadb_json_bau_fehler
	sql_abfragen_original_anzahl="$(echo "${sql_abfragen_json}" | jq -r 'length' 2>/dev/null || echo "0")"
	sql_abfragen_uebernommen_anzahl="${sql_abfragen_original_anzahl}"
	mariadb_payload_gekuerzt=0
	mariadb_payload_gekuerzt_grund=""
	mariadb_json_bau_status="ok"
	mariadb_json_bau_fehler=""

	local sql_abfragen_limitiert_json
	sql_abfragen_limitiert_json="$(echo "${sql_abfragen_json}" | jq -c '
		map(
			. as $q
			| .fehler_text = (($q.fehler_text // "" | tostring) | if length > 2048 then .[:2048] else . end)
			| .query_sql_roh = (($q.query_sql_roh // "" | tostring) | if length > 4096 then .[:4096] else . end)
			| .query_sql_normalisiert = (($q.query_sql_normalisiert // "" | tostring) | if length > 4096 then .[:4096] else . end)
			| .query_digest_text = (if ($q.query_digest_text // null) == null then null else (($q.query_digest_text | tostring) | if length > 2048 then .[:2048] else . end) end)
			| .query_json = (
				if ($q.query_json // null) == null then null
				elif (($q.query_json | tostring | length) > 8192) then {"gekuerzt":true,"grund":"feld_limit","vorschau":(($q.query_json | tostring)[:2048])}
				else $q.query_json
				end
			)
		)
		| sort_by((if ((.diagnose_status // "ok") != "ok" or (.fehler_text // "") != "") then 1 else 0 end), (.query_dauer_ms // 0))
		| reverse
		| .[:100]
	' 2>/dev/null || echo "${sql_abfragen_json}")"
	sql_abfragen_uebernommen_anzahl="$(echo "${sql_abfragen_limitiert_json}" | jq -r 'length' 2>/dev/null || echo "${sql_abfragen_original_anzahl}")"
	if [ "${sql_abfragen_uebernommen_anzahl}" -lt "${sql_abfragen_original_anzahl}" ]; then
		mariadb_payload_gekuerzt=1
		mariadb_payload_gekuerzt_grund="query_limit"
	fi
	if [ "${mariadb_payload_gekuerzt}" -eq 0 ] && ! diff -q <(echo "${sql_abfragen_json}") <(echo "${sql_abfragen_limitiert_json}") >/dev/null 2>&1; then
		mariadb_payload_gekuerzt=1
		mariadb_payload_gekuerzt_grund="feld_limit"
	fi
	if [ "${mariadb_payload_gekuerzt}" -eq 1 ]; then
		mariadb_json_bau_status="warnung"
	fi
	sql_abfragen_json="${sql_abfragen_limitiert_json}"

	local mariadb_json mariadb_json_jq_stderr_datei
	mariadb_json_jq_stderr_datei="$(mktemp)"
	local tmp_flavor tmp_version tmp_version_comment tmp_read_only tmp_super_read_only tmp_hostname tmp_port tmp_datadir tmp_socket tmp_status_flag tmp_diagnose_status tmp_fehler_text
	local tmp_verfuegbar tmp_erreichbar tmp_status tmp_vars tmp_qps tmp_slow_min tmp_recv_ps tmp_sent_ps tmp_threads_ratio tmp_sql_abfragen
	tmp_flavor="$(mktemp)"; printf '%s' "${flavor}" > "${tmp_flavor}"
	tmp_version="$(mktemp)"; printf '%s' "${version}" > "${tmp_version}"
	tmp_version_comment="$(mktemp)"; printf '%s' "${version_comment}" > "${tmp_version_comment}"
	tmp_read_only="$(mktemp)"; printf '%s' "${read_only}" > "${tmp_read_only}"
	tmp_super_read_only="$(mktemp)"; printf '%s' "${super_read_only}" > "${tmp_super_read_only}"
	tmp_hostname="$(mktemp)"; printf '%s' "${hostname}" > "${tmp_hostname}"
	tmp_port="$(mktemp)"; printf '%s' "${port}" > "${tmp_port}"
	tmp_datadir="$(mktemp)"; printf '%s' "${datadir}" > "${tmp_datadir}"
	tmp_socket="$(mktemp)"; printf '%s' "${socket}" > "${tmp_socket}"
	tmp_status_flag="$(mktemp)"; printf '%s' "${status_flag}" > "${tmp_status_flag}"
	tmp_diagnose_status="$(mktemp)"; printf '%s' "${diagnose_status}" > "${tmp_diagnose_status}"
	tmp_fehler_text="$(mktemp)"; printf '%s' "${fehler_text}" > "${tmp_fehler_text}"
	tmp_verfuegbar="$(mktemp)"; echo "${verfuegbar_json}" | jq -c . > "${tmp_verfuegbar}" 2>/dev/null || echo 'false' > "${tmp_verfuegbar}"
	tmp_erreichbar="$(mktemp)"; echo "${erreichbar_json}" | jq -c . > "${tmp_erreichbar}" 2>/dev/null || echo 'false' > "${tmp_erreichbar}"
	tmp_status="$(mktemp)"; echo "${status_json}" | jq -c . > "${tmp_status}" 2>/dev/null || echo '{}' > "${tmp_status}"
	tmp_vars="$(mktemp)"; echo "${variablen_json}" | jq -c . > "${tmp_vars}" 2>/dev/null || echo '{}' > "${tmp_vars}"
	tmp_qps="$(mktemp)"; echo "${qps_json}" | jq -c . > "${tmp_qps}" 2>/dev/null || echo 'null' > "${tmp_qps}"
	tmp_slow_min="$(mktemp)"; echo "${slow_min_json}" | jq -c . > "${tmp_slow_min}" 2>/dev/null || echo 'null' > "${tmp_slow_min}"
	tmp_recv_ps="$(mktemp)"; echo "${bytes_recv_ps}" | jq -c . > "${tmp_recv_ps}" 2>/dev/null || echo 'null' > "${tmp_recv_ps}"
	tmp_sent_ps="$(mktemp)"; echo "${bytes_sent_ps}" | jq -c . > "${tmp_sent_ps}" 2>/dev/null || echo 'null' > "${tmp_sent_ps}"
	tmp_threads_ratio="$(mktemp)"; echo "${threads_running_ratio}" | jq -c . > "${tmp_threads_ratio}" 2>/dev/null || echo 'null' > "${tmp_threads_ratio}"
	tmp_sql_abfragen="$(mktemp)"; echo "${sql_abfragen_json}" | jq -c . > "${tmp_sql_abfragen}" 2>/dev/null || echo '[]' > "${tmp_sql_abfragen}"
	mariadb_json="$(jq -n --rawfile flavor "${tmp_flavor}" --rawfile version "${tmp_version}" --rawfile version_comment "${tmp_version_comment}" --rawfile read_only "${tmp_read_only}" --rawfile super_read_only "${tmp_super_read_only}" --rawfile hostname "${tmp_hostname}" --rawfile port "${tmp_port}" --rawfile datadir "${tmp_datadir}" --rawfile socket "${tmp_socket}" --rawfile status_flag "${tmp_status_flag}" --rawfile diagnose_status "${tmp_diagnose_status}" --rawfile fehler_text "${tmp_fehler_text}" --slurpfile verfuegbar "${tmp_verfuegbar}" --slurpfile erreichbar "${tmp_erreichbar}" --slurpfile status "${tmp_status}" --slurpfile vars "${tmp_vars}" --slurpfile qps "${tmp_qps}" --slurpfile slow_min "${tmp_slow_min}" --slurpfile recv_ps "${tmp_recv_ps}" --slurpfile sent_ps "${tmp_sent_ps}" --slurpfile threads_ratio "${tmp_threads_ratio}" --slurpfile sql_abfragen "${tmp_sql_abfragen}" --argjson payload_gekuerzt "${mariadb_payload_gekuerzt}" --arg payload_gekuerzt_grund "${mariadb_payload_gekuerzt_grund}" --argjson sql_abfragen_original_anzahl "${sql_abfragen_original_anzahl}" --argjson sql_abfragen_uebernommen_anzahl "${sql_abfragen_uebernommen_anzahl}" --arg json_bau_status "${mariadb_json_bau_status}" --arg json_bau_fehler "${mariadb_json_bau_fehler}" '
		def n($v): ($v | if type=="string" and test("^-?[0-9]+([.][0-9]+)?$") then tonumber elif (type=="number") then . else null end);
		def i($obj;$key): n($obj[$key]);
		($verfuegbar[0] // false) as $verfuegbar
		| ($erreichbar[0] // false) as $erreichbar
		| ($status[0] // {}) as $status
		| ($vars[0] // {}) as $vars
		| ($qps[0] // null) as $qps
		| ($slow_min[0] // null) as $slow_min
		| ($recv_ps[0] // null) as $recv_ps
		| ($sent_ps[0] // null) as $sent_ps
		| ($threads_ratio[0] // null) as $threads_ratio
		| ($sql_abfragen[0] // []) as $sql_abfragen
		| {
			verfuegbar:$verfuegbar,
			typ:$flavor,
			status:$status_flag,
			fehler_text:($fehler_text | tostring | if length > 2048 then .[:2048] else . end),
			erreichbar:$erreichbar,
			diagnose_status:$diagnose_status,
			servertyp:$flavor,
			version:$version,
			version_comment:$version_comment,
			read_only:$read_only,
			super_read_only:$super_read_only,
			hostname:$hostname,
			port:(if ($port|test("^[0-9]+$")) then ($port|tonumber) else null end),
			datadir:$datadir,
			socket:$socket,
			uptime_sekunden:i($status;"Uptime"),
			threads_connected:i($status;"Threads_connected"),
			threads_running:i($status;"Threads_running"),
			max_used_connections:i($status;"Max_used_connections"),
			aborted_connects:i($status;"Aborted_connects"),
			aborted_clients:i($status;"Aborted_clients"),
			connections:i($status;"Connections"),
			questions:i($status;"Questions"),
			queries:i($status;"Queries"),
			slow_queries:i($status;"Slow_queries"),
			bytes_received:i($status;"Bytes_received"),
			bytes_sent:i($status;"Bytes_sent"),
			grundzustand:{erreichbar:$erreichbar,uptime:i($status;"Uptime"),threads_connected:i($status;"Threads_connected"),threads_running:i($status;"Threads_running"),max_used_connections:i($status;"Max_used_connections"),aborted_connects:i($status;"Aborted_connects"),aborted_clients:i($status;"Aborted_clients"),connections:i($status;"Connections"),questions:i($status;"Questions"),queries:i($status;"Queries"),slow_queries:i($status;"Slow_queries"),bytes_received:i($status;"Bytes_received"),bytes_sent:i($status;"Bytes_sent")},
			workload:{com_select:i($status;"Com_select"),com_insert:i($status;"Com_insert"),com_update:i($status;"Com_update"),com_delete:i($status;"Com_delete"),com_replace:i($status;"Com_replace"),com_commit:i($status;"Com_commit"),com_rollback:i($status;"Com_rollback"),created_tmp_tables:i($status;"Created_tmp_tables"),created_tmp_disk_tables:i($status;"Created_tmp_disk_tables"),created_tmp_files:i($status;"Created_tmp_files"),sort_merge_passes:i($status;"Sort_merge_passes"),sort_rows:i($status;"Sort_rows"),sort_scan:i($status;"Sort_scan"),select_full_join:i($status;"Select_full_join"),select_full_range_join:i($status;"Select_full_range_join"),select_range:i($status;"Select_range"),select_range_check:i($status;"Select_range_check"),select_scan:i($status;"Select_scan"),handler_read_first:i($status;"Handler_read_first"),handler_read_key:i($status;"Handler_read_key"),handler_read_next:i($status;"Handler_read_next"),handler_read_prev:i($status;"Handler_read_prev"),handler_read_rnd:i($status;"Handler_read_rnd"),handler_read_rnd_next:i($status;"Handler_read_rnd_next"),innodb_rows_read:i($status;"Innodb_rows_read"),innodb_rows_inserted:i($status;"Innodb_rows_inserted"),innodb_rows_updated:i($status;"Innodb_rows_updated"),innodb_rows_deleted:i($status;"Innodb_rows_deleted")},
			innodb:{innodb_buffer_pool_read_requests:i($status;"Innodb_buffer_pool_read_requests"),innodb_buffer_pool_reads:i($status;"Innodb_buffer_pool_reads"),innodb_buffer_pool_pages_data:i($status;"Innodb_buffer_pool_pages_data"),innodb_buffer_pool_pages_dirty:i($status;"Innodb_buffer_pool_pages_dirty"),innodb_buffer_pool_pages_free:i($status;"Innodb_buffer_pool_pages_free"),innodb_buffer_pool_bytes_data:i($status;"Innodb_buffer_pool_bytes_data"),innodb_buffer_pool_bytes_dirty:i($status;"Innodb_buffer_pool_bytes_dirty"),innodb_data_reads:i($status;"Innodb_data_reads"),innodb_data_writes:i($status;"Innodb_data_writes"),innodb_data_read:i($status;"Innodb_data_read"),innodb_data_written:i($status;"Innodb_data_written"),innodb_log_waits:i($status;"Innodb_log_waits"),innodb_log_writes:i($status;"Innodb_log_writes"),innodb_os_log_written:i($status;"Innodb_os_log_written"),innodb_row_lock_waits:i($status;"Innodb_row_lock_waits"),innodb_row_lock_time:i($status;"Innodb_row_lock_time"),innodb_row_lock_current_waits:i($status;"Innodb_row_lock_current_waits"),innodb_history_list_length:i($status;"Innodb_history_list_length")},
			replikation_galera:{wsrep_on:($vars["wsrep_on"] // null),wsrep_cluster_status:($status["wsrep_cluster_status"] // null),wsrep_cluster_size:i($status;"wsrep_cluster_size"),wsrep_local_state_comment:($status["wsrep_local_state_comment"] // null),wsrep_ready:($status["wsrep_ready"] // null),wsrep_connected:($status["wsrep_connected"] // null),wsrep_flow_control_paused:n($status["wsrep_flow_control_paused"]),wsrep_local_recv_queue:n($status["wsrep_local_recv_queue"]),wsrep_local_send_queue:n($status["wsrep_local_send_queue"]),wsrep_received:i($status;"wsrep_received"),wsrep_replicated:i($status;"wsrep_replicated"),wsrep_cert_deps_distance:n($status["wsrep_cert_deps_distance"]),wsrep_apply_oooe:n($status["wsrep_apply_oooe"]),wsrep_commit_oooe:n($status["wsrep_commit_oooe"]),wsrep_local_bf_aborts:i($status;"wsrep_local_bf_aborts"),wsrep_local_cert_failures:i($status;"wsrep_local_cert_failures")},
			ableitungen:{queries_pro_sekunde:$qps,slow_queries_pro_minute:$slow_min,bytes_received_pro_sekunde:$recv_ps,bytes_sent_pro_sekunde:$sent_ps,threads_running_ratio:$threads_ratio,tmp_disk_tables_anteil:(if i($status;"Created_tmp_tables") != null and i($status;"Created_tmp_tables") > 0 and i($status;"Created_tmp_disk_tables") != null then (i($status;"Created_tmp_disk_tables") / i($status;"Created_tmp_tables")) else null end),buffer_pool_hit_rate:(if i($status;"Innodb_buffer_pool_read_requests") != null and i($status;"Innodb_buffer_pool_read_requests") > 0 and i($status;"Innodb_buffer_pool_reads") != null then (1 - (i($status;"Innodb_buffer_pool_reads") / i($status;"Innodb_buffer_pool_read_requests"))) else null end),diagnose_unvollstaendig:(if $status_flag=="eingeschraenkt" then true else false end)},
			queries_pro_sekunde:$qps,
			slow_queries_pro_minute:$slow_min,
			innodb_buffer_pool_usage_prozent:(if i($status;"Innodb_buffer_pool_pages_data") != null and i($status;"Innodb_buffer_pool_pages_free") != null and (i($status;"Innodb_buffer_pool_pages_data")+i($status;"Innodb_buffer_pool_pages_free")) > 0 then ((i($status;"Innodb_buffer_pool_pages_data")*100)/(i($status;"Innodb_buffer_pool_pages_data")+i($status;"Innodb_buffer_pool_pages_free"))) else null end),
			query_quelle:(if ($sql_abfragen|length)>0 then ($sql_abfragen[0].query_quelle // "diagnose") else "diagnose" end),
			query_modus:(if ($sql_abfragen|length)>0 then ($sql_abfragen[0].query_modus // "diagnose") else "diagnose" end),
			mariadb_payload_gekuerzt:($payload_gekuerzt|if .>0 then 1 else 0 end),
			mariadb_payload_gekuerzt_grund:(if $payload_gekuerzt_grund == "" then null else $payload_gekuerzt_grund end),
			mariadb_sql_abfragen_original_anzahl:$sql_abfragen_original_anzahl,
			mariadb_sql_abfragen_uebernommen_anzahl:$sql_abfragen_uebernommen_anzahl,
			mariadb_json_bau_status:$json_bau_status,
			mariadb_json_bau_fehler:(if $json_bau_fehler == "" then null else $json_bau_fehler end),
			sql_abfragen:$sql_abfragen
		}
	' 2>"${mariadb_json_jq_stderr_datei}" || echo '')"

	if [ -z "${mariadb_json}" ]; then
		local mariadb_json_jq_fehler_detail detail_text_safe json_bau_fehler
		mariadb_json_jq_fehler_detail="$(tr '\r\n' '  ' < "${mariadb_json_jq_stderr_datei}" 2>/dev/null | sed 's/[[:space:]]\+/ /g' | cut -c1-500)"
		if [ -z "${mariadb_json_jq_fehler_detail}" ]; then
			mariadb_json_jq_fehler_detail="keine_jq_stderr_details"
		fi
		json_bau_fehler=""
		if echo "${mariadb_json_jq_fehler_detail}" | grep -Eiq 'argumentliste ist zu lang|argument list too long'; then
			json_bau_fehler="argumentliste_zu_lang"
		fi
		detail_text_safe="${fehler_text}"
		if [ -z "${detail_text_safe}" ]; then
			detail_text_safe="${mariadb_json_jq_fehler_detail}"
		else
			detail_text_safe="${detail_text_safe}; jq=${mariadb_json_jq_fehler_detail}"
		fi
		nodepilot_log "error" "mariadb_json: jq-Fehler beim JSON-Bau; Rohursache diagnose_status=${diagnose_status} detail=${detail_text_safe}."
		echo "$(jq -n --arg diagnose_status "json_fehler" --arg fehler_text "MariaDB-JSON-Bau fehlgeschlagen." --arg detail_status "${diagnose_status}" --arg detail_text "${detail_text_safe}" --arg json_bau_fehler "${json_bau_fehler}" '{verfuegbar:false,erreichbar:false,status:"fehler",diagnose_status:$diagnose_status,fehler_text:($fehler_text + " Vorstatus=" + $detail_status + "; Detail=" + (if ($detail_text // "")=="" then "unbekannt" else $detail_text end)),mariadb_payload_gekuerzt:1,mariadb_payload_gekuerzt_grund:"arg_limit_vermieden",mariadb_sql_abfragen_original_anzahl:0,mariadb_sql_abfragen_uebernommen_anzahl:0,mariadb_json_bau_status:"fehler",mariadb_json_bau_fehler:(if $json_bau_fehler=="" then null else $json_bau_fehler end),sql_abfragen:[{typ:"diagnose",diagnose_status:"json_fehler",fehler_text:($fehler_text + " Vorstatus=" + $detail_status + "; Detail=" + (if ($detail_text // "")=="" then "unbekannt" else $detail_text end)),quelle:"agent"}]}' 2>/dev/null || echo '{"verfuegbar":false,"erreichbar":false,"status":"fehler","diagnose_status":"json_fehler","fehler_text":"MariaDB-JSON-Bau fehlgeschlagen.","mariadb_payload_gekuerzt":1,"mariadb_payload_gekuerzt_grund":"arg_limit_vermieden","mariadb_sql_abfragen_original_anzahl":0,"mariadb_sql_abfragen_uebernommen_anzahl":0,"mariadb_json_bau_status":"fehler","mariadb_json_bau_fehler":"argumentliste_zu_lang","sql_abfragen":[{"typ":"diagnose","diagnose_status":"json_fehler","fehler_text":"MariaDB-JSON-Bau fehlgeschlagen.","quelle":"agent"}]}')"
		rm -f "${mariadb_json_jq_stderr_datei}" "${tmp_flavor}" "${tmp_version}" "${tmp_version_comment}" "${tmp_read_only}" "${tmp_super_read_only}" "${tmp_hostname}" "${tmp_port}" "${tmp_datadir}" "${tmp_socket}" "${tmp_status_flag}" "${tmp_diagnose_status}" "${tmp_fehler_text}" "${tmp_verfuegbar}" "${tmp_erreichbar}" "${tmp_status}" "${tmp_vars}" "${tmp_qps}" "${tmp_slow_min}" "${tmp_recv_ps}" "${tmp_sent_ps}" "${tmp_threads_ratio}" "${tmp_sql_abfragen}" 2>/dev/null || true
		return
	fi
	rm -f "${mariadb_json_jq_stderr_datei}" "${tmp_flavor}" "${tmp_version}" "${tmp_version_comment}" "${tmp_read_only}" "${tmp_super_read_only}" "${tmp_hostname}" "${tmp_port}" "${tmp_datadir}" "${tmp_socket}" "${tmp_status_flag}" "${tmp_diagnose_status}" "${tmp_fehler_text}" "${tmp_verfuegbar}" "${tmp_erreichbar}" "${tmp_status}" "${tmp_vars}" "${tmp_qps}" "${tmp_slow_min}" "${tmp_recv_ps}" "${tmp_sent_ps}" "${tmp_threads_ratio}" "${tmp_sql_abfragen}" 2>/dev/null || true

	mariadb_json="$(nodepilot_mariadb_live_felder_hinzufuegen "${mariadb_json}" "${mariadb_cli}")"

	echo "${mariadb_json}"
}

nodepilot_mariadb_live_query_kennzahlen_json()
{
	local mariadb_cli="$1"
	local tmp_err processlist_raw processlist_err
	local kennzahlen_standard
	kennzahlen_standard='{"mariadb_aktuelle_queries":null,"mariadb_aktuelle_queries_select":null,"mariadb_aktuelle_queries_insert":null,"mariadb_aktuelle_queries_update":null,"mariadb_aktuelle_queries_delete":null,"mariadb_aktuelle_queries_sonstige":null,"mariadb_aktuelle_queries_langlaufend":null,"mariadb_aktuelle_sleep_connections":null}'
	tmp_err="$(mktemp "${NODEPILOT_TMP_DIR}/mariadb-processlist.XXXXXX.err" 2>/dev/null || true)"
	if [ -z "${tmp_err}" ]; then
		echo "${kennzahlen_standard}"
		return
	fi
	processlist_raw="$("${mariadb_cli}" --connect-timeout=4 -NBe "SELECT ID,USER,COMMAND,TIME,LEFT(INFO,4000) FROM INFORMATION_SCHEMA.PROCESSLIST WHERE ID <> CONNECTION_ID() ORDER BY TIME DESC LIMIT 120" 2>"${tmp_err}" || true)"
	processlist_err="$(cat "${tmp_err}" 2>/dev/null || true)"
	rm -f "${tmp_err}" 2>/dev/null || true

	if [ -n "${processlist_err}" ]; then
		nodepilot_log "warn" "mariadb_live_query_kennzahlen: processlist_aggregation_fehler detail=$(echo "${processlist_err}" | tr '\n\r\t' ' ' | sed 's/[[:space:]]\+/ /g' | cut -c1-220)"
		echo "${kennzahlen_standard}"
		return
	fi

	if [ -z "${processlist_raw}" ]; then
		echo '{"mariadb_aktuelle_queries":0,"mariadb_aktuelle_queries_select":0,"mariadb_aktuelle_queries_insert":0,"mariadb_aktuelle_queries_update":0,"mariadb_aktuelle_queries_delete":0,"mariadb_aktuelle_queries_sonstige":0,"mariadb_aktuelle_queries_langlaufend":0,"mariadb_aktuelle_sleep_connections":0}'
		return
	fi

	echo "${processlist_raw}" | jq -R -s '
		[
			split("\n")[]
			| select(length > 0)
			| split("\t")
			| {
				command_type:((.[2] // "") | tostring | ascii_downcase),
				laufzeit:(((.[3] // "0") | tonumber?) // 0),
				sql_text:((.[4] // "") | tostring | ascii_upcase)
			}
		] as $rows
		| ($rows | map(select(.command_type == "sleep")) | length) as $sleep_count
		| ($rows | map(select(.command_type != "sleep" and .command_type != "daemon" and .command_type != "binlog dump"))) as $aktive
		| ($aktive | map(select(.sql_text | startswith("SELECT"))) | length) as $select_count
		| ($aktive | map(select(.sql_text | startswith("INSERT"))) | length) as $insert_count
		| ($aktive | map(select(.sql_text | startswith("UPDATE"))) | length) as $update_count
		| ($aktive | map(select(.sql_text | startswith("DELETE"))) | length) as $delete_count
		| {
			mariadb_aktuelle_queries:($aktive | length),
			mariadb_aktuelle_queries_select:$select_count,
			mariadb_aktuelle_queries_insert:$insert_count,
			mariadb_aktuelle_queries_update:$update_count,
			mariadb_aktuelle_queries_delete:$delete_count,
			mariadb_aktuelle_queries_langlaufend:($aktive | map(select(.laufzeit >= 5)) | length),
			mariadb_aktuelle_sleep_connections:$sleep_count,
			mariadb_aktuelle_queries_sonstige:(($aktive | length) - $select_count - $insert_count - $update_count - $delete_count)
		}
	' 2>/dev/null || echo "${kennzahlen_standard}"
}

nodepilot_mariadb_live_felder_hinzufuegen()
{
	local mariadb_json="$1"
	local mariadb_cli="$2"
	local live_json
	live_json="$(nodepilot_mariadb_live_query_kennzahlen_json "${mariadb_cli}")"
	if ! echo "${live_json}" | jq -e 'type=="object"' >/dev/null 2>&1; then
		echo "${mariadb_json}"
		return 0
	fi
	echo "${mariadb_json}" | jq -c --argjson live "${live_json}" '
		. as $basis
		| $basis + {
			mariadb_aktuelle_queries:($basis.mariadb_aktuelle_queries // $live.mariadb_aktuelle_queries),
			mariadb_aktuelle_queries_select:($basis.mariadb_aktuelle_queries_select // $live.mariadb_aktuelle_queries_select),
			mariadb_aktuelle_queries_insert:($basis.mariadb_aktuelle_queries_insert // $live.mariadb_aktuelle_queries_insert),
			mariadb_aktuelle_queries_update:($basis.mariadb_aktuelle_queries_update // $live.mariadb_aktuelle_queries_update),
			mariadb_aktuelle_queries_delete:($basis.mariadb_aktuelle_queries_delete // $live.mariadb_aktuelle_queries_delete),
			mariadb_aktuelle_queries_sonstige:($basis.mariadb_aktuelle_queries_sonstige // $live.mariadb_aktuelle_queries_sonstige),
			mariadb_aktuelle_queries_langlaufend:($basis.mariadb_aktuelle_queries_langlaufend // $live.mariadb_aktuelle_queries_langlaufend),
			mariadb_aktuelle_sleep_connections:($basis.mariadb_aktuelle_sleep_connections // $live.mariadb_aktuelle_sleep_connections)
		}
	' 2>/dev/null || echo "${mariadb_json}"
}

nodepilot_mariadb_transport_poll_payload_reduzieren()
{
	local mariadb_json="$1"
	echo "${mariadb_json}" | jq -c '
		(if (.sql_abfragen|type)=="array" then .sql_abfragen else [] end) as $sql_abfragen
		| ($sql_abfragen | map(select(((.diagnose_status // "ok")=="ok") and ((.query_command_type // .prozess_command // "" | ascii_downcase)!="sleep") and ((.query_typ // .typ // "" | tostring | ascii_downcase | contains("diagnose") | not)) and ((.query_typ // .typ // "" | tostring | ascii_downcase)!="processlist_system")))) as $aktive
		| ($aktive | map(select((.query_sql_normalisiert // .query_sql_roh // .sql // "" | tostring | ascii_upcase | startswith("SELECT")))) | length) as $fallback_select
		| ($aktive | map(select((.query_sql_normalisiert // .query_sql_roh // .sql // "" | tostring | ascii_upcase | startswith("INSERT")))) | length) as $fallback_insert
		| ($aktive | map(select((.query_sql_normalisiert // .query_sql_roh // .sql // "" | tostring | ascii_upcase | startswith("UPDATE")))) | length) as $fallback_update
		| ($aktive | map(select((.query_sql_normalisiert // .query_sql_roh // .sql // "" | tostring | ascii_upcase | startswith("DELETE")))) | length) as $fallback_delete
		| ($aktive | map(select((((.query_laufzeit_sekunden // .process_time_seconds // .process_time // 0) | tonumber?) // 0) >= 5)) | length) as $fallback_langlaufend
		| ($sql_abfragen | map(select(((.query_command_type // .prozess_command // "" | ascii_downcase)=="sleep") or ((.query_typ // .typ // "" | tostring | ascii_downcase)=="processlist_sleep"))) | length) as $fallback_sleep
		| {
			verfuegbar:(.verfuegbar // false),
			erreichbar:(.erreichbar // false),
			status:(.status // "unbekannt"),
			diagnose_status:(.diagnose_status // null),
			fehler_text:(.fehler_text // null),
			typ:(.typ // null),
			version:(.version // null),
			threads_connected:(.threads_connected // (.grundzustand.threads_connected // null)),
			threads_running:(.threads_running // (.grundzustand.threads_running // null)),
			questions:(.questions // (.grundzustand.questions // null)),
			slow_queries:(.slow_queries // (.grundzustand.slow_queries // null)),
			bytes_received:(.bytes_received // (.grundzustand.bytes_received // null)),
			bytes_sent:(.bytes_sent // (.grundzustand.bytes_sent // null)),
			queries_pro_sekunde:(.queries_pro_sekunde // (.ableitungen.queries_pro_sekunde // null)),
			slow_queries_pro_minute:(.slow_queries_pro_minute // (.ableitungen.slow_queries_pro_minute // null)),
			ableitungen:{
				queries_pro_sekunde:(.ableitungen.queries_pro_sekunde // .queries_pro_sekunde // null),
				slow_queries_pro_minute:(.ableitungen.slow_queries_pro_minute // .slow_queries_pro_minute // null),
				bytes_received_pro_sekunde:(.ableitungen.bytes_received_pro_sekunde // null),
				bytes_sent_pro_sekunde:(.ableitungen.bytes_sent_pro_sekunde // null)
			},
			mariadb_aktuelle_queries:(.mariadb_aktuelle_queries // ($aktive | length)),
			mariadb_aktuelle_queries_select:(.mariadb_aktuelle_queries_select // $fallback_select),
			mariadb_aktuelle_queries_insert:(.mariadb_aktuelle_queries_insert // $fallback_insert),
			mariadb_aktuelle_queries_update:(.mariadb_aktuelle_queries_update // $fallback_update),
			mariadb_aktuelle_queries_delete:(.mariadb_aktuelle_queries_delete // $fallback_delete),
			mariadb_aktuelle_queries_langlaufend:(.mariadb_aktuelle_queries_langlaufend // $fallback_langlaufend),
			mariadb_aktuelle_sleep_connections:(.mariadb_aktuelle_sleep_connections // $fallback_sleep),
			mariadb_aktuelle_queries_sonstige:(.mariadb_aktuelle_queries_sonstige // (($aktive | length) - $fallback_select - $fallback_insert - $fallback_update - $fallback_delete)),
			query_quelle:(.query_quelle // null),
			query_modus:(.query_modus // null),
			mariadb_sql_abfragen_original_anzahl:(.mariadb_sql_abfragen_original_anzahl // ($sql_abfragen|length)),
			mariadb_sql_abfragen_uebernommen_anzahl:(.mariadb_sql_abfragen_uebernommen_anzahl // ($sql_abfragen|length)),
			mariadb_transport_status:"fertig",
			sql_abfragen:[]
		}
	' 2>/dev/null || echo '{"verfuegbar":false,"erreichbar":false,"status":"fehler","diagnose_status":"json_fehler","fehler_text":"MariaDB-Metadaten konnten nicht reduziert werden.","mariadb_transport_status":"queue_fehler","sql_abfragen":[]}'
}

nodepilot_mariadb_transport_enqueue()
{
	local mariadb_json="$1"
	local poll_modus="${2:-normal}"
	local queue_limit chunk_groesse transfer_id zeitstempel node_id agent_uuid
	queue_limit="$(nodepilot_ini_wert "mariadb_transport" "max_queue_dateien" "400")"
	chunk_groesse="$(nodepilot_ini_wert "mariadb_transport" "chunk_sql_abfragen" "50")"
	if ! [[ "${chunk_groesse}" =~ ^[0-9]+$ ]] || [ "${chunk_groesse}" -le 0 ]; then
		chunk_groesse=50
	fi
	if ! [[ "${queue_limit}" =~ ^[0-9]+$ ]] || [ "${queue_limit}" -le 50 ]; then
		queue_limit=400
	fi

	node_id="$(nodepilot_ini_wert "auth" "node_id" "0")"
	agent_uuid="$(nodepilot_ini_wert "agent" "agent_uuid" "")"
	zeitstempel="$(date -Iseconds)"
	transfer_id="$(cat /proc/sys/kernel/random/uuid 2>/dev/null || uuidgen)"

	install -d -m 0750 "${NODEPILOT_MARIADB_QUEUE_DIR}" "${NODEPILOT_MARIADB_DEADLETTER_DIR}"

	local queue_anzahl
	queue_anzahl="$(find "${NODEPILOT_MARIADB_QUEUE_DIR}" -maxdepth 1 -type f -name '*.json' 2>/dev/null | wc -l | awk '{print $1+0}')"
	if [ "${queue_anzahl}" -ge "${queue_limit}" ]; then
		nodepilot_log "warn" "mariadb_transport retry: queue_limit_erreicht queue_dateien=${queue_anzahl} limit=${queue_limit}."
		return 0
	fi

	local basis_name payload_datei_tmp payload_datei payload_bytes
	basis_name="$(date -u +%Y%m%dT%H%M%SZ)_${transfer_id}"
	local builder_input_datei builder_output_temp_datei builder_tool input_bytes output_bytes
	builder_tool="python3"
	builder_input_datei="$(mktemp "${NODEPILOT_MARIADB_QUEUE_DIR}/.query_builder_input.XXXXXX.json")"
	payload_datei_tmp="$(mktemp "${NODEPILOT_MARIADB_QUEUE_DIR}/.transport_build.XXXXXX.json")"
	builder_output_temp_datei="${payload_datei_tmp}"
	printf '%s' "${mariadb_json}" > "${builder_input_datei}"
	input_bytes="$(wc -c < "${builder_input_datei}" 2>/dev/null | awk '{print $1+0}')"
	nodepilot_log "info" "mariadb_transport builder_input_datei=${builder_input_datei} builder_output_datei=${builder_output_temp_datei} builder_input_bytes=${input_bytes} verwendetes_tool=${builder_tool} argv_frei=ja."
	if ! python3 - "${payload_datei_tmp}" "${builder_input_datei}" "${poll_modus}" "${zeitstempel}" "$(nodepilot_zahl_oder_fallback "${node_id}" "0" "mariadb_transport/node_id")" "${agent_uuid}" "${transfer_id}" "${chunk_groesse}" <<'PY'
import json
import os
import pathlib
import sys
import tempfile

build_path = pathlib.Path(sys.argv[1])
mariadb_input_path = pathlib.Path(sys.argv[2])
poll_modus = sys.argv[3]
zeitstempel = sys.argv[4]
node_id = int(sys.argv[5]) if str(sys.argv[5]).isdigit() else 0
agent_uuid = sys.argv[6]
transfer_id = sys.argv[7]
chunk_groesse = int(sys.argv[8]) if str(sys.argv[8]).isdigit() else 50

base_name = transfer_id
if "_" in build_path.name:
	base_name = build_path.name.split("_", 1)[0]

try:
	mariadb_raw = mariadb_input_path.read_text(encoding="utf-8")
	mariadb = json.loads(mariadb_raw)
except Exception as exc:
	raise SystemExit(f"ungueltiges_mariadb_json:{exc}")

queries = mariadb.get("sql_abfragen")
if not isinstance(queries, list):
	queries = []

snapshot = dict(mariadb)
snapshot["sql_abfragen"] = []

query_anzahl = len(queries)
chunk_count = 0 if query_anzahl == 0 else (query_anzahl + chunk_groesse - 1) // chunk_groesse

payloads = []
payloads.append((
	"000_snapshot",
	{
		"payload_typ": "snapshot",
		"transfer_id": transfer_id,
		"poll_modus": poll_modus,
		"zeitstempel": zeitstempel,
		"node_id": node_id,
		"agent_uuid": agent_uuid,
		"query_anzahl": query_anzahl,
		"chunk_count": chunk_count,
		"chunk_index": 0,
		"mariadb_snapshot": snapshot,
	}
))

for idx in range(chunk_count):
	start = idx * chunk_groesse
	ende = start + chunk_groesse
	chunk = queries[start:ende]
	payloads.append((
		f"{idx + 1:03d}_query_chunk",
		{
			"payload_typ": "query_chunk",
			"transfer_id": transfer_id,
			"poll_modus": poll_modus,
			"zeitstempel": zeitstempel,
			"node_id": node_id,
			"agent_uuid": agent_uuid,
			"query_anzahl": query_anzahl,
			"chunk_count": chunk_count,
			"chunk_index": idx + 1,
			"snapshot_transfer_id": transfer_id,
			"snapshot_zeitstempel": zeitstempel,
			"sql_abfragen": chunk,
		}
	))

build_data = {
	"base_name": base_name,
	"query_anzahl": query_anzahl,
	"chunk_count": chunk_count,
	"payloads": payloads,
}
build_path.write_text(json.dumps(build_data, ensure_ascii=False), encoding="utf-8")
PY
	then
		nodepilot_log "error" "mariadb_transport enqueue_fehlgeschlagen grund=builder_fehler transfer_id=${transfer_id} builder_input_datei=${builder_input_datei} builder_output_datei=${builder_output_temp_datei} builder_input_bytes=${input_bytes} verwendetes_tool=${builder_tool} argv_frei=ja build_ok=nein fehlergrund=python_builder_exit."
		rm -f "${payload_datei_tmp}" "${builder_input_datei}" 2>/dev/null || true
		return 0
	fi

	if [ ! -f "${payload_datei_tmp}" ]; then
		nodepilot_log "error" "mariadb_transport enqueue_fehlgeschlagen grund=builder_output_fehlt transfer_id=${transfer_id} builder_input_datei=${builder_input_datei} builder_output_datei=${builder_output_temp_datei} builder_input_bytes=${input_bytes} verwendetes_tool=${builder_tool} argv_frei=ja build_ok=nein fehlergrund=builder_output_datei_fehlt."
		rm -f "${payload_datei_tmp}" "${builder_input_datei}" 2>/dev/null || true
		return 0
	fi
	output_bytes="$(wc -c < "${payload_datei_tmp}" 2>/dev/null | awk '{print $1+0}')"
	if [ "${output_bytes}" -le 0 ]; then
		nodepilot_log "error" "mariadb_transport enqueue_fehlgeschlagen grund=builder_output_leer transfer_id=${transfer_id} builder_input_datei=${builder_input_datei} builder_output_datei=${builder_output_temp_datei} builder_input_bytes=${input_bytes} builder_output_bytes=${output_bytes} verwendetes_tool=${builder_tool} argv_frei=ja build_ok=nein fehlergrund=builder_output_0_bytes."
		rm -f "${payload_datei_tmp}" "${builder_input_datei}" 2>/dev/null || true
		return 0
	fi
	if ! jq -e '
		(.base_name|type=="string") and
		(.query_anzahl|type=="number") and
		(.chunk_count|type=="number") and
		(.payloads|type=="array") and
		(all(.payloads[]?;
			(type=="array") and
			(length==2) and
			(.[1]|type=="object") and
			((.[1].payload_typ // "")|type=="string") and
			((.[1].transfer_id // "")|type=="string") and
			((.[1].zeitstempel // "")|type=="string")
		))
	' "${payload_datei_tmp}" >/dev/null 2>&1; then
		nodepilot_log "error" "mariadb_transport enqueue_fehlgeschlagen grund=builder_output_ungueltig transfer_id=${transfer_id} builder_input_datei=${builder_input_datei} builder_output_datei=${builder_output_temp_datei} builder_input_bytes=${input_bytes} builder_output_bytes=${output_bytes} verwendetes_tool=${builder_tool} argv_frei=ja build_ok=nein fehlergrund=builder_output_json_ungueltig."
		rm -f "${payload_datei_tmp}" "${builder_input_datei}" 2>/dev/null || true
		return 0
	fi
	nodepilot_log "info" "mariadb_transport builder_input_datei=${builder_input_datei} builder_output_datei=${builder_output_temp_datei} builder_input_bytes=${input_bytes} builder_output_bytes=${output_bytes} verwendetes_tool=${builder_tool} argv_frei=ja build_ok=ja."

	local query_anzahl chunk_count
	query_anzahl="$(jq -r '.query_anzahl // 0' "${payload_datei_tmp}" 2>/dev/null || echo "0")"
	chunk_count="$(jq -r '.chunk_count // 0' "${payload_datei_tmp}" 2>/dev/null || echo "0")"

	local payload_index payload_typ payload_json chunk_index chunk_queries chunk_datei chunk_tmp
	payload_index=0
	while true; do
		payload_typ="$(jq -r --argjson idx "${payload_index}" '.payloads[$idx][1].payload_typ // ""' "${payload_datei_tmp}" 2>/dev/null || echo "")"
		[ -n "${payload_typ}" ] || break
		payload_json="$(jq -c --argjson idx "${payload_index}" '.payloads[$idx][1]' "${payload_datei_tmp}" 2>/dev/null || echo '')"
		if [ -z "${payload_json}" ] || [ "${payload_json}" = "null" ]; then
			nodepilot_log "error" "mariadb_transport enqueue_fehlgeschlagen grund=payload_extract_fehler transfer_id=${transfer_id} builder_input_datei=${builder_input_datei} builder_output_datei=${builder_output_temp_datei} verwendetes_tool=${builder_tool} argv_frei=ja build_ok=nein fehlergrund=payload_json_leer payload_index=${payload_index}."
			payload_index=$((payload_index + 1))
			continue
		fi
		chunk_index="$(jq -r --argjson idx "${payload_index}" '.payloads[$idx][1].chunk_index // 0' "${payload_datei_tmp}" 2>/dev/null || echo "0")"
		chunk_queries="$(echo "${payload_json}" | jq -r 'if .payload_typ=="query_chunk" then (.sql_abfragen|length) else (.query_anzahl // 0) end' 2>/dev/null || echo "0")"
		chunk_datei="${NODEPILOT_MARIADB_QUEUE_DIR}/${basis_name}_$(printf '%03d' "${chunk_index}")_${payload_typ}.json"
		chunk_tmp="$(mktemp "${NODEPILOT_MARIADB_QUEUE_DIR}/.chunk_write.XXXXXX")"
		printf '%s' "${payload_json}" > "${chunk_tmp}"
		nodepilot_log "info" "mariadb_transport chunk_datei_temp=${chunk_tmp} chunk_datei_final=${chunk_datei}."
		if ! nodepilot_mariadb_transport_datei_validieren "${chunk_tmp}" "vor_queue"; then
			nodepilot_mariadb_transport_deadletter_verschieben "${chunk_tmp}" "ungueltig_vor_queue"
			payload_index=$((payload_index + 1))
			continue
		fi
		mv "${chunk_tmp}" "${chunk_datei}"
		chmod 0640 "${chunk_datei}" 2>/dev/null || true
		payload_bytes="$(wc -c < "${chunk_datei}" 2>/dev/null | awk '{print $1+0}')"
		nodepilot_log "info" "mariadb_transport queue_datei_erstellt payload_typ=${payload_typ} transfer_id=${transfer_id} chunk_index=${chunk_index} chunk_count=${chunk_count} payload_bytes=${payload_bytes} query_anzahl=${chunk_queries} queue_datei_final=${chunk_datei} build_ok=ja."
		payload_index=$((payload_index + 1))
	done
	rm -f "${payload_datei_tmp}" "${builder_input_datei}" 2>/dev/null || true
}

nodepilot_mariadb_transport_deadletter_verschieben()
{
	local payload_datei="$1"
	local grund="${2:-unbekannt}"
	local transfer_id node_id agent_uuid payload_typ meta_datei
	if [ ! -f "${payload_datei}" ]; then
		return
	fi
	transfer_id="$(jq -r '.transfer_id // ""' "${payload_datei}" 2>/dev/null || echo "")"
	node_id="$(jq -r '.node_id // 0' "${payload_datei}" 2>/dev/null | awk '{print $1+0}' || echo "0")"
	agent_uuid="$(jq -r '.agent_uuid // ""' "${payload_datei}" 2>/dev/null || echo "")"
	payload_typ="$(jq -r '.payload_typ // ""' "${payload_datei}" 2>/dev/null || echo "")"
	install -d -m 0750 "${NODEPILOT_MARIADB_DEADLETTER_DIR}"
	local ziel
	ziel="${NODEPILOT_MARIADB_DEADLETTER_DIR}/$(date -u +%Y%m%dT%H%M%SZ)_grund-${grund}_${payload_datei##*/}"
	mv "${payload_datei}" "${ziel}" 2>/dev/null || rm -f "${payload_datei}" 2>/dev/null || true
	meta_datei="$(nodepilot_mariadb_transport_meta_datei "${payload_datei}")"
	rm -f "${meta_datei}" 2>/dev/null || true
	nodepilot_log "error" "mariadb_transport deadletter_grund=${grund} payload_typ=${payload_typ} transfer_id=${transfer_id} agent_uuid=${agent_uuid:-leer} node_id=${node_id} datei=${ziel}."
}

nodepilot_mariadb_transport_meta_datei()
{
	local payload_datei="$1"
	echo "${payload_datei}.meta"
}

nodepilot_mariadb_transport_meta_lesen()
{
	local payload_datei="$1"
	local meta_datei
	meta_datei="$(nodepilot_mariadb_transport_meta_datei "${payload_datei}")"
	if [ -f "${meta_datei}" ] && jq -e . "${meta_datei}" >/dev/null 2>&1; then
		cat "${meta_datei}"
		return
	fi
	echo '{"first_seen_unix":0,"send_fehler_anzahl":0,"warte_anzahl":0,"letzter_grund":""}'
}

nodepilot_mariadb_transport_meta_schreiben()
{
	local payload_datei="$1"
	local meta_json="$2"
	local meta_datei meta_tmp
	meta_datei="$(nodepilot_mariadb_transport_meta_datei "${payload_datei}")"
	meta_tmp="$(mktemp "${NODEPILOT_MARIADB_QUEUE_DIR}/.meta.XXXXXX")"
	printf '%s' "${meta_json}" > "${meta_tmp}"
	mv "${meta_tmp}" "${meta_datei}"
	chmod 0640 "${meta_datei}" 2>/dev/null || true
}

nodepilot_mariadb_transport_meta_ereignis_buchen()
{
	local payload_datei="$1"
	local ereignis="$2"
	local grund="${3:-}"
	local jetzt meta_json neues_meta
	jetzt="$(date +%s)"
	meta_json="$(nodepilot_mariadb_transport_meta_lesen "${payload_datei}")"
	neues_meta="$(printf '%s' "${meta_json}" | jq -c --arg event "${ereignis}" --arg grund "${grund}" --argjson now "${jetzt}" '
		.first_seen_unix = ((.first_seen_unix // 0) | tonumber? // 0)
		| .send_fehler_anzahl = ((.send_fehler_anzahl // 0) | tonumber? // 0)
		| .warte_anzahl = ((.warte_anzahl // 0) | tonumber? // 0)
		| if .first_seen_unix <= 0 then .first_seen_unix = $now else . end
		| .letzter_grund = (if $grund == "" then (.letzter_grund // "") else $grund end)
		| .letztes_ereignis = $event
		| .letztes_ereignis_unix = $now
		| if $event == "send_fehler" then .send_fehler_anzahl = (.send_fehler_anzahl + 1)
		  elif $event == "warten" then .warte_anzahl = (.warte_anzahl + 1)
		  else . end
	' 2>/dev/null || echo '')"
	if [ -z "${neues_meta}" ] || ! printf '%s' "${neues_meta}" | jq -e . >/dev/null 2>&1; then
		neues_meta='{"first_seen_unix":'"${jetzt}"',"send_fehler_anzahl":0,"warte_anzahl":0,"letzter_grund":"meta_schreibfehler"}'
	fi
	nodepilot_mariadb_transport_meta_schreiben "${payload_datei}" "${neues_meta}"
	echo "${neues_meta}"
}

nodepilot_mariadb_transport_snapshot_marker_datei()
{
	local transfer_id="$1"
	local transfer_id_safe
	transfer_id_safe="$(echo "${transfer_id}" | tr -c 'A-Za-z0-9._-' '_')"
	echo "${NODEPILOT_MARIADB_TRANSFER_STATE_DIR}/snapshot-${transfer_id_safe}.ok"
}

nodepilot_mariadb_transport_snapshot_marker_setzen()
{
	local transfer_id="$1"
	local snapshot_id="$2"
	local marker_datei
	[ -n "${transfer_id}" ] || return
	install -d -m 0750 "${NODEPILOT_MARIADB_TRANSFER_STATE_DIR}"
	marker_datei="$(nodepilot_mariadb_transport_snapshot_marker_datei "${transfer_id}")"
	jq -cn --arg zeit "$(date -Iseconds)" --argjson snapshot_id "$(nodepilot_zahl_oder_fallback "${snapshot_id}" "0" "mariadb_transport/snapshot_id")" '{ack_zeit:$zeit,snapshot_id:$snapshot_id}' > "${marker_datei}" 2>/dev/null || printf '%s\n' "$(nodepilot_zahl_oder_fallback "${snapshot_id}" "0" "mariadb_transport/snapshot_id_fallback")" > "${marker_datei}"
	chmod 0640 "${marker_datei}" 2>/dev/null || true
}

nodepilot_mariadb_transport_snapshot_wartet_auf_transfer()
{
	local payload_datei="$1"
	local transfer_id="$2"
	local marker_datei datei_kandidat transfer_kandidat
	[ -n "${transfer_id}" ] || return 0
	marker_datei="$(nodepilot_mariadb_transport_snapshot_marker_datei "${transfer_id}")"
	if [ -f "${marker_datei}" ]; then
		return 1
	fi

	for datei_kandidat in "${NODEPILOT_MARIADB_QUEUE_DIR}"/*.json; do
		[ -e "${datei_kandidat}" ] || break
		if [ "${datei_kandidat}" = "${payload_datei}" ]; then
			continue
		fi
		transfer_kandidat="$(jq -r 'if (.payload_typ // "")=="snapshot" then (.transfer_id // "") else "" end' "${datei_kandidat}" 2>/dev/null || echo "")"
		if [ "${transfer_kandidat}" = "${transfer_id}" ]; then
			return 0
		fi
	done
	return 0
}

nodepilot_mariadb_transport_snapshot_id_laden()
{
	local transfer_id="$1"
	local marker_datei snapshot_id
	[ -n "${transfer_id}" ] || { echo "0"; return; }
	marker_datei="$(nodepilot_mariadb_transport_snapshot_marker_datei "${transfer_id}")"
	if [ ! -f "${marker_datei}" ]; then
		echo "0"
		return
	fi
	snapshot_id="$(jq -r '.snapshot_id // 0' "${marker_datei}" 2>/dev/null || cat "${marker_datei}" 2>/dev/null || echo "0")"
	echo "$(nodepilot_zahl_oder_fallback "${snapshot_id}" "0" "mariadb_transport/marker_snapshot_id")"
}

nodepilot_mariadb_transport_payload_snapshot_id_setzen()
{
	local payload_datei="$1"
	local snapshot_id="$2"
	local payload_typ
	payload_typ="$(jq -r '.payload_typ // ""' "${payload_datei}" 2>/dev/null || echo "")"
	if [ "${payload_typ}" != "query_chunk" ]; then
		return 0
	fi
	if [ "$(nodepilot_zahl_oder_fallback "${snapshot_id}" "0" "mariadb_transport/payload_snapshot_id")" -le 0 ]; then
		return 1
	fi
	if jq -e '.snapshot_id|tonumber? // 0 | . > 0' "${payload_datei}" >/dev/null 2>&1; then
		return 0
	fi
	if ! jq --argjson snapshot_id "$(nodepilot_zahl_oder_fallback "${snapshot_id}" "0" "mariadb_transport/payload_snapshot_id_jq")" '.snapshot_id=$snapshot_id' "${payload_datei}" > "${payload_datei}.tmp" 2>/dev/null; then
		rm -f "${payload_datei}.tmp" 2>/dev/null || true
		return 1
	fi
	mv "${payload_datei}.tmp" "${payload_datei}"
	chmod 0640 "${payload_datei}" 2>/dev/null || true
	return 0
}

nodepilot_mariadb_transport_payload_identitaet_ok()
{
	local payload_datei="$1"
	local payload_typ transfer_id lokale_node_id lokale_agent_uuid payload_node_id payload_agent_uuid
	payload_typ="$(jq -r '.payload_typ // ""' "${payload_datei}" 2>/dev/null || echo "")"
	transfer_id="$(jq -r '.transfer_id // ""' "${payload_datei}" 2>/dev/null || echo "")"
	lokale_node_id="$(nodepilot_zahl_oder_fallback "$(nodepilot_ini_wert "auth" "node_id" "0")" "0" "mariadb_transport/identitaet/lokal_node_id")"
	lokale_agent_uuid="$(nodepilot_ini_wert "agent" "agent_uuid" "")"
	payload_node_id="$(jq -r '.node_id // 0' "${payload_datei}" 2>/dev/null || echo "0")"
	payload_agent_uuid="$(jq -r '.agent_uuid // ""' "${payload_datei}" 2>/dev/null || echo "")"
	payload_node_id="$(nodepilot_zahl_oder_fallback "${payload_node_id}" "0" "mariadb_transport/identitaet/payload_node_id")"
	NODEPILOT_MARIADB_IDENTITAET_ENTSCHEIDUNG="retry"
	NODEPILOT_MARIADB_IDENTITAET_GRUND=""

	if [ "${payload_node_id}" -ne "${lokale_node_id}" ] || [ "${payload_agent_uuid}" != "${lokale_agent_uuid}" ]; then
		if [ -n "${payload_agent_uuid}" ] && [ "${payload_agent_uuid}" = "${lokale_agent_uuid}" ] && [ "${payload_node_id}" -ne "${lokale_node_id}" ]; then
			NODEPILOT_MARIADB_IDENTITAET_ENTSCHEIDUNG="veraltet_verworfen"
			NODEPILOT_MARIADB_IDENTITAET_GRUND="veraltete_identitaet"
		else
			NODEPILOT_MARIADB_IDENTITAET_ENTSCHEIDUNG="deadletter"
			NODEPILOT_MARIADB_IDENTITAET_GRUND="identitaet_unpassend"
		fi
		nodepilot_log "error" "mariadb_transport identitaet_unpassend datei=${payload_datei} payload_typ=${payload_typ:-leer} transfer_id=${transfer_id:-leer} agent_uuid=${payload_agent_uuid:-leer} payload_node_id=${payload_node_id} lokale_node_id=${lokale_node_id} entscheidung=${NODEPILOT_MARIADB_IDENTITAET_ENTSCHEIDUNG} grund=${NODEPILOT_MARIADB_IDENTITAET_GRUND}."
		return 1
	fi
	return 0
}

nodepilot_mariadb_transport_datei_validieren()
{
	local payload_datei="$1"
	local phase="${2:-vor_versand}"
	if [ ! -f "${payload_datei}" ]; then
		nodepilot_log "error" "mariadb_transport validierung_fehler phase=${phase} datei=${payload_datei} grund=datei_fehlt."
		return 1
	fi

	local payload_bytes json_valid root_keys payload_typ node_id agent_uuid query_anzahl query_anzahl_payload
	payload_bytes="$(wc -c < "${payload_datei}" 2>/dev/null | awk '{print $1+0}')"
	if [ "${payload_bytes}" -le 0 ]; then
		nodepilot_log "error" "mariadb_transport validierung_fehler phase=${phase} datei=${payload_datei} grund=datei_leer dateigroesse_bytes=${payload_bytes}."
		return 1
	fi

	if jq -e . "${payload_datei}" >/dev/null 2>&1; then
		json_valid="ja"
	else
		json_valid="nein"
	fi

	root_keys="$(jq -c 'if type=="object" then keys_unsorted else [] end' "${payload_datei}" 2>/dev/null || echo '[]')"
	payload_typ="$(jq -r '.payload_typ // ""' "${payload_datei}" 2>/dev/null || echo "")"
	node_id="$(jq -r '.node_id // 0' "${payload_datei}" 2>/dev/null || echo "0")"
	node_id="$(nodepilot_zahl_oder_fallback "${node_id}" "0" "mariadb_transport/validierung/node_id")"
	agent_uuid="$(jq -r '.agent_uuid // ""' "${payload_datei}" 2>/dev/null || echo "")"
	query_anzahl="$(jq -r 'if .payload_typ=="query_chunk" then (.sql_abfragen|length) else (.query_anzahl // 0) end' "${payload_datei}" 2>/dev/null || echo "0")"
	nodepilot_log "info" "mariadb_transport validierung phase=${phase} datei=${payload_datei} dateigroesse_bytes=${payload_bytes} json_valid=${json_valid} root_keys=${root_keys} node_id=${node_id} agent_uuid=${agent_uuid:-leer} payload_typ=${payload_typ} query_anzahl=${query_anzahl}."

	if [ "${json_valid}" != "ja" ]; then
		return 1
	fi

	if ! jq -e 'type=="object" and has("payload_typ") and has("node_id") and has("agent_uuid") and has("query_anzahl") and has("chunk_count") and has("chunk_index") and has("transfer_id") and has("zeitstempel") and has("poll_modus")' "${payload_datei}" >/dev/null 2>&1; then
		nodepilot_log "error" "mariadb_transport validierung_fehler phase=${phase} datei=${payload_datei} grund=root_keys_fehlen."
		return 1
	fi

	if [ "${node_id}" -le 0 ]; then
		nodepilot_log "error" "mariadb_transport validierung_fehler phase=${phase} datei=${payload_datei} grund=node_id_ungueltig node_id=${node_id}."
		return 1
	fi
	if [ -z "${agent_uuid}" ]; then
		nodepilot_log "error" "mariadb_transport validierung_fehler phase=${phase} datei=${payload_datei} grund=agent_uuid_leer."
		return 1
	fi
	if [ -z "${payload_typ}" ]; then
		nodepilot_log "error" "mariadb_transport validierung_fehler phase=${phase} datei=${payload_datei} grund=payload_typ_leer."
		return 1
	fi

	if [ "${payload_typ}" = "query_chunk" ]; then
		query_anzahl_payload="$(jq -r '(.query_anzahl|tonumber?) // 0' "${payload_datei}" 2>/dev/null || echo "0")"
		query_anzahl_payload="$(nodepilot_zahl_oder_fallback "${query_anzahl_payload}" "0" "mariadb_transport/validierung/query_anzahl_payload")"
		if ! jq -e 'has("payload_typ") and has("transfer_id") and has("node_id") and has("agent_uuid") and has("chunk_index") and has("sql_abfragen") and has("query_anzahl") and has("chunk_count") and has("zeitstempel") and has("poll_modus") and (.sql_abfragen|type=="array") and ((.sql_abfragen|length)>0) and (((.query_anzahl|tonumber?) // 0) >= (.sql_abfragen|length))' "${payload_datei}" >/dev/null 2>&1; then
			nodepilot_log "error" "mariadb_transport validierung_fehler phase=${phase} datei=${payload_datei} grund=query_chunk_inkonsistent."
			return 1
		fi
		if [ "${query_anzahl_payload}" -lt "${query_anzahl}" ]; then
			nodepilot_log "error" "mariadb_transport validierung_fehler phase=${phase} datei=${payload_datei} grund=query_anzahl_unterlauf query_anzahl_payload=${query_anzahl_payload} chunk_query_anzahl=${query_anzahl}."
			return 1
		fi
	elif [ "${payload_typ}" = "snapshot" ]; then
		if ! jq -e '.mariadb_snapshot|type=="object"' "${payload_datei}" >/dev/null 2>&1; then
			nodepilot_log "error" "mariadb_transport validierung_fehler phase=${phase} datei=${payload_datei} grund=snapshot_fehlt."
			return 1
		fi
	else
		nodepilot_log "error" "mariadb_transport validierung_fehler phase=${phase} datei=${payload_datei} grund=payload_typ_unbekannt payload_typ=${payload_typ}."
		return 1
	fi

	return 0
}

nodepilot_mariadb_transport_queue_verarbeiten()
{
	install -d -m 0750 "${NODEPILOT_MARIADB_QUEUE_DIR}" "${NODEPILOT_MARIADB_DEADLETTER_DIR}"
	if command -v flock >/dev/null 2>&1; then
		exec 8>"${NODEPILOT_MARIADB_QUEUE_LOCK}"
		if ! flock -n 8; then
			return 0
		fi
	fi

	local datei
	for datei in "${NODEPILOT_MARIADB_QUEUE_DIR}"/*.json; do
		[ -e "${datei}" ] || break
		local payload_typ transfer_id chunk_index chunk_count query_anzahl payload_bytes context endpoint antwort server_response_code server_status persistiert http_status api_erfolg node_id agent_uuid
		local max_send_fehler max_warte_anzahl max_warte_sekunden meta_json send_fehler_anzahl warte_anzahl first_seen_unix alter_sekunden
		max_send_fehler="$(nodepilot_zahl_oder_fallback "$(nodepilot_ini_wert "mariadb_transport" "max_send_fehler_anzahl" "8")" "8" "mariadb_transport/max_send_fehler_anzahl")"
		max_warte_anzahl="$(nodepilot_zahl_oder_fallback "$(nodepilot_ini_wert "mariadb_transport" "max_warte_anzahl" "12")" "12" "mariadb_transport/max_warte_anzahl")"
		max_warte_sekunden="$(nodepilot_zahl_oder_fallback "$(nodepilot_ini_wert "mariadb_transport" "max_warte_sekunden" "1800")" "1800" "mariadb_transport/max_warte_sekunden")"
		payload_typ="$(jq -r '.payload_typ // ""' "${datei}" 2>/dev/null || echo "")"
		transfer_id="$(jq -r '.transfer_id // ""' "${datei}" 2>/dev/null || echo "")"
		chunk_index="$(jq -r '.chunk_index // 0' "${datei}" 2>/dev/null || echo "0")"
		chunk_count="$(jq -r '.chunk_count // 0' "${datei}" 2>/dev/null || echo "0")"
		query_anzahl="$(jq -r 'if .payload_typ=="query_chunk" then (.sql_abfragen|length) else (.query_anzahl // 0) end' "${datei}" 2>/dev/null || echo "0")"
		node_id="$(jq -r '.node_id // 0' "${datei}" 2>/dev/null | awk '{print $1+0}' || echo "0")"
		agent_uuid="$(jq -r '.agent_uuid // ""' "${datei}" 2>/dev/null || echo "")"
		if ! nodepilot_mariadb_transport_datei_validieren "${datei}" "queue_eingang"; then
			nodepilot_mariadb_transport_deadletter_verschieben "${datei}" "ungueltig_queue_eingang"
			continue
		fi

		if ! nodepilot_mariadb_transport_payload_identitaet_ok "${datei}"; then
			if [ "${NODEPILOT_MARIADB_IDENTITAET_GRUND:-identitaet_unpassend}" = "veraltete_identitaet" ]; then
				nodepilot_mariadb_transport_deadletter_verschieben "${datei}" "veraltete_identitaet"
			else
				nodepilot_mariadb_transport_deadletter_verschieben "${datei}" "identitaet_unpassend"
			fi
			continue
		fi

		if [ "${payload_typ}" = "query_chunk" ] && nodepilot_mariadb_transport_snapshot_wartet_auf_transfer "${datei}" "${transfer_id}"; then
			meta_json="$(nodepilot_mariadb_transport_meta_ereignis_buchen "${datei}" "warten" "snapshot_transfer_noch_nicht_bestaetigt")"
			send_fehler_anzahl="$(printf '%s' "${meta_json}" | jq -r '.send_fehler_anzahl // 0' 2>/dev/null || echo "0")"
			warte_anzahl="$(printf '%s' "${meta_json}" | jq -r '.warte_anzahl // 0' 2>/dev/null || echo "0")"
			first_seen_unix="$(printf '%s' "${meta_json}" | jq -r '.first_seen_unix // 0' 2>/dev/null || echo "0")"
			alter_sekunden="$(( $(date +%s) - $(nodepilot_zahl_oder_fallback "${first_seen_unix}" "0" "mariadb_transport/first_seen_unix") ))"
			if [ "${warte_anzahl}" -ge "${max_warte_anzahl}" ] || [ "${alter_sekunden}" -ge "${max_warte_sekunden}" ]; then
				nodepilot_mariadb_transport_deadletter_verschieben "${datei}" "snapshot_wartezeit_ueberschritten"
				continue
			fi
			nodepilot_log "warn" "mariadb_transport warten_auf_snapshot transfer_id=${transfer_id} payload_typ=${payload_typ} warte_anzahl=${warte_anzahl}/${max_warte_anzahl} alter_sekunden=${alter_sekunden}/${max_warte_sekunden} agent_uuid=${agent_uuid:-leer} node_id=${node_id} datei=${datei}."
			continue
		fi

		if [ "${payload_typ}" = "query_chunk" ]; then
			local snapshot_id_marker
			snapshot_id_marker="$(nodepilot_mariadb_transport_snapshot_id_laden "${transfer_id}")"
			if ! nodepilot_mariadb_transport_payload_snapshot_id_setzen "${datei}" "${snapshot_id_marker}"; then
				meta_json="$(nodepilot_mariadb_transport_meta_ereignis_buchen "${datei}" "warten" "snapshot_id_noch_unbekannt")"
				nodepilot_log "warn" "mariadb_transport warten_auf_snapshot_id transfer_id=${transfer_id} payload_typ=${payload_typ} marker_snapshot_id=${snapshot_id_marker} datei=${datei}."
				continue
			fi
		fi

		if ! nodepilot_mariadb_transport_datei_validieren "${datei}" "vor_versand"; then
			nodepilot_mariadb_transport_deadletter_verschieben "${datei}" "ungueltig_vor_versand"
			continue
		fi

		nodepilot_mariadb_transport_payload_metadaten_loggen "${datei}" "vor_versand"
		payload_bytes="$(wc -c < "${datei}" 2>/dev/null | awk '{print $1+0}')"
		context="mariadb-transport-${payload_typ}-${transfer_id}-${chunk_index}"
		endpoint="$(nodepilot_ini_wert "server" "api_mariadb_transport_endpoint" "/agent/mariadb-transport")"
		nodepilot_log "info" "mariadb_transport versand_start payload_typ=${payload_typ} transfer_id=${transfer_id} chunk_index=${chunk_index} chunk_count=${chunk_count} payload_bytes=${payload_bytes} query_anzahl=${query_anzahl} node_id=${node_id} agent_uuid=${agent_uuid:-leer} datei=${datei}."

		set +e
		antwort="$(nodepilot_retry_post_datei "${endpoint}" "${datei}" "${context}")"
		local rc=$?
		set -e

		if [ "${rc}" -ne 0 ]; then
			meta_json="$(nodepilot_mariadb_transport_meta_ereignis_buchen "${datei}" "send_fehler" "retry_post_datei_rc_${rc}")"
			send_fehler_anzahl="$(printf '%s' "${meta_json}" | jq -r '.send_fehler_anzahl // 0' 2>/dev/null || echo "0")"
			nodepilot_log "warn" "mariadb_transport versand_fehler payload_typ=${payload_typ} transfer_id=${transfer_id} chunk_index=${chunk_index} chunk_count=${chunk_count} payload_bytes=${payload_bytes} query_anzahl=${query_anzahl} node_id=${node_id} agent_uuid=${agent_uuid:-leer} fehlergrund=retry_post_datei_rc_${rc} send_fehler_anzahl=${send_fehler_anzahl}/${max_send_fehler} http_status=unbekannt api_erfolg=nein persistiert=nein datei=${datei}."
			if [ "${send_fehler_anzahl}" -ge "${max_send_fehler}" ]; then
				nodepilot_mariadb_transport_deadletter_verschieben "${datei}" "retry_limit_ueberschritten"
				continue
			fi
			break
		fi

		server_response_code="$(printf '%s' "${antwort}" | jq -r '(.server_response_code // .http_status // .code // "unbekannt")' 2>/dev/null || echo "unbekannt")"
		http_status="$(printf '%s' "${antwort}" | jq -r '(.http_status // .server_response_code // .code // "unbekannt")' 2>/dev/null || echo "unbekannt")"
		server_status="$(printf '%s' "${antwort}" | jq -r '.status // "ok"' 2>/dev/null || echo "ok")"
		persistiert="$(printf '%s' "${antwort}" | jq -r 'if (.persistiert|type)=="boolean" then (if .persistiert then "ja" else "nein" end) elif (.persistiert // "")|tostring|ascii_downcase=="ja" then "ja" else "ja" end' 2>/dev/null || echo "ja")"
		api_erfolg="$(printf '%s' "${antwort}" | jq -r 'if ((.status // "ok")|tostring|ascii_downcase)=="ok" or ((.erfolg // false)==true) then "ja" else "nein" end' 2>/dev/null || echo "ja")"
		nodepilot_log "info" "mariadb_transport versand_antwort payload_typ=${payload_typ} transfer_id=${transfer_id} chunk_index=${chunk_index} http_status=${http_status} api_erfolg=${api_erfolg} persistiert=${persistiert} status=${server_status} server_response_code=${server_response_code}."

		if [ "${api_erfolg}" != "ja" ]; then
			meta_json="$(nodepilot_mariadb_transport_meta_ereignis_buchen "${datei}" "send_fehler" "api_status_${server_status}")"
			send_fehler_anzahl="$(printf '%s' "${meta_json}" | jq -r '.send_fehler_anzahl // 0' 2>/dev/null || echo "0")"
			if [ "${payload_typ}" = "query_chunk" ] && printf '%s' "${antwort}" | tr '[:upper:]' '[:lower:]' | grep -Eq 'snapshot(_id)?[^a-z0-9]*(unknown|unbekannt|nicht_gefunden|fehlend)'; then
				meta_json="$(nodepilot_mariadb_transport_meta_ereignis_buchen "${datei}" "warten" "snapshot_unbekannt_serverseitig")"
				warte_anzahl="$(printf '%s' "${meta_json}" | jq -r '.warte_anzahl // 0' 2>/dev/null || echo "0")"
				first_seen_unix="$(printf '%s' "${meta_json}" | jq -r '.first_seen_unix // 0' 2>/dev/null || echo "0")"
				alter_sekunden="$(( $(date +%s) - $(nodepilot_zahl_oder_fallback "${first_seen_unix}" "0" "mariadb_transport/first_seen_unix_server") ))"
				if [ "${warte_anzahl}" -ge "${max_warte_anzahl}" ] || [ "${alter_sekunden}" -ge "${max_warte_sekunden}" ]; then
					nodepilot_mariadb_transport_deadletter_verschieben "${datei}" "snapshot_unbekannt_timeout"
					continue
				fi
				nodepilot_log "warn" "mariadb_transport snapshot_noch_unbekannt transfer_id=${transfer_id} agent_uuid=${agent_uuid:-leer} node_id=${node_id} warte_anzahl=${warte_anzahl}/${max_warte_anzahl} alter_sekunden=${alter_sekunden}/${max_warte_sekunden} datei=${datei}."
				continue
			fi
			if [ "${send_fehler_anzahl}" -ge "${max_send_fehler}" ]; then
				nodepilot_mariadb_transport_deadletter_verschieben "${datei}" "api_retry_limit_ueberschritten"
				continue
			fi
			nodepilot_log "warn" "mariadb_transport api_status_nicht_ok transfer_id=${transfer_id} payload_typ=${payload_typ} status=${server_status} server_response_code=${server_response_code} send_fehler_anzahl=${send_fehler_anzahl}/${max_send_fehler} agent_uuid=${agent_uuid:-leer} node_id=${node_id} datei=${datei}."
			break
		fi

		if [ "${payload_typ}" = "snapshot" ]; then
			local snapshot_id_antwort
			snapshot_id_antwort="$(printf '%s' "${antwort}" | jq -r '.snapshot_id // 0' 2>/dev/null || echo "0")"
			snapshot_id_antwort="$(nodepilot_zahl_oder_fallback "${snapshot_id_antwort}" "0" "mariadb_transport/snapshot_id_antwort")"
			if [ "${snapshot_id_antwort}" -le 0 ]; then
				meta_json="$(nodepilot_mariadb_transport_meta_ereignis_buchen "${datei}" "send_fehler" "snapshot_ack_ohne_snapshot_id")"
				nodepilot_log "warn" "mariadb_transport snapshot_ack_ungueltig transfer_id=${transfer_id} snapshot_id=${snapshot_id_antwort} datei=${datei}."
				continue
			fi
			nodepilot_mariadb_transport_snapshot_marker_setzen "${transfer_id}" "${snapshot_id_antwort}"
		fi

		rm -f "${datei}" 2>/dev/null || true
		rm -f "$(nodepilot_mariadb_transport_meta_datei "${datei}")" 2>/dev/null || true
		nodepilot_log "info" "mariadb_transport erfolgreich persistiert transfer_id=${transfer_id} payload_typ=${payload_typ} chunk_index=${chunk_index} chunk_count=${chunk_count}."
	done

	if command -v flock >/dev/null 2>&1; then
		flock -u 8 || true
		exec 8>&-
	fi
}

nodepilot_queue_veraltete_identitaet_bereinigen()
{
	local lokale_node_id lokale_agent_uuid bereinigt_telemetrie bereinigt_mariadb
	lokale_node_id="$(nodepilot_zahl_oder_fallback "$(nodepilot_ini_wert "auth" "node_id" "0")" "0" "queue_cleanup/lokale_node_id")"
	lokale_agent_uuid="$(nodepilot_ini_wert "agent" "agent_uuid" "")"
	bereinigt_telemetrie=0
	bereinigt_mariadb=0
	install -d -m 0750 "${NODEPILOT_HTTP_OUTBOX_DIR}" "${NODEPILOT_MARIADB_QUEUE_DIR}" "${NODEPILOT_HTTP_DEADLETTER_DIR}" "${NODEPILOT_MARIADB_DEADLETTER_DIR}"

	local datei payload_node_id payload_agent_uuid payload_typ transfer_id
	for datei in "${NODEPILOT_HTTP_OUTBOX_DIR}"/*.json; do
		[ -e "${datei}" ] || break
		payload_node_id="$(nodepilot_zahl_oder_fallback "$(jq -r '.node_id // 0' "${datei}" 2>/dev/null || echo "0")" "0" "queue_cleanup/telemetrie_payload_node_id")"
		payload_agent_uuid="$(jq -r '.agent_uuid // ""' "${datei}" 2>/dev/null || echo "")"
		payload_typ="$(jq -r '.payload_typ // "telemetrie_poll"' "${datei}" 2>/dev/null || echo "telemetrie_poll")"
		transfer_id="$(jq -r '.transfer_id // ""' "${datei}" 2>/dev/null || echo "")"
		if [ -n "${payload_agent_uuid}" ] && [ "${payload_agent_uuid}" = "${lokale_agent_uuid}" ] && [ "${payload_node_id}" -ne "${lokale_node_id}" ]; then
			nodepilot_telemetrie_queue_deadletter_verschieben "${datei}" "veraltete_identitaet" "${payload_typ}" "${transfer_id}" "${payload_agent_uuid}" "${payload_node_id}" "${lokale_node_id}" "veraltet_verworfen"
			bereinigt_telemetrie=$((bereinigt_telemetrie + 1))
		fi
	done

	for datei in "${NODEPILOT_MARIADB_QUEUE_DIR}"/*.json; do
		[ -e "${datei}" ] || break
		payload_node_id="$(nodepilot_zahl_oder_fallback "$(jq -r '.node_id // 0' "${datei}" 2>/dev/null || echo "0")" "0" "queue_cleanup/mariadb_payload_node_id")"
		payload_agent_uuid="$(jq -r '.agent_uuid // ""' "${datei}" 2>/dev/null || echo "")"
		if [ -n "${payload_agent_uuid}" ] && [ "${payload_agent_uuid}" = "${lokale_agent_uuid}" ] && [ "${payload_node_id}" -ne "${lokale_node_id}" ]; then
			nodepilot_mariadb_transport_deadletter_verschieben "${datei}" "veraltete_identitaet"
			bereinigt_mariadb=$((bereinigt_mariadb + 1))
		fi
	done

	if [ "${bereinigt_telemetrie}" -gt 0 ] || [ "${bereinigt_mariadb}" -gt 0 ]; then
		nodepilot_outbox_status_feld_setzen "letzter_deadletter_grund" "veraltete_identitaet"
		nodepilot_outbox_status_feld_setzen "letzter_queue_vorgang" "cleanup_veraltete_identitaet:telemetrie=${bereinigt_telemetrie}:mariadb=${bereinigt_mariadb}"
		nodepilot_outbox_status_ermitteln >/dev/null
	fi
	nodepilot_log "info" "queue_cleanup veraltete_identitaet telemetrie_bereinigt=${bereinigt_telemetrie} mariadb_bereinigt=${bereinigt_mariadb} lokale_node_id=${lokale_node_id} lokale_agent_uuid=${lokale_agent_uuid:-leer}."
}

nodepilot_mariadb_transport_payload_metadaten_loggen()
{
	local payload_datei="$1"
	local phase="${2:-vor_versand}"
	if [ ! -f "${payload_datei}" ]; then
		return
	fi

	local root_keys node_id agent_uuid payload_typ payload_bytes query_anzahl transfer_id chunk_index chunk_count
	root_keys="$(jq -c 'keys_unsorted' "${payload_datei}" 2>/dev/null || echo '[]')"
	node_id="$(jq -r '.node_id // 0' "${payload_datei}" 2>/dev/null || echo "0")"
	node_id="$(nodepilot_zahl_oder_fallback "${node_id}" "0" "mariadb_transport/metadaten/node_id")"
	agent_uuid="$(jq -r '.agent_uuid // ""' "${payload_datei}" 2>/dev/null || echo "")"
	payload_typ="$(jq -r '.payload_typ // ""' "${payload_datei}" 2>/dev/null || echo "")"
	transfer_id="$(jq -r '.transfer_id // ""' "${payload_datei}" 2>/dev/null || echo "")"
	chunk_index="$(jq -r '.chunk_index // 0' "${payload_datei}" 2>/dev/null || echo "0")"
	chunk_count="$(jq -r '.chunk_count // 0' "${payload_datei}" 2>/dev/null || echo "0")"
	payload_bytes="$(wc -c < "${payload_datei}" 2>/dev/null | awk '{print $1+0}')"
	query_anzahl="$(jq -r 'if .payload_typ=="query_chunk" then (.sql_abfragen|length) else (.query_anzahl // 0) end' "${payload_datei}" 2>/dev/null || echo "0")"

	nodepilot_log "info" "mariadb_transport ${phase} root_keys=${root_keys} payload_typ=${payload_typ} transfer_id=${transfer_id} chunk_index=${chunk_index} chunk_count=${chunk_count} payload_bytes=${payload_bytes} query_anzahl=${query_anzahl} node_id=${node_id} agent_uuid=${agent_uuid:-leer}."
}

nodepilot_programme_json()
{
	local programme_json_tmp roh_programme_tmp
	programme_json_tmp="$(mktemp "${NODEPILOT_TMP_DIR}/programme_json.XXXXXX.json")"
	roh_programme_tmp="$(mktemp "${NODEPILOT_TMP_DIR}/programme_roh.XXXXXX.txt")"
	if ! dpkg-query -W -f='${Package}\t${Version}\t${Architecture}\n' 2>/dev/null | head -n 5000 > "${roh_programme_tmp}"; then
		rm -f "${programme_json_tmp}" "${roh_programme_tmp}" 2>/dev/null || true
		echo '[]'
		return
	fi
	if ! jq -R -s 'split("\n")|map(select(length>0))|map(split("\t"))|map({name:(.[0] // ""),version:(.[1] // ""),architektur:(.[2] // ""),paketquelle:"apt"})|map(select((.name|length)>=2 and (.name|test("[A-Za-z0-9]"))))' "${roh_programme_tmp}" > "${programme_json_tmp}" 2>/dev/null; then
		nodepilot_parser_fehler_loggen "programme_json" "$(cat "${roh_programme_tmp}" 2>/dev/null || true)"
		rm -f "${programme_json_tmp}" "${roh_programme_tmp}" 2>/dev/null || true
		echo '[]'
		return
	fi
	jq -c . "${programme_json_tmp}" 2>/dev/null || echo '[]'
	rm -f "${programme_json_tmp}" "${roh_programme_tmp}" 2>/dev/null || true
}

nodepilot_befehlsergebnisse_laden()
{
	local datei
	datei="$(nodepilot_ini_wert "lokal" "befehlsergebnisse_datei" "/var/lib/nodepilot/befehlsergebnisse.json")"
	local lock_datei
	lock_datei="$(nodepilot_lockfile_pfad)"
	if command -v flock >/dev/null 2>&1; then
		exec 9>"${lock_datei}"
		flock -s 9
		if [ -f "${datei}" ]; then
			if jq -e 'type=="array"' "${datei}" >/dev/null 2>&1; then
				cat "${datei}"
			else
				nodepilot_log "warn" "befehlsergebnisse_laden: ungueltiges JSON in ${datei}; liefere []."
				nodepilot_parser_fehler_loggen "befehlsergebnisse_laden" "$(cat "${datei}" 2>/dev/null || true)"
				echo '[]'
			fi
		else
			echo '[]'
		fi
		flock -u 9
		exec 9>&-
		return
	fi
	if [ -f "${datei}" ]; then
		if jq -e 'type=="array"' "${datei}" >/dev/null 2>&1; then
			cat "${datei}"
		else
			nodepilot_log "warn" "befehlsergebnisse_laden: ungueltiges JSON in ${datei}; liefere []."
			nodepilot_parser_fehler_loggen "befehlsergebnisse_laden" "$(cat "${datei}" 2>/dev/null || true)"
			echo '[]'
		fi
	else
		echo '[]'
	fi
}

nodepilot_befehlsergebnisse_leeren()
{
	local datei
	datei="$(nodepilot_ini_wert "lokal" "befehlsergebnisse_datei" "/var/lib/nodepilot/befehlsergebnisse.json")"
	local lock_datei
	lock_datei="$(nodepilot_lockfile_pfad)"
	if command -v flock >/dev/null 2>&1; then
		exec 9>"${lock_datei}"
		flock -x 9
		echo '[]' > "${datei}"
		chmod 0640 "${datei}"
		flock -u 9
		exec 9>&-
		return
	fi
	echo '[]' > "${datei}"
	chmod 0640 "${datei}"
}

nodepilot_befehlsergebnis_speichern()
{
	local item="$1"
	local datei
	datei="$(nodepilot_ini_wert "lokal" "befehlsergebnisse_datei" "/var/lib/nodepilot/befehlsergebnisse.json")"
	local lock_datei
	lock_datei="$(nodepilot_lockfile_pfad)"
	local temp_datei item_datei bestehend_datei
	temp_datei="$(mktemp "${NODEPILOT_TMP_DIR}/queue.XXXXXX.json")"
	item_datei="$(mktemp "${NODEPILOT_TMP_DIR}/queue-item.XXXXXX.json")"
	bestehend_datei="$(mktemp "${NODEPILOT_TMP_DIR}/queue-bestand.XXXXXX.json")"
	printf '%s' "${item}" > "${item_datei}"
	if ! jq -e 'type=="object"' "${item_datei}" >/dev/null 2>&1; then
		nodepilot_log "warn" "befehlsergebnisse_speichern: Teilpayload ungueltig; Eintrag verworfen quelle=item_json."
		nodepilot_parser_fehler_loggen "befehlsergebnisse_item" "${item}"
		rm -f "${temp_datei}" "${item_datei}" "${bestehend_datei}" 2>/dev/null || true
		return
	fi
	if command -v flock >/dev/null 2>&1; then
		exec 9>"${lock_datei}"
		flock -x 9
		if [ -f "${datei}" ] && jq -e 'type=="array"' "${datei}" >/dev/null 2>&1; then
			cp "${datei}" "${bestehend_datei}"
		else
			[ -f "${datei}" ] && nodepilot_parser_fehler_loggen "befehlsergebnisse_bestand" "$(cat "${datei}" 2>/dev/null || true)"
			echo '[]' > "${bestehend_datei}"
		fi
		jq -s -c '.[0] + [.[1]]' "${bestehend_datei}" "${item_datei}" > "${temp_datei}" 2>/dev/null || {
			nodepilot_log "error" "befehlsergebnisse_speichern: JSON-Zusammenfuehrung fehlgeschlagen; Bestand bleibt unveraendert."
			rm -f "${temp_datei}" "${item_datei}" "${bestehend_datei}" 2>/dev/null || true
			flock -u 9
			exec 9>&-
			return
		}
		mv "${temp_datei}" "${datei}"
		chmod 0640 "${datei}"
		rm -f "${item_datei}" "${bestehend_datei}" 2>/dev/null || true
		flock -u 9
		exec 9>&-
		return
	fi
	if [ -f "${datei}" ] && jq -e 'type=="array"' "${datei}" >/dev/null 2>&1; then
		cp "${datei}" "${bestehend_datei}"
	else
		[ -f "${datei}" ] && nodepilot_parser_fehler_loggen "befehlsergebnisse_bestand" "$(cat "${datei}" 2>/dev/null || true)"
		echo '[]' > "${bestehend_datei}"
	fi
	jq -s -c '.[0] + [.[1]]' "${bestehend_datei}" "${item_datei}" > "${temp_datei}" 2>/dev/null || {
		nodepilot_log "error" "befehlsergebnisse_speichern: JSON-Zusammenfuehrung fehlgeschlagen; Bestand bleibt unveraendert."
		rm -f "${temp_datei}" "${item_datei}" "${bestehend_datei}" 2>/dev/null || true
		return
	}
	mv "${temp_datei}" "${datei}"
	chmod 0640 "${datei}"
	rm -f "${item_datei}" "${bestehend_datei}" 2>/dev/null || true
}


nodepilot_text_begrenzen()
{
	local text="$1"
	local max_len="$2"
	if [ -z "${max_len}" ] || [ "${max_len}" -le 0 ] 2>/dev/null; then
		max_len=4096
	fi
	if [ "${#text}" -le "${max_len}" ]; then
		echo "${text}"
		return
	fi
	echo "${text:0:${max_len}}
[gekuerzt durch NodePilot-Agent]"
}

nodepilot_script_aufloesung_json()
{
	local script_name="$1"
	local script_path="$2"
	local befehls_verzeichnis
	befehls_verzeichnis="$(nodepilot_ini_wert "lokal" "befehls_verzeichnis" "/usr/lib/nodepilot/agent/scripts")"
	local -a kandidaten=()

	if [ -n "${script_path}" ]; then

		if [[ "${script_path}" = /* ]]; then
			kandidaten+=("${script_path}")
		else
			kandidaten+=("${befehls_verzeichnis}/${script_path}")
		fi
	fi

	if [ -n "${script_name}" ]; then

		if [[ "${script_name}" = /* ]]; then
			kandidaten+=("${script_name}")
		else
			kandidaten+=("${befehls_verzeichnis}/${script_name}")
		fi
	fi

	local -a final_kandidaten=()
	local kandidat
	for kandidat in "${kandidaten[@]}"; do

		if [ -z "${kandidat}" ]; then
			continue
		fi
		if [[ "${kandidat}" = *".."* ]]; then
			continue
		fi
		final_kandidaten+=("${kandidat}")
		if [[ "${kandidat}" != *.sh ]]; then
			final_kandidaten+=("${kandidat}.sh")
		fi
	done

	local beste_ursache="Script-Pfad nicht gesetzt."
	local -a gepruefte_kandidaten=()
	for kandidat in "${final_kandidaten[@]}"; do

		if [ ! -f "${kandidat}" ]; then
			gepruefte_kandidaten+=("${kandidat}")
			beste_ursache="Script-Datei nicht gefunden: ${kandidat}"
			continue
		fi

		if [ ! -x "${kandidat}" ]; then
			gepruefte_kandidaten+=("${kandidat}")
			beste_ursache="Script-Datei ist nicht ausfuehrbar: ${kandidat}"
			continue
		fi

		jq -nc --arg pfad "${kandidat}" --arg runner "direkt" --arg fehler "" '{ok:true,pfad:$pfad,runner:$runner,fehler:$fehler}'
		return 0
	done

	if [ "${#gepruefte_kandidaten[@]}" -gt 0 ]; then
		beste_ursache="${beste_ursache} (Gepruefte Kandidaten: ${gepruefte_kandidaten[*]})"
	fi

	jq -nc --arg fehler "${beste_ursache}" '{ok:false,pfad:"",runner:"",fehler:$fehler}'
}

nodepilot_befehl_einzeln_ausfuehren()
{
	local befehl="$1"
	local quelle="$2"
	local lauf_id node_befehl_id timeout script_name script_path parameter_json befehl_key lauf_befehl_text lauf_parameter_text
	lauf_id="$(echo "${befehl}" | jq -r '.lauf_id // 0')"
	node_befehl_id="$(echo "${befehl}" | jq -r '.node_befehl_id // .befehl_global_id // 0')"
	timeout="$(echo "${befehl}" | jq -r '.timeout_sekunden // 30')"
	script_name="$(echo "${befehl}" | jq -r '.script_name // ""')"
	script_path="$(echo "${befehl}" | jq -r '.script_path // ""')"
	lauf_befehl_text="$(echo "${befehl}" | jq -r '.lauf_befehl_text // ""')"
	lauf_parameter_text="$(echo "${befehl}" | jq -r '.lauf_parameter_text // ""')"
	befehl_key="$(echo "${befehl}" | jq -r '.befehl_key // .key // "unbekannt"')"
	parameter_json="$(echo "${befehl}" | jq -c '.parameter // .parameter_json // {}' 2>/dev/null || echo '{}')"

	if ! [[ "${timeout}" =~ ^[0-9]+$ ]]; then
		timeout=30
	fi
	if [ "${timeout}" -lt 1 ]; then
		timeout=1
	fi
	if [ "${timeout}" -gt 3600 ]; then
		timeout=3600
	fi

	local resolve_json pfad runner
	local direkt_befehl_aktiv="nein"

	if [ -z "${lauf_befehl_text}" ] && { [ "${befehl_key}" = "node_direkt_befehl" ] || [ "${befehl_key}" = "direkt_befehl" ]; }; then
		local parameter_direkt_befehl_text parameter_direkt_parameter_text
		parameter_direkt_befehl_text="$(echo "${parameter_json}" | jq -r '.direkt_befehl_text // ""' 2>/dev/null || echo "")"
		parameter_direkt_parameter_text="$(echo "${parameter_json}" | jq -r '.direkt_parameter_text // ""' 2>/dev/null || echo "")"

		if [ -n "${parameter_direkt_befehl_text}" ]; then
			lauf_befehl_text="${parameter_direkt_befehl_text}"
		fi

		if [ -z "${lauf_parameter_text}" ] && [ -n "${parameter_direkt_parameter_text}" ]; then
			lauf_parameter_text="${parameter_direkt_parameter_text}"
		fi
	fi

	if [ -n "${lauf_befehl_text}" ]; then
		direkt_befehl_aktiv="ja"
		pfad=""
		runner="shell"
	else
		resolve_json="$(nodepilot_script_aufloesung_json "${script_name}" "${script_path}")"
		pfad="$(echo "${resolve_json}" | jq -r '.pfad // ""')"
		runner="$(echo "${resolve_json}" | jq -r '.runner // ""')"
	fi

	local start_ms end_ms exit_code stdout stderr status stdout_json_text stdout_text
	local fehler_text=""
	start_ms="$(date +%s%3N)"

	if [ "${direkt_befehl_aktiv}" = "ja" ]; then
		local stderr_datei direkt_kommando
		stderr_datei="$(mktemp "${NODEPILOT_TMP_DIR}/stderr.XXXXXX")"
		direkt_kommando="${lauf_befehl_text}"
		if [ -n "${lauf_parameter_text}" ]; then
			direkt_kommando="${direkt_kommando} ${lauf_parameter_text}"
		fi
		nodepilot_log "info" "Direktbefehl gestartet (${quelle}/${befehl_key}): ${direkt_kommando}"
		set +e
		stdout="$(timeout "${timeout}" bash -lc "${direkt_kommando}" 2>"${stderr_datei}")"
		exit_code=$?
		set -e
		stderr="$(cat "${stderr_datei}" 2>/dev/null || true)"
		rm -f "${stderr_datei}"
		if [ "${exit_code}" -eq 124 ] && [ -z "${stderr}" ]; then
			stderr="Befehl nach Timeout (${timeout}s) beendet."
		fi
	else
		if [ "$(echo "${resolve_json}" | jq -r '.ok // false')" != "true" ]; then
			stdout=""
			stderr="$(echo "${resolve_json}" | jq -r '.fehler // "Script-Aufloesung fehlgeschlagen."')"
			fehler_text="${stderr}"
			exit_code=127
			nodepilot_log "error" "Job fehlgeschlagen (${quelle}/${befehl_key}): ${stderr}"
		else
			local stderr_datei
			stderr_datei="$(mktemp "${NODEPILOT_TMP_DIR}/stderr.XXXXXX")"
			nodepilot_log "info" "Job gestartet (${quelle}/${befehl_key}): ${pfad}"
			set +e
			if [ "${runner}" = "bash" ]; then
				stdout="$(timeout "${timeout}" bash "${pfad}" "${parameter_json}" 2>"${stderr_datei}")"
			else
				stdout="$(timeout "${timeout}" "${pfad}" "${parameter_json}" 2>"${stderr_datei}")"
			fi
			exit_code=$?
			set -e
			stderr="$(cat "${stderr_datei}" 2>/dev/null || true)"
			rm -f "${stderr_datei}"
			if [ "${exit_code}" -eq 124 ] && [ -z "${stderr}" ]; then
				stderr="Befehl nach Timeout (${timeout}s) beendet."
			fi
		fi
	fi

	end_ms="$(date +%s%3N)"
	stdout_text="$(nodepilot_text_begrenzen "${stdout}" 64000)"
	stderr="$(nodepilot_text_begrenzen "${stderr}" 32000)"
	stdout_json_text=""
	if echo "${stdout}" | jq -e 'type=="object" or type=="array"' >/dev/null 2>&1; then
		stdout_json_text="$(echo "${stdout}" | jq -c . 2>/dev/null || echo "")"
	fi
	if [ "${exit_code}" -eq 0 ]; then
		local mariadb_fehler_text
		mariadb_fehler_text="$(nodepilot_befehl_fachstatus_mariadb_pruefen "${script_name}" "${script_path}" "${stdout_json_text}" || true)"
		if [ -n "${mariadb_fehler_text}" ]; then
			exit_code=2
			fehler_text="${mariadb_fehler_text}"
			if [ -n "${stderr}" ]; then
				stderr="${stderr}
${mariadb_fehler_text}"
			else
				stderr="${mariadb_fehler_text}"
			fi
			nodepilot_log "warn" "${mariadb_fehler_text}"
		fi
	fi
	if [ "${exit_code}" -eq 0 ]; then
		status="ok"
	else
		status="fehler"
	fi
	if [ "${exit_code}" -eq 124 ]; then
		status="timeout"
	fi
	if [ -z "${fehler_text}" ] && [ "${exit_code}" -ne 0 ]; then
		fehler_text="${stderr}"
	fi

	local payload
	local node_id_lokal script_quelle
	node_id_lokal="$(nodepilot_ini_wert "auth" "node_id" "0")"
	if [ "${direkt_befehl_aktiv}" = "ja" ]; then
		script_quelle="direkt_befehl_shell"
	else
		script_quelle="${pfad}"
	fi
	payload="$(jq -n 		--argjson lauf_id "${lauf_id}" 		--argjson node_befehl_id "${node_befehl_id}" 		--arg status "${status}" 		--argjson exit_code "${exit_code}" 		--arg stdout_json "${stdout_json_text}" 		--arg stdout_text "${stdout_text}" 		--arg stderr_text "${stderr}" 		--arg fehler_text "${fehler_text}" 		--argjson laufzeit_ms "$((end_ms-start_ms))" 		--arg gestartet "$(date -Iseconds -d @$((${start_ms}/1000)))" 		--arg beendet "$(date -Iseconds -d @$((${end_ms}/1000)))" 		'{lauf_id:$lauf_id,node_befehl_id:$node_befehl_id,status:$status,exit_code:$exit_code,stdout_json:$stdout_json,stdout_text:$stdout_text,stderr_text:$stderr_text,fehler_text:$fehler_text,laufzeit_ms:$laufzeit_ms,gestartet:$gestartet,beendet:$beendet}')"
	payload="$(echo "${payload}" | jq -c \
		--arg befehl_key "${befehl_key}" \
		--arg quelle "${quelle}" \
		--arg script_name "${script_name}" \
		--arg script_path "${script_path}" \
		--arg script_quelle "${script_quelle}" \
		--argjson node_id "$(nodepilot_zahl_oder_fallback "${node_id_lokal}" "0" "command/node_id")" \
		--arg parameter_json "${parameter_json}" \
		--arg lauf_befehl_text "${lauf_befehl_text}" \
		--arg lauf_parameter_text "${lauf_parameter_text}" \
		'. + {befehl_key:$befehl_key,quelle:$quelle,node_id:$node_id,script_name:$script_name,script_path:$script_path,script_quelle:$script_quelle,parameter_json:$parameter_json,lauf_befehl_text:$lauf_befehl_text,lauf_parameter_text:$lauf_parameter_text}' 2>/dev/null || echo "${payload}")"
	nodepilot_befehlsergebnis_speichern "${payload}"
	nodepilot_befehl_letzte_response_schreiben "${befehl_key}" "${payload}" || true
	nodepilot_log "info" "Job beendet (${quelle}/${befehl_key}): status=${status}, exit_code=${exit_code}"

	if [ "${status}" = "ok" ] && [ "${befehl_key}" = "nodepilot_install_update" ]; then
		local neue_version
		neue_version="$(nodepilot_agent_version_synchronisieren)"
		nodepilot_log "info" "Update-Lauf abgeschlossen; Version neu erkannt=${neue_version}. Sofort-Poll + Update-Check werden ausgeloest."
		nodepilot_poll "schnellkern" || true
		nodepilot_update_check || true
	fi
}

nodepilot_befehle_ausfuehren()
{
	local antwort_json="$1"
	local befehle_liste
	befehle_liste="$(echo "${antwort_json}" | jq -c '.befehle[]?' 2>/dev/null || true)"
	if [ -z "${befehle_liste}" ]; then
		nodepilot_log "debug" "Keine gueltigen Befehle in Poll-Antwort vorhanden."
		return
	fi
	echo "${befehle_liste}" | while IFS= read -r befehl; do
		nodepilot_befehl_einzeln_ausfuehren "${befehl}" "manuell"
	done
}

nodepilot_agent_config_meta_setzen()
{
	local key="$1"
	local value="$2"
	install -d -m 0750 "${NODEPILOT_TMP_DIR}" "$(dirname "${NODEPILOT_AGENT_CONFIG_META_DATEI}")"
	if [ ! -f "${NODEPILOT_AGENT_CONFIG_META_DATEI}" ]; then
		echo '{}' > "${NODEPILOT_AGENT_CONFIG_META_DATEI}"
	fi
	local tmp
	tmp="$(mktemp "${NODEPILOT_TMP_DIR}/config-meta.XXXXXX" 2>/dev/null || true)"
	if [ -z "${tmp}" ]; then
		nodepilot_log "error" "Agent-Config-Meta: mktemp fehlgeschlagen (${NODEPILOT_TMP_DIR}/config-meta.XXXXXX)."
		return 1
	fi
	jq --arg key "${key}" --arg value "${value}" '.[$key]=$value' "${NODEPILOT_AGENT_CONFIG_META_DATEI}" > "${tmp}" 2>/dev/null || echo '{}' > "${tmp}"
	if mv "${tmp}" "${NODEPILOT_AGENT_CONFIG_META_DATEI}" 2>/dev/null; then
		:
	else
		nodepilot_log "error" "Agent-Config-Meta: mv auf ${NODEPILOT_AGENT_CONFIG_META_DATEI} fehlgeschlagen."
		rm -f "${tmp}" 2>/dev/null || true
		return 1
	fi
	chmod 0640 "${NODEPILOT_AGENT_CONFIG_META_DATEI}" 2>/dev/null || true
}

nodepilot_agent_state_laden()
{
	install -d -m 0750 "$(dirname "${NODEPILOT_AGENT_STATE_DATEI}")" "${NODEPILOT_TMP_DIR}"
	if [ ! -f "${NODEPILOT_AGENT_STATE_DATEI}" ]; then
		NODEPILOT_AGENT_STATE_JSON='{}'
		return
	fi
	local state_roh state_json
	state_roh="$(cat "${NODEPILOT_AGENT_STATE_DATEI}" 2>/dev/null || echo '')"
	state_json="$(echo "${state_roh}" | jq -c 'if type=="object" then . else {} end' 2>/dev/null || echo '{}')"
	NODEPILOT_AGENT_STATE_JSON="${state_json}"
}

nodepilot_agent_state_speichern()
{
	install -d -m 0750 "$(dirname "${NODEPILOT_AGENT_STATE_DATEI}")" "${NODEPILOT_TMP_DIR}"
	local tmp
	tmp="$(mktemp "${NODEPILOT_TMP_DIR}/agent-state.XXXXXX" 2>/dev/null || true)"
	if [ -z "${tmp}" ]; then
		nodepilot_log "error" "Agent-State: mktemp fehlgeschlagen."
		return 1
	fi
	echo "${NODEPILOT_AGENT_STATE_JSON}" > "${tmp}"
	if mv "${tmp}" "${NODEPILOT_AGENT_STATE_DATEI}" 2>/dev/null; then
		chmod 0640 "${NODEPILOT_AGENT_STATE_DATEI}" 2>/dev/null || true
		return 0
	fi
	rm -f "${tmp}" 2>/dev/null || true
	nodepilot_log "error" "Agent-State: Speichern fehlgeschlagen (${NODEPILOT_AGENT_STATE_DATEI})."
	return 1
}

nodepilot_agent_state_wert_lesen()
{
	local jq_pfad="$1"
	echo "${NODEPILOT_AGENT_STATE_JSON}" | jq -r "${jq_pfad} // empty" 2>/dev/null || echo ""
}

nodepilot_agent_state_block_setzen()
{
	local block="$1"
	local feld="$2"
	local wert="$3"
	NODEPILOT_AGENT_STATE_JSON="$(echo "${NODEPILOT_AGENT_STATE_JSON}" | jq -c --arg block "${block}" --arg feld "${feld}" --arg wert "${wert}" '.blocks = (.blocks // {}) | .blocks[$block] = (.blocks[$block] // {}) | .blocks[$block][$feld]=$wert' 2>/dev/null || echo "${NODEPILOT_AGENT_STATE_JSON}")"
}

nodepilot_agent_state_block_setzen_json()
{
	local block="$1"
	local feld="$2"
	local wert_json="$3"
	NODEPILOT_AGENT_STATE_JSON="$(echo "${NODEPILOT_AGENT_STATE_JSON}" | jq -c --arg block "${block}" --arg feld "${feld}" --argjson wert "${wert_json}" '.blocks = (.blocks // {}) | .blocks[$block] = (.blocks[$block] // {}) | .blocks[$block][$feld]=$wert' 2>/dev/null || echo "${NODEPILOT_AGENT_STATE_JSON}")"
}

nodepilot_agent_block_config_laden()
{
	local block="$1"
	echo "${NODEPILOT_AGENT_CONFIG_JSON}" | jq -c --arg block "${block}" '.blocks[$block] // empty' 2>/dev/null || true
}

nodepilot_agent_block_faellig()
{
	local block="$1"
	local config
	config="$(nodepilot_agent_block_config_laden "${block}")"
	if [ -z "${config}" ]; then
		nodepilot_log "warn" "Block ${block}: keine Config vorhanden, Block wird ausgelassen."
		return 1
	fi
	local aktiv intervall
	aktiv="$(echo "${config}" | jq -r '.aktiv // empty' 2>/dev/null || echo "")"
	intervall="$(echo "${config}" | jq -r '.intervall_sekunden // empty' 2>/dev/null || echo "")"
	if [ "${aktiv}" != "true" ]; then
		return 1
	fi
	if ! [[ "${intervall}" =~ ^[0-9]+$ ]] || [ "${intervall}" -le 0 ]; then
		nodepilot_log "warn" "Block ${block}: ungueltige oder fehlende intervall_sekunden in Config."
		return 1
	fi
	local letzter now
	letzter="$(nodepilot_agent_state_wert_lesen ".blocks.\"${block}\".letzte_erfolgreiche_sammlung_unix")"
	now="$(date +%s)"
	if [[ "${letzter}" =~ ^[0-9]+$ ]]; then
		if [ $((now - letzter)) -lt "${intervall}" ]; then
			return 1
		fi
	fi
	return 0
}

nodepilot_agent_block_payload_pruefen_und_merken()
{
	local block="$1"
	local daten_json="$2"
	local config
	config="$(nodepilot_agent_block_config_laden "${block}")"
	if [ -z "${config}" ]; then
		return 1
	fi
	local nur_bei_aenderung vollsync_max_sekunden
	nur_bei_aenderung="$(echo "${config}" | jq -r '.nur_bei_aenderung // false' 2>/dev/null || echo "false")"
	vollsync_max_sekunden="$(echo "${config}" | jq -r '.vollsync_max_sekunden // empty' 2>/dev/null || echo "")"
	local hash letzter_hash jetzt letzter_sendung
	hash="$(echo "${daten_json}" | jq -c . 2>/dev/null | sha1sum | awk '{print $1}')"
	jetzt="$(date +%s)"
	letzter_hash="$(nodepilot_agent_state_wert_lesen ".blocks.\"${block}\".letzter_payload_hash")"
	letzter_sendung="$(nodepilot_agent_state_wert_lesen ".blocks.\"${block}\".letzte_sendung_unix")"
	if [ "${nur_bei_aenderung}" = "true" ] && [ -n "${letzter_hash}" ] && [ "${letzter_hash}" = "${hash}" ]; then
		if [[ "${vollsync_max_sekunden}" =~ ^[0-9]+$ ]] && [ "${vollsync_max_sekunden}" -gt 0 ] && [[ "${letzter_sendung}" =~ ^[0-9]+$ ]]; then
			if [ $((jetzt - letzter_sendung)) -ge "${vollsync_max_sekunden}" ]; then
				:
			else
				return 1
			fi
		else
			return 1
		fi
	fi
	nodepilot_agent_state_block_setzen "${block}" "letzter_payload_hash" "${hash}"
	nodepilot_agent_state_block_setzen "${block}" "letzte_sendung_unix" "${jetzt}"
	return 0
}

nodepilot_agent_config_validieren_und_normalisieren()
{
	local config_json="$1"
	echo "${config_json}" | jq -c '
		if (type != "object") then error("config kein Objekt") else . end
		| {
			version: (.version // "unbekannt" | tostring),
			hash: (.hash // "" | tostring),
			pull_interval_sekunden: ((.pull_interval_sekunden // 600) | tonumber | floor),
			blocks: ((.blocks // {}) | if type=="object" then . else {} end | with_entries(
				.value = {
					aktiv: ((.value.aktiv // false) | if type=="boolean" then . else false end),
					intervall_sekunden: (.value.intervall_sekunden // null),
					nur_bei_aenderung: ((.value.nur_bei_aenderung // false) | if type=="boolean" then . else false end),
					vollsync_max_sekunden: (.value.vollsync_max_sekunden // null)
				}
			)),
			befehle: ((.befehle // []) | if type=="array" then . else [] end | map({
				befehl_global_id: (.befehl_global_id // 0 | tonumber | floor),
				node_befehl_id: (.node_befehl_id // .befehl_global_id // 0 | tonumber | floor),
				befehl_key: (.befehl_key // .key // "" | tostring),
				aktiv: ((.aktiv // false) | if type=="boolean" then . else false end),
				script_name: (.script_name // "" | tostring),
				script_path: (.script_path // "" | tostring),
				intervall_sekunden: ((.intervall_sekunden // 0) | tonumber | floor),
				timeout_sekunden: ((.timeout_sekunden // 30) | tonumber | floor),
				prioritaet: ((.prioritaet // 0) | tonumber | floor),
				parallel_erlaubt: ((.parallel_erlaubt // false) | if type=="boolean" then . else false end),
				parameter: (.parameter // .parameter_json // {})
			}))
		}
		| .pull_interval_sekunden = (if .pull_interval_sekunden < 30 then 30 else .pull_interval_sekunden end)
		| .blocks |= with_entries(
			.value.intervall_sekunden = (if (.value.intervall_sekunden|type)=="number" then (.value.intervall_sekunden|floor) else null end)
			| .value.vollsync_max_sekunden = (if (.value.vollsync_max_sekunden|type)=="number" then (.value.vollsync_max_sekunden|floor) else null end)
		)
		| .befehle |= map(.intervall_sekunden = (if .intervall_sekunden < 0 then 0 else .intervall_sekunden end))
	' 2>/dev/null
}

nodepilot_agent_config_lokal_laden()
{
	if [ ! -f "${NODEPILOT_AGENT_CONFIG_DATEI}" ]; then
		nodepilot_log "warn" "Keine lokale Agent-Config vorhanden; nutze Default-Konfiguration."
		return
	fi
	local config_roh config_json
	config_roh="$(cat "${NODEPILOT_AGENT_CONFIG_DATEI}" 2>/dev/null || echo '')"
	if [ -z "${config_roh}" ]; then
		nodepilot_log "warn" "Lokale Agent-Config ist leer; nutze Default-Konfiguration."
		return
	fi
	config_json="$(nodepilot_agent_config_validieren_und_normalisieren "${config_roh}" || true)"
	if [ -z "${config_json}" ]; then
		nodepilot_log "error" "Lokale Agent-Config ist ungueltig; nutze letzte aktive In-Memory-Konfiguration."
		return
	fi
	NODEPILOT_AGENT_CONFIG_JSON="${config_json}"
	NODEPILOT_AGENT_CONFIG_VERSION="$(echo "${config_json}" | jq -r '.version // ""')"
	NODEPILOT_AGENT_CONFIG_HASH="$(echo "${config_json}" | jq -r '.hash // ""')"
	NODEPILOT_AGENT_PULL_INTERVAL="$(echo "${config_json}" | jq -r '.pull_interval_sekunden // 600')"
	nodepilot_log "info" "Lokale Agent-Config geladen: version=${NODEPILOT_AGENT_CONFIG_VERSION}, hash=${NODEPILOT_AGENT_CONFIG_HASH}, pull=${NODEPILOT_AGENT_PULL_INTERVAL}s"
}

nodepilot_agent_scheduler_neuaufbauen()
{
	NODEPILOT_SCHEDULER_JOB_IDS=()
	NODEPILOT_SCHEDULER_JOB_NEXT_RUN=()
	NODEPILOT_SCHEDULER_JOB_PARALLEL=()
	NODEPILOT_SCHEDULER_JOB_TIMEOUT=()
	NODEPILOT_SCHEDULER_JOB_DEFINITION=()
	local now
	now="$(date +%s)"
	local -a befehle_liste=()
	mapfile -t befehle_liste < <(echo "${NODEPILOT_AGENT_CONFIG_JSON}" | jq -c '.befehle // [] | sort_by(-(.prioritaet // 0))[]?' 2>/dev/null || true)
	if [ "${#befehle_liste[@]}" -eq 0 ]; then
		nodepilot_log "info" "Scheduler aktiv, aber keine automatischen Befehle in der Config vorhanden."
		return
	fi
	local befehl
	for befehl in "${befehle_liste[@]}"; do
		local aktiv intervall key timeout parallel
		aktiv="$(echo "${befehl}" | jq -r '.aktiv // false')"
		intervall="$(echo "${befehl}" | jq -r '.intervall_sekunden // 0')"
		key="$(echo "${befehl}" | jq -r '.befehl_key // ""')"
		timeout="$(echo "${befehl}" | jq -r '.timeout_sekunden // 30')"
		parallel="$(echo "${befehl}" | jq -r '.parallel_erlaubt // false')"
		if [ "${aktiv}" != "true" ]; then
			continue
		fi
		if ! [[ "${intervall}" =~ ^[0-9]+$ ]] || [ "${intervall}" -le 0 ]; then
			continue
		fi
		if [ -z "${key}" ]; then
			key="job_$(echo "${befehl}" | jq -r '.befehl_global_id // .node_befehl_id // 0')"
		fi
		NODEPILOT_SCHEDULER_JOB_IDS+=("${key}")
		NODEPILOT_SCHEDULER_JOB_NEXT_RUN["${key}"]="$((now + intervall))"
		NODEPILOT_SCHEDULER_JOB_PARALLEL["${key}"]="${parallel}"
		NODEPILOT_SCHEDULER_JOB_TIMEOUT["${key}"]="${timeout}"
		NODEPILOT_SCHEDULER_JOB_DEFINITION["${key}"]="${befehl}"
		nodepilot_log "info" "Scheduler-Job eingeplant: key=${key}, intervall=${intervall}s, parallel=${parallel}"
	done
}

nodepilot_agent_scheduler_tick()
{
	local now key
	now="$(date +%s)"
	for key in "${NODEPILOT_SCHEDULER_JOB_IDS[@]}"; do

		local running_pid laeuft_aktiv="nein"
		running_pid="${NODEPILOT_SCHEDULER_JOB_RUNNING_PID[${key}]:-}"
		if [ -n "${running_pid}" ]; then
			if kill -0 "${running_pid}" 2>/dev/null; then
				laeuft_aktiv="ja"
			else
				wait "${running_pid}" 2>/dev/null || true
				unset NODEPILOT_SCHEDULER_JOB_RUNNING_PID["${key}"]
				unset NODEPILOT_SCHEDULER_JOB_RUNNING_SEIT["${key}"]
			fi
		fi

		local naechster_lauf
		naechster_lauf="${NODEPILOT_SCHEDULER_JOB_NEXT_RUN[${key}]:-0}"
		if [ "${now}" -lt "${naechster_lauf}" ]; then
			continue
		fi

		if [ "${laeuft_aktiv}" = "ja" ] && [ "${NODEPILOT_SCHEDULER_JOB_PARALLEL[${key}]:-false}" != "true" ]; then
			nodepilot_log "info" "Job ausgelassen wegen laufender Sperre: ${key}"
			NODEPILOT_SCHEDULER_JOB_NEXT_RUN["${key}"]="$((now + 1))"
			continue
		fi

		local befehl_json
		befehl_json="${NODEPILOT_SCHEDULER_JOB_DEFINITION[${key}]}"
		(
			nodepilot_befehl_einzeln_ausfuehren "${befehl_json}" "automatisch"
		) &
		NODEPILOT_SCHEDULER_JOB_RUNNING_PID["${key}"]="$!"
		NODEPILOT_SCHEDULER_JOB_RUNNING_SEIT["${key}"]="${now}"
		local intervall
		intervall="$(echo "${befehl_json}" | jq -r '.intervall_sekunden // 60')"
		if ! [[ "${intervall}" =~ ^[0-9]+$ ]] || [ "${intervall}" -lt 1 ]; then
			intervall=60
		fi
		NODEPILOT_SCHEDULER_JOB_NEXT_RUN["${key}"]="$((now + intervall))"
	done
}

nodepilot_agent_config_lokal_speichern_und_aktivieren()
{
	local config_json="$1"
	echo "${config_json}" > "${NODEPILOT_AGENT_CONFIG_DATEI}"
	chmod 0640 "${NODEPILOT_AGENT_CONFIG_DATEI}" 2>/dev/null || true
	NODEPILOT_AGENT_CONFIG_JSON="${config_json}"
	NODEPILOT_AGENT_CONFIG_VERSION="$(echo "${config_json}" | jq -r '.version // ""')"
	NODEPILOT_AGENT_CONFIG_HASH="$(echo "${config_json}" | jq -r '.hash // ""')"
	NODEPILOT_AGENT_PULL_INTERVAL="$(echo "${config_json}" | jq -r '.pull_interval_sekunden // 600')"
	nodepilot_agent_config_meta_setzen "config_version" "${NODEPILOT_AGENT_CONFIG_VERSION}"
	nodepilot_agent_config_meta_setzen "config_hash" "${NODEPILOT_AGENT_CONFIG_HASH}"
	nodepilot_agent_config_meta_setzen "zuletzt_erfolgreich_geladen" "$(date -Iseconds)"
	nodepilot_agent_config_meta_setzen "letzter_fehler" ""
	nodepilot_agent_scheduler_neuaufbauen
}

nodepilot_agent_config_vom_server_pullen()
{
	nodepilot_log "info" "Config-Pull gestartet."
	nodepilot_agent_config_meta_setzen "letzter_pull_versuch" "$(date -Iseconds)"
	local payload
	payload="$(jq -n 		--arg agent_uuid "$(nodepilot_ini_wert "agent" "agent_uuid" "")" 		--argjson node_id "$(nodepilot_ini_wert "auth" "node_id" "0")" 		--arg config_version "${NODEPILOT_AGENT_CONFIG_VERSION}" 		--arg config_hash "${NODEPILOT_AGENT_CONFIG_HASH}" 		'{agent_uuid:$agent_uuid,node_id:$node_id,config_version:$config_version,config_hash:$config_hash}')"
	local antwort
	antwort="$(nodepilot_retry_post "$(nodepilot_ini_wert "server" "api_agent_config_endpoint" "/agent/config")" "${payload}" "agent-config")" || {
		local fehlermeldung="Config-Pull fehlgeschlagen: HTTP/Transportfehler."
		nodepilot_log "error" "${fehlermeldung}"
		nodepilot_agent_config_meta_setzen "letzter_fehler" "${fehlermeldung}"
		NODEPILOT_AGENT_NEXT_PULL_UNIX="$(( $(date +%s) + NODEPILOT_AGENT_PULL_INTERVAL ))"
		return 1
	}
	antwort="$(nodepilot_api_antwort_json_normieren "agent-config" "${antwort}")" || {
		local fehlermeldung="Config-Pull fehlgeschlagen: ungueltige Serverantwort."
		nodepilot_log "error" "${fehlermeldung}"
		nodepilot_agent_config_meta_setzen "letzter_fehler" "${fehlermeldung}"
		NODEPILOT_AGENT_NEXT_PULL_UNIX="$(( $(date +%s) + NODEPILOT_AGENT_PULL_INTERVAL ))"
		return 1
	}

	local config_json
	config_json="$(echo "${antwort}" | jq -c '.data.config // .config // {}' 2>/dev/null || echo '')"
	config_json="$(nodepilot_agent_config_validieren_und_normalisieren "${config_json}" || true)"
	if [ -z "${config_json}" ]; then
		local fehlermeldung="Config-Pull fehlgeschlagen: Payload ohne gueltige effektive Config."
		nodepilot_log "error" "${fehlermeldung}"
		nodepilot_agent_config_meta_setzen "letzter_fehler" "${fehlermeldung}"
		NODEPILOT_AGENT_NEXT_PULL_UNIX="$(( $(date +%s) + NODEPILOT_AGENT_PULL_INTERVAL ))"
		return 1
	fi

	local neue_version neuer_hash
	neue_version="$(echo "${config_json}" | jq -r '.version // ""')"
	neuer_hash="$(echo "${config_json}" | jq -r '.hash // ""')"

	if [ "${neue_version}" = "${NODEPILOT_AGENT_CONFIG_VERSION}" ] && [ "${neuer_hash}" = "${NODEPILOT_AGENT_CONFIG_HASH}" ]; then
		NODEPILOT_AGENT_PULL_INTERVAL="$(echo "${config_json}" | jq -r '.pull_interval_sekunden // 600')"
		nodepilot_agent_config_meta_setzen "zuletzt_erfolgreich_geladen" "$(date -Iseconds)"
		nodepilot_agent_config_meta_setzen "letzter_fehler" ""
		nodepilot_log "info" "Config-Pull erfolgreich, Konfiguration unveraendert (version=${neue_version}, hash=${neuer_hash})."
		NODEPILOT_AGENT_NEXT_PULL_UNIX="$(( $(date +%s) + NODEPILOT_AGENT_PULL_INTERVAL ))"
		return 0
	fi

	nodepilot_agent_config_lokal_speichern_und_aktivieren "${config_json}"
	nodepilot_log "info" "Neue Config uebernommen: version=${NODEPILOT_AGENT_CONFIG_VERSION}, hash=${NODEPILOT_AGENT_CONFIG_HASH}, pull=${NODEPILOT_AGENT_PULL_INTERVAL}s"
	NODEPILOT_AGENT_NEXT_PULL_UNIX="$(( $(date +%s) + NODEPILOT_AGENT_PULL_INTERVAL ))"
	return 0
}


nodepilot_mariadb_cli_ermitteln()
{
	if command -v mariadb >/dev/null 2>&1; then
		echo "mariadb"
		return 0
	fi

	if command -v mysql >/dev/null 2>&1; then
		echo "mysql"
		return 0
	fi

	return 1
}

nodepilot_befehl_fachstatus_mariadb_pruefen()
{
	local script_name="$1"
	local script_path="$2"
	local stdout_json_text="$3"

	local script_basis
	script_basis="$(basename "${script_name:-${script_path}}")"
	if [ "${script_basis}" != "mariadb_status_kurz" ] && [ "${script_basis}" != "mariadb_status_kurz.sh" ] && [ "${script_basis}" != "mariadb_status_detail" ] && [ "${script_basis}" != "mariadb_status_detail.sh" ]; then
		return 0
	fi

	if [ -z "${stdout_json_text}" ]; then
		return 0
	fi

	if ! echo "${stdout_json_text}" | jq -e 'type=="object"' >/dev/null 2>&1; then
		return 0
	fi

	if [ "$(echo "${stdout_json_text}" | jq -r '.verfuegbar // empty')" = "true" ]; then
		return 0
	fi

	local cli_found meldung
	cli_found="$(echo "${stdout_json_text}" | jq -r '.cli // ""')"
	meldung="$(echo "${stdout_json_text}" | jq -r '.meldung // "MariaDB-Fachpruefung fehlgeschlagen."' | tr '\n' ' ' | tr '\r' ' ')"
	if [ -z "${cli_found}" ]; then
		cli_found="keine"
	fi
	echo "MariaDB-Fachpruefung fehlgeschlagen (Script=${script_basis}, gesucht=mariadb,mysql, verwendet=${cli_found}): ${meldung}"
	return 1
}

nodepilot_register()
{
	local agent_version
	agent_version="$(nodepilot_agent_version_synchronisieren)"
	local system_os os_ver kernel arch host fqdn machine hardware
	host="$(nodepilot_ini_wert "lokal" "hostname" "$(hostname -s)")"
	fqdn="$(nodepilot_ini_wert "lokal" "fqdn" "$(hostname -f 2>/dev/null || hostname)")"
	machine="$(nodepilot_ini_wert "lokal" "machine_id" "")"
	hardware="$(nodepilot_ini_wert "lokal" "hardware_uuid" "")"
	system_os="$(awk -F= '/^PRETTY_NAME=/{gsub(/"/,"",$2);print $2}' /etc/os-release 2>/dev/null || uname -s)"
	os_ver="$(awk -F= '/^VERSION_ID=/{gsub(/"/,"",$2);print $2}' /etc/os-release 2>/dev/null || uname -r)"
	kernel="$(uname -v)"
	arch="$(uname -m)"
	local mariadb_cli mariadb_verfuegbar_json
	mariadb_cli="$(nodepilot_mariadb_cli_ermitteln || true)"
	if [ -n "${mariadb_cli}" ]; then
		mariadb_verfuegbar_json="true"
	else
		mariadb_verfuegbar_json="false"
	fi
	local payload
	payload="$(jq -n \
		--arg agent_uuid "$(nodepilot_ini_wert "agent" "agent_uuid" "")" \
		--arg machine_id "${machine}" \
		--arg hardware_uuid "${hardware}" \
		--arg hostname "${host}" \
		--arg fqdn "${fqdn}" \
		--arg os_name "${system_os}" \
		--arg os_version "${os_ver}" \
		--arg kernel_version "${kernel}" \
		--arg architektur "${arch}" \
		--argjson ipv4_liste "$(nodepilot_ip_liste_json 4)" \
		--argjson ipv6_liste "$(nodepilot_ip_liste_json 6)" \
		--argjson docker_verfuegbar "$(command -v docker >/dev/null 2>&1 && echo true || echo false)" \
		--argjson mariadb_verfuegbar "${mariadb_verfuegbar_json}" \
		--arg agent_version "${agent_version}" \
		--arg repo_channel "$(nodepilot_ini_wert "agent" "repo_channel" "stable")" \
		--arg kopplungs_id "$(nodepilot_ini_wert "auth" "kopplungs_id" "")" \
		--arg kopplungs_id_gueltig_bis "$(nodepilot_ini_wert "auth" "kopplungs_id_gueltig_bis" "")" \
		--arg agent_key "$(nodepilot_ini_wert "auth" "agent_key" "")" \
		--arg zeitstempel "$(date -Iseconds)" \
		'{agent_uuid:$agent_uuid,machine_id:$machine_id,hardware_uuid:$hardware_uuid,hostname:$hostname,fqdn:$fqdn,os_name:$os_name,os_version:$os_version,kernel_version:$kernel_version,architektur:$architektur,ipv4_liste:$ipv4_liste,ipv6_liste:$ipv6_liste,docker_verfuegbar:$docker_verfuegbar,mariadb_verfuegbar:$mariadb_verfuegbar,agent_version:$agent_version,repo_channel:$repo_channel,kopplungs_id:$kopplungs_id,kopplungs_id_gueltig_bis:$kopplungs_id_gueltig_bis,agent_key:$agent_key,zeitstempel:$zeitstempel}')"
	local antwort
	set +e
	antwort="$(nodepilot_retry_post "$(nodepilot_ini_wert "server" "api_register_endpoint" "/agent/register")" "${payload}" "register")"
	local rc_register=$?
	set -e
	if [ "${rc_register}" -eq 43 ]; then
		nodepilot_identitaetskonflikt_lokal_behandeln "register"
		nodepilot_status_schreiben "letzter_register" "$(date -Iseconds)"
		return 1
	fi
	if [ "${rc_register}" -ne 0 ]; then
		return 1
	fi
	nodepilot_remote_server_parameter_guard_loggen "${antwort}"
	if [ "$(echo "${antwort}" | jq -r '.status // "fehler"')" = "ok" ]; then
		nodepilot_ini_setzen "auth" "node_id" "$(echo "${antwort}" | jq -r '.node_id // 0')"
		nodepilot_status_schreiben "register_status" "ok"
		nodepilot_status_schreiben "letzter_register" "$(date -Iseconds)"
		nodepilot_log "info" "Register erfolgreich. Node-ID $(nodepilot_ini_wert "auth" "node_id" "0")"
		nodepilot_log "info" "Direkter Start-Flush aktiviert: Poll-Stufen schnellkern -> detail -> vollsync."
		nodepilot_poll "schnellkern" || true
		nodepilot_poll "detail" || true
		nodepilot_poll "vollsync" || true
	else
		nodepilot_status_schreiben "register_status" "fehler"
		nodepilot_status_schreiben "letzter_register" "$(date -Iseconds)"
		nodepilot_log "error" "Register fehlgeschlagen."
		return 1
	fi
}

nodepilot_poll()
{
	local agent_version
	agent_version="$(nodepilot_agent_version_synchronisieren)"
	nodepilot_agent_state_laden || true
	local poll_modus="${1:-normal}"
	nodepilot_agent_laufereignis_loggen "poll_start" "start" "poll_modus=${poll_modus}"
	local payload
	payload="$(jq -n \
		--arg poll_modus "${poll_modus}" \
		--argjson node_id "$(nodepilot_zahl_oder_fallback "$(nodepilot_ini_wert "auth" "node_id" "0")" "0" "poll/${poll_modus}/node_id")" \
		--arg agent_uuid "$(nodepilot_ini_wert "agent" "agent_uuid" "")" \
		--arg agent_version "${agent_version}" \
		--arg zeitstempel "$(date -Iseconds)" \
		'{poll_modus:$poll_modus,node_id:$node_id,agent_uuid:$agent_uuid,agent_version:$agent_version,zeitstempel:$zeitstempel}' 2>/dev/null || true)"
	payload="$(nodepilot_json_oder_fallback "${payload}" '{"poll_modus":"normal","node_id":0,"agent_uuid":"","agent_version":"","zeitstempel":""}' "poll/${poll_modus}/payload_basis")"

	local block_name block_json
	for block_name in metriken dienste docker_container mariadb system filesystem ipv4_liste ipv6_liste installierte_programme befehlsergebnisse; do

		if ! nodepilot_agent_block_faellig "${block_name}"; then
			continue
		fi

		block_json=""
		if [ "${block_name}" = "metriken" ]; then
			block_json="$(nodepilot_json_oder_fallback "$(nodepilot_metriken_json)" '{}' "poll/${poll_modus}/${block_name}")"
		elif [ "${block_name}" = "dienste" ]; then
			block_json="$(nodepilot_json_oder_fallback "$(nodepilot_dienste_json)" "[]" "poll/${poll_modus}/${block_name}")"
		elif [ "${block_name}" = "docker_container" ]; then
			block_json="$(nodepilot_json_oder_fallback "$(nodepilot_docker_json)" "[]" "poll/${poll_modus}/${block_name}")"
		elif [ "${block_name}" = "mariadb" ]; then
			block_json="$(nodepilot_json_oder_fallback "$(nodepilot_mariadb_json)" '{"verfuegbar":false,"sql_abfragen":[]}' "poll/${poll_modus}/${block_name}")"
			local mariadb_cli_fuer_live
			mariadb_cli_fuer_live="$(nodepilot_mariadb_cli_ermitteln || true)"
			if [ -n "${mariadb_cli_fuer_live}" ]; then
				block_json="$(nodepilot_mariadb_live_felder_hinzufuegen "${block_json}" "${mariadb_cli_fuer_live}")"
			fi
			nodepilot_mariadb_debug_artefakt_schreiben "mariadb_json.last.json" "${block_json}" "mariadb_roh poll_modus=${poll_modus} query_quelle=$(echo "${block_json}" | jq -r '.query_quelle // \"unbekannt\"' 2>/dev/null || echo "unbekannt") query_modus=$(echo "${block_json}" | jq -r '.query_modus // \"unbekannt\"' 2>/dev/null || echo "unbekannt") roh=$(echo "${block_json}" | jq -r '.mariadb_sql_abfragen_original_anzahl // (.sql_abfragen|length) // 0' 2>/dev/null || echo 0) nach_filter=$(echo "${block_json}" | jq -r '.mariadb_sql_abfragen_uebernommen_anzahl // (.sql_abfragen|length) // 0' 2>/dev/null || echo 0) fallback=$(echo "${block_json}" | jq -r '.diagnose_status // \"ok\"' 2>/dev/null || echo "unbekannt")" || true
			nodepilot_mariadb_transport_enqueue "${block_json}" "${poll_modus}" || true
			block_json="$(nodepilot_mariadb_transport_poll_payload_reduzieren "${block_json}")"
			nodepilot_mariadb_debug_artefakt_schreiben "mariadb_transport.last.json" "${block_json}" "mariadb_transport_reduziert poll_modus=${poll_modus} query_quelle=$(echo "${block_json}" | jq -r '.query_quelle // \"unbekannt\"' 2>/dev/null || echo "unbekannt") query_modus=$(echo "${block_json}" | jq -r '.query_modus // \"unbekannt\"' 2>/dev/null || echo "unbekannt") transportiert=$(echo "${block_json}" | jq -r '.mariadb_sql_abfragen_uebernommen_anzahl // 0' 2>/dev/null || echo 0) aktuelle_queries=$(echo "${block_json}" | jq -r '.mariadb_aktuelle_queries // -1' 2>/dev/null || echo -1)" || true
		elif [ "${block_name}" = "system" ]; then
			block_json="$(jq -n --arg hostname "$(nodepilot_ini_wert "lokal" "hostname" "")" --arg fqdn "$(nodepilot_ini_wert "lokal" "fqdn" "")" --arg os_name "$(uname -s)" --arg os_version "$(uname -r)" --arg kernel_version "$(uname -v)" --arg architektur "$(uname -m)" --argjson uptime_sekunden "$(nodepilot_zahl_oder_fallback "$(awk '{print int($1)}' /proc/uptime 2>/dev/null || echo 0)" "0" "system/uptime")" '{hostname:$hostname,fqdn:$fqdn,os_name:$os_name,os_version:$os_version,kernel_version:$kernel_version,architektur:$architektur,uptime_sekunden:$uptime_sekunden}' 2>/dev/null || true)"
			block_json="$(nodepilot_json_oder_fallback "${block_json}" '{}' "poll/${poll_modus}/${block_name}")"
		elif [ "${block_name}" = "filesystem" ]; then
			block_json="$(df -Pm / | awk 'NR==2{printf "[{\"mountpoint\":\"/\",\"gesamt_mb\":%s,\"frei_mb\":%s,\"belegt_prozent\":%s}]",$2,$4,$5+0}' )"
			block_json="$(nodepilot_json_oder_fallback "${block_json}" "[]" "poll/${poll_modus}/${block_name}")"
		elif [ "${block_name}" = "ipv4_liste" ]; then
			block_json="$(nodepilot_json_oder_fallback "$(nodepilot_ip_liste_json 4)" "[]" "poll/${poll_modus}/${block_name}")"
		elif [ "${block_name}" = "ipv6_liste" ]; then
			block_json="$(nodepilot_json_oder_fallback "$(nodepilot_ip_liste_json 6)" "[]" "poll/${poll_modus}/${block_name}")"
		elif [ "${block_name}" = "installierte_programme" ]; then
			local programme_tmp_json
			programme_tmp_json="$(mktemp "${NODEPILOT_TMP_DIR}/installierte_programme_block.XXXXXX.json")"
			nodepilot_programme_json > "${programme_tmp_json}" 2>/dev/null || echo '[]' > "${programme_tmp_json}"
			if ! jq -c . "${programme_tmp_json}" >/dev/null 2>&1; then
				nodepilot_log "warn" "poll/${poll_modus}/${block_name}: ungueltiges JSON erkannt; nutze Fallback []."
				echo '[]' > "${programme_tmp_json}"
			fi
			if ! nodepilot_agent_block_payload_pruefen_und_merken "${block_name}" "$(cat "${programme_tmp_json}" 2>/dev/null || echo '[]')"; then
				rm -f "${programme_tmp_json}" 2>/dev/null || true
				continue
			fi
			payload="$(nodepilot_json_merge_block_aus_datei "${payload}" "${block_name}" "${programme_tmp_json}" 2>/dev/null || echo "${payload}")"
			nodepilot_agent_state_block_setzen "${block_name}" "letzte_erfolgreiche_sammlung_unix" "$(date +%s)"
			rm -f "${programme_tmp_json}" 2>/dev/null || true
			continue
		elif [ "${block_name}" = "befehlsergebnisse" ]; then
			block_json="$(nodepilot_json_oder_fallback "$(nodepilot_befehlsergebnisse_laden)" "[]" "poll/${poll_modus}/${block_name}")"
			if [ "$(echo "${block_json}" | jq -r 'length' 2>/dev/null || echo "0")" -eq 0 ]; then
				continue
			fi
		fi

		if ! echo "${block_json}" | jq -c . >/dev/null 2>&1; then
			nodepilot_log "warn" "Block ${block_name}: Collector lieferte ungueltiges JSON; Block wird ausgelassen."
			continue
		fi
		nodepilot_agent_state_block_setzen "${block_name}" "letzte_erfolgreiche_sammlung_unix" "$(date +%s)"
		if ! nodepilot_agent_block_payload_pruefen_und_merken "${block_name}" "${block_json}"; then
			continue
		fi
		payload="$(nodepilot_json_merge_block_dateibasiert "${payload}" "${block_name}" "${block_json}" 2>/dev/null || echo "${payload}")"
	done

	local antwort endpoint_poll payload_datei_tmp payload_bytes
	local latenz_snapshot_json
	latenz_snapshot_json="$(nodepilot_latenz_ms_json)"
	payload="$(echo "${payload}" | jq -c --argjson latenz_snapshot "${latenz_snapshot_json}" '.latenz_ms = ($latenz_snapshot.latenz_ms // null) | .latenz_status = ($latenz_snapshot.latenz_status // "kein_messpunkt")' 2>/dev/null || echo "${payload}")"
	endpoint_poll="$(nodepilot_ini_wert "server" "api_poll_endpoint" "/agent/poll")"
	nodepilot_agent_state_speichern || true
	nodepilot_telemetrie_queue_verarbeiten "${endpoint_poll}" || true
	payload_datei_tmp="$(mktemp "${NODEPILOT_TMP_DIR}/telemetrie-poll.XXXXXX.json")"
	printf '%s' "${payload}" > "${payload_datei_tmp}"
	payload_bytes="$(wc -c < "${payload_datei_tmp}" 2>/dev/null | awk '{print $1+0}')"
	nodepilot_log "info" "telemetrie_transport payload gebaut poll_modus=${poll_modus} payload_bytes=${payload_bytes}."
	nodepilot_log "info" "telemetrie_transport payload gesendet poll_modus=${poll_modus} payload_bytes=${payload_bytes}."
	set +e
	antwort="$(nodepilot_retry_post_datei "${endpoint_poll}" "${payload_datei_tmp}" "telemetrie-poll-${poll_modus}")"
	local rc_poll=$?
	set -e
	rm -f "${payload_datei_tmp}" 2>/dev/null || true
	if [ "${rc_poll}" -ne 0 ]; then
		nodepilot_telemetrie_queue_einreihen "${payload}" "${poll_modus}"
	fi
	if [ "${rc_poll}" -eq 43 ]; then
		nodepilot_identitaetskonflikt_lokal_behandeln "poll-${poll_modus}"
		nodepilot_status_schreiben "letzter_poll" "$(date -Iseconds)"
		nodepilot_agent_laufereignis_loggen "poll_ende" "abbruch" "poll_modus=${poll_modus} grund=identitaetskonflikt"
		return 1
	fi
	if [ "${rc_poll}" -eq 44 ]; then
		nodepilot_agent_unbekannt_lokal_behandeln "poll-${poll_modus}" "AGENT_UNBEKANNT"
		nodepilot_status_schreiben "letzter_poll" "$(date -Iseconds)"
		nodepilot_agent_laufereignis_loggen "poll_ende" "abbruch" "poll_modus=${poll_modus} grund=agent_unbekannt"
		return 1
	fi
	if [ "${rc_poll}" -ne 0 ]; then
		nodepilot_agent_laufereignis_loggen "poll_ende" "fehler" "poll_modus=${poll_modus} grund=transportfehler"
		return 1
	fi
	nodepilot_log "info" "telemetrie_transport payload bestaetigt poll_modus=${poll_modus} payload_bytes=${payload_bytes}."
	antwort="$(nodepilot_api_antwort_json_normieren "poll-${poll_modus}" "${antwort}")" || {
		nodepilot_status_schreiben "poll_status" "fehler"
		nodepilot_status_schreiben "letzter_poll" "$(date -Iseconds)"
		nodepilot_log "error" "Poll (${poll_modus}) fehlgeschlagen: API-Antwort ungueltig."
		nodepilot_agent_laufereignis_loggen "poll_ende" "fehler" "poll_modus=${poll_modus} grund=antwort_ungueltig"
		return 1
	}
	nodepilot_remote_server_parameter_guard_loggen "${antwort}"
	if [ "$(echo "${antwort}" | jq -r '.aktion // ""')" = "register_required" ] && [ "$(echo "${antwort}" | jq -r '.fehlercode // ""')" = "unknown_agent" ]; then
		nodepilot_agent_unbekannt_lokal_behandeln "poll-${poll_modus}" "unknown_agent/register_required"
		nodepilot_status_schreiben "letzter_poll" "$(date -Iseconds)"
		nodepilot_agent_laufereignis_loggen "poll_ende" "abbruch" "poll_modus=${poll_modus} grund=register_required_unknown_agent"
		return 1
	fi
	if [ "$(echo "${antwort}" | jq -r '.status // "fehler"')" != "ok" ]; then
		nodepilot_status_schreiben "poll_status" "fehler"
		nodepilot_status_schreiben "letzter_poll" "$(date -Iseconds)"
		nodepilot_log "error" "Poll (${poll_modus}) fehlgeschlagen."
		nodepilot_agent_laufereignis_loggen "poll_ende" "fehler" "poll_modus=${poll_modus} grund=status_nicht_ok"
		return 1
	fi
	nodepilot_status_schreiben "poll_status" "ok"
	nodepilot_status_schreiben "letzter_poll" "$(date -Iseconds)"
	nodepilot_befehlsergebnisse_leeren
	nodepilot_mariadb_transport_queue_verarbeiten || true
	nodepilot_agent_state_speichern || true
	nodepilot_befehle_ausfuehren "${antwort}"
	nodepilot_ini_setzen "poll" "poll_intervall_sekunden" "$(echo "${antwort}" | jq -r '.naechster_poll_sekunden // 5')"
	nodepilot_ini_setzen "poll" "vollsync_intervall_sekunden" "$(echo "${antwort}" | jq -r '.naechster_vollsync_sekunden // 300')"
	nodepilot_log "info" "Poll (${poll_modus}) erfolgreich."
	nodepilot_agent_laufereignis_loggen "poll_ende" "ok" "poll_modus=${poll_modus}"
}

nodepilot_update_check()
{
	local lokale_node_id
	lokale_node_id="$(nodepilot_ini_wert "auth" "node_id" "0")"
	if [ "${lokale_node_id}" -le 0 ]; then
		nodepilot_log "info" "update-check: uebersprungen, da keine gueltige lokale node_id vorhanden (Re-Register ausstehend)."
		nodepilot_status_schreiben "update_check_status" "ausgesetzt_re_register"
		nodepilot_status_schreiben "letzter_update_check" "$(date -Iseconds)"
		return 0
	fi

	local agent_version
	agent_version="$(nodepilot_agent_version_synchronisieren)"
	local payload
	payload="$(jq -n \
		--argjson node_id "$(nodepilot_ini_wert "auth" "node_id" "0")" \
		--arg agent_uuid "$(nodepilot_ini_wert "agent" "agent_uuid" "")" \
		--arg machine_id "$(nodepilot_ini_wert "lokal" "machine_id" "")" \
		--arg hardware_uuid "$(nodepilot_ini_wert "lokal" "hardware_uuid" "")" \
		--arg agent_version "${agent_version}" \
		--arg repo_channel "$(nodepilot_ini_wert "agent" "repo_channel" "stable")" \
		'{node_id:$node_id,agent_uuid:$agent_uuid,machine_id:$machine_id,hardware_uuid:$hardware_uuid,agent_version:$agent_version,repo_channel:$repo_channel}')"
	local antwort
	set +e
	antwort="$(nodepilot_retry_post "$(nodepilot_ini_wert "server" "api_update_check_endpoint" "/agent/update-check")" "${payload}" "update-check")"
	local rc_update_check=$?
	set -e
	if [ "${rc_update_check}" -eq 44 ]; then
		nodepilot_agent_unbekannt_lokal_behandeln "update-check" "AGENT_UNBEKANNT"
		nodepilot_status_schreiben "update_check_status" "recovery_re_register"
		nodepilot_status_schreiben "letzter_update_check" "$(date -Iseconds)"
		return 0
	fi
	if [ "${rc_update_check}" -ne 0 ]; then
		nodepilot_status_schreiben "update_check_status" "fehler"
		nodepilot_status_schreiben "letzter_update_check" "$(date -Iseconds)"
		return 0
	fi
	antwort="$(nodepilot_api_antwort_json_normieren "update-check" "${antwort}")" || {
		nodepilot_log "error" "update-check: Antwort verworfen; Folgeentscheidungen werden nicht ausgefuehrt."
		nodepilot_status_schreiben "update_check_status" "fehler"
		nodepilot_status_schreiben "letzter_update_check" "$(date -Iseconds)"
		return 0
	}
	nodepilot_remote_server_parameter_guard_loggen "${antwort}"
	if [ "$(echo "${antwort}" | jq -r '.aktion // ""')" = "register_required" ] && [ "$(echo "${antwort}" | jq -r '.fehlercode // ""')" = "unknown_agent" ]; then
		nodepilot_agent_unbekannt_lokal_behandeln "update-check" "unknown_agent/register_required"
		nodepilot_status_schreiben "update_check_status" "recovery_re_register"
		nodepilot_status_schreiben "letzter_update_check" "$(date -Iseconds)"
		return 0
	fi
	local update_verfuegbar ziel_version repo_channel
	update_verfuegbar="$(echo "${antwort}" | jq -r '.update_verfuegbar // false')"
	ziel_version="$(echo "${antwort}" | jq -r '.ziel_version // ""')"
	repo_channel="$(echo "${antwort}" | jq -r '.repo_channel // ""')"
	if [ -n "${repo_channel}" ]; then
		nodepilot_ini_setzen "agent" "repo_channel" "${repo_channel}"
	fi
	nodepilot_ini_setzen "agent" "agent_version" "${agent_version}"
	nodepilot_status_schreiben "update_check_status" "ok"
	nodepilot_status_schreiben "letzter_update_check" "$(date -Iseconds)"
	if [ "${update_verfuegbar}" = "true" ] && [ -n "${ziel_version}" ]; then
		nodepilot_log "info" "Update verfuegbar: ${agent_version} -> ${ziel_version} (Kanal ${repo_channel:-$(nodepilot_ini_wert "agent" "repo_channel" "stable")})."
	else
		nodepilot_log "debug" "update-check: Kein Agent-Update verfuegbar."
	fi
}

nodepilot_restart_marker_verarbeiten()
{
	if [ ! -f "${NODEPILOT_RESTART_MARKER_DATEI}" ]; then
		return 1
	fi

	local marker_inhalt erste_zeile
	marker_inhalt="$(cat "${NODEPILOT_RESTART_MARKER_DATEI}" 2>/dev/null || true)"
	erste_zeile="$(printf '%s\n' "${marker_inhalt}" | awk 'NR==1 {gsub(/\r/, "", $0); print; exit}')"
	nodepilot_log "info" "Restart-Marker erkannt: datei=${NODEPILOT_RESTART_MARKER_DATEI}, details=${erste_zeile:-leer}."
	nodepilot_agent_laufereignis_loggen "restart_marker_erkannt" "ok" "datei=${NODEPILOT_RESTART_MARKER_DATEI};details=${erste_zeile:-leer}"

	if rm -f "${NODEPILOT_RESTART_MARKER_DATEI}" 2>/dev/null; then
		nodepilot_log "info" "Restart-Marker entfernt: ${NODEPILOT_RESTART_MARKER_DATEI}."
	else
		nodepilot_log "warn" "Restart-Marker konnte nicht entfernt werden: ${NODEPILOT_RESTART_MARKER_DATEI}."
	fi

	nodepilot_log "info" "Kontrollierter Exit fuer systemd-Restart wird ausgefuehrt (Restart=always)."
	nodepilot_agent_laufereignis_loggen "agent_ende" "restart_requested" "grund=restart_marker"
	return 0
}

main()
{
	nodepilot_absichern_root
	nodepilot_defaults_sicherstellen
	nodepilot_agent_version_synchronisieren >/dev/null
	nodepilot_lokaldaten_ermitteln
	nodepilot_agent_state_laden
	nodepilot_agent_laufereignis_loggen "agent_start" "start" "modus=${1:-once}"
	nodepilot_log "info" "Serverziel bleibt lokal fuehrend (INI): server_url/api_schema/api_host/api_port/api_basis_pfad werden nie aus Remote-Antworten uebernommen."

	local modus="once"
	if [ "${1:-}" = "status" ]; then
		nodepilot_status_ausgeben
		exit 0
	elif [ "${1:-}" = "pairing-code" ]; then
		nodepilot_pairing_code_ausgeben
		exit 0
	elif [ "${1:-}" = "--queue-cleanup-stale-identity" ]; then
		nodepilot_queue_veraltete_identitaet_bereinigen
		exit 0
	elif [ "${1:-}" = "--loop" ]; then
		modus="loop"
	elif [ "${1:-}" = "--register-only" ]; then
		nodepilot_agent_singleton_sperre_setzen
		nodepilot_register
		exit 0
	elif [ "${1:-}" = "--poll-only" ]; then
		nodepilot_agent_singleton_sperre_setzen
		nodepilot_poll "normal"
		exit 0
	fi

	nodepilot_agent_singleton_sperre_setzen
	if [ "${modus}" = "loop" ]; then
		nodepilot_single_loop_sperre_setzen
	fi

	nodepilot_agent_config_lokal_laden
	nodepilot_agent_scheduler_neuaufbauen
	nodepilot_queue_veraltete_identitaet_bereinigen
	NODEPILOT_AGENT_NEXT_PULL_UNIX=0

	while true; do
		local aktueller_node_id aktuelle_agent_uuid aktuelle_machine_id aktuelle_hardware_uuid
		aktueller_node_id="$(nodepilot_ini_wert "auth" "node_id" "0")"
		aktuelle_agent_uuid="$(nodepilot_ini_wert "agent" "agent_uuid" "")"
		aktuelle_machine_id="$(nodepilot_ini_wert "lokal" "machine_id" "")"
		aktuelle_hardware_uuid="$(nodepilot_ini_wert "lokal" "hardware_uuid" "")"

		if [ "${aktueller_node_id}" -le 0 ]; then
			nodepilot_log "warn" "Register wird ausgefuehrt: node_id=${aktueller_node_id}, agent_uuid=${aktuelle_agent_uuid:-leer}, machine_id=${aktuelle_machine_id:-leer}, hardware_uuid=${aktuelle_hardware_uuid:-leer}. Grund=keine_gueltige_lokale_node_id"
			nodepilot_register || true
		else
			nodepilot_log "info" "Register nicht noetig: vorhandene lokale Identitaet wird wiederverwendet (node_id=${aktueller_node_id}, agent_uuid=${aktuelle_agent_uuid:-leer})."
		fi

		if [ "$(date +%s)" -ge "${NODEPILOT_AGENT_NEXT_PULL_UNIX}" ]; then
			nodepilot_agent_config_vom_server_pullen || true
		fi

		nodepilot_agent_scheduler_tick
		nodepilot_poll "normal" || true
		nodepilot_update_check

		if nodepilot_restart_marker_verarbeiten; then
			exit 0
		fi

		[ "${modus}" = "once" ] && break
		sleep 1
	done
	nodepilot_agent_laufereignis_loggen "agent_ende" "ok" "modus=${modus}"
}

if [ "${BASH_SOURCE[0]}" = "$0" ]; then
	main "${@}"
fi
