diff --git a/.empire/AGENTS.md b/.empire/AGENTS.md index e72281f052..1b7ad1dcda 100644 --- a/.empire/AGENTS.md +++ b/.empire/AGENTS.md @@ -1574,6 +1574,31 @@ Gemessen: `ritual.sh` gab `64` zurück (kein Brief erzeugt, nichts zugestellt), **Regel:** In jedem `.cmd`/`.ps1`/`.sh`-Wrapper den Exit-Code der Nutzlast **unmittelbar** sichern (`set "RC=%ERRORLEVEL%"` bzw. `rc=$?`) und am Ende weiterreichen. Kein Kommando zwischen Nutzlast und Sicherung — auch kein Log-`echo`. Vorbild im Haus: `google-mcp/scripts/token-probe-task.cmd`. Und danach die Rot-Probe: der Wrapper muss mit einem garantiert scheiternden Aufruf einen Nicht-Null-Code liefern, sonst ist der Fix unbewiesen. +### Ritual-Heartbeat (buzz#101 — der Leser für diese Exit-Codes) + +Ein echter Exit-Code nützt nichts, solange ihn niemand liest. Seit 2026-08-01 hängt hinter jedem Ritual ein Uptime-Kuma-Push-Monitor: + +| Ritual | Windows-Aufgabe | Kuma-Monitor | Token in `~/.secrets/master.env` | Intervall | +|---|---|---|---|---| +| Morgenbrief 08:45 | `Buzz-Ritual-Morgenbrief` | `ritual-morgenbrief` (id 64) | `KUMA_PUSH_RITUAL_MORGENBRIEF` | 93 600 s (26 h) | +| Gate-Batch 20:45 | `Buzz-Ritual-Gate-Batch` | `ritual-gate-batch` (id 65) | `KUMA_PUSH_RITUAL_GATE_BATCH` | 93 600 s (26 h) | +| Wochen-Review So 18:00 | `Buzz-Ritual-Wochen-Review` | `ritual-wochen-review` (id 66) | `KUMA_PUSH_RITUAL_WOCHEN_REVIEW` | 691 200 s (8 d) | + +- Push: `.empire/tools/ritual-push.sh `, aufgerufen von `ritual-task.cmd` **nach** `set "RC=%ERRORLEVEL%"`. +- Abbildung: `rc` 0/1 → `status=up` · `rc` ≥ 2 → **expliziter** `status=down` (Erkennung sofort, nicht erst nach 26 h). Deshalb `maxretries: 0` an den Monitoren — mit Retries würde aus dem Down-Push erst PENDING. +- **Rangfolge im Wrapper:** Ritual-Fehler schlägt Herzschlag-Fehler. Ist das Ritual grün, aber der Push scheitert (Token weg, Kuma tot), meldet die Aufgabe **4** — ein blinder Wächter darf nicht als grün durchgehen. Ein fehlender Token endet nie still mit `exit 0` (das Gegenbeispiel im Haus ist `/opt/agency/push-health.sh`). +- Provisionierung: `agency-infra` `stacks/monitoring/provision/kuma-add-rituals.mjs` (idempotent, Benachrichtigungen zur Laufzeit — s. agency-infra#134). +- **Rot-Probe-Rezept** (Set→Beweis→Revert als ein Block, ohne irgendetwas kaputtzumachen): + ```powershell + $env:RITUAL_VAULT_LOG = "C:\nonexistent\vault-log.sh" # Vault-Transport scheitert -> ritual.sh rc=3 + cmd /c ".empire\tools\ritual-task.cmd gate-batch" # erwartet: Exit 3, Monitor Down, Alarm + $env:RITUAL_VAULT_LOG = "" + cmd /c ".empire\tools\ritual-task.cmd gate-batch" # erwartet: Exit 0, Monitor Up, Recovery + ``` + Für den Token-Pfad stattdessen `$env:RITUAL_SECRETS_FILE = "/nonexistent"` — erwartet: `HERZSCHLAG-LÜCKE` in `~/.buzz/ritual.log` und Aufgaben-Exit **4**. +- **Falle:** `HTTP 200` beweist beim Kuma-Push nichts — ein falscher Token liefert `{"ok":false}`. `ritual-push.sh` prüft deshalb den Body, nicht nur den Code. +- **Falle (agency-infra#148):** Ein Push-Monitor **ohne ersten Beat** alarmiert nie. Neue Ritual-Monitore brauchen sofort einen Bootstrap-Beat (`bash ritual-push.sh 0`), sonst sind sie bis zum ersten echten Lauf wirkungslos still. + ### Aus der n8n-Execution-Historie darf man „ist nie gelaufen" NICHT schließen `GET /executions?workflowId=…` meldete für drei aktive Wochen-Workflows null Executions. Alle drei waren gelaufen. Grund: n8n prunt nach **Anzahl** (Default `EXECUTIONS_DATA_PRUNE_MAX_COUNT=10000`, im Container ist keine `EXECUTIONS_*`-Variable gesetzt), und bei ~2 900 Executions/Tag reicht die Historie nur **3,4 Tage** zurück — kürzer als eine Wochen-Periode. diff --git a/.empire/tools/ritual-push.sh b/.empire/tools/ritual-push.sh new file mode 100644 index 0000000000..56e9ac5773 --- /dev/null +++ b/.empire/tools/ritual-push.sh @@ -0,0 +1,75 @@ +#!/usr/bin/env bash +# .empire/tools/ritual-push.sh — Herzschlag der drei Führungsrituale (buzz#101) +# +# bash ritual-push.sh +# +# Wird von ritual-task.cmd aufgerufen, NACHDEM der Exit-Code von ritual.sh +# feststeht. Bildet ihn auf den passenden Uptime-Kuma-Push-Monitor ab. +# +# WARUM: Morgenbrief (08:45), Gate-Batch (20:45) und Wochen-Review (So 18:00) +# sind Munirs einziger Kanal auf Backlog, Blocker und Zahlungen. Bis 2026-08-01 +# hatten sie keinen einzigen Gesundheits-Monitor — sogar der Gmail-Token hatte +# einen, das tägliche Führungsbriefing nicht. Seit PR #98 liefert die Aufgabe +# überhaupt erst echte Exit-Codes; dieses Script ist der Leser dafür. +# +# Exit-Abbildung (identisch zu ritual-task.cmd, bewusst an EINER Stelle erklärt): +# rc 0 = alle Quellen geliefert -> up +# rc 1 = Brief zugestellt, mit Lücken -> up (Lücken sind der Normalfall; +# würde 1 rot melden, wäre praktisch jeder Tag rot und niemand schaute hin) +# rc >= 2 = Brief hat Munir NICHT erreicht -> down (expliziter Down-Push: +# Erkennung sofort statt erst nach Ablauf des Intervalls) +# +# Eigene Exit-Codes (die Aufgabe wertet sie aus, s. ritual-task.cmd): +# 0 = Push zugestellt · 2 = Aufruffehler · 3 = Push-Token fehlt · 4 = Push abgelehnt +# Ein fehlender Token endet NICHT still mit 0 — ein Herzschlag, der niemanden +# erreicht, ist derselbe Fehler wie gar kein Monitor. +set -u + +LOG="$HOME/.buzz/ritual.log" +mkdir -p "$HOME/.buzz" 2>/dev/null || true +log() { printf '%s ritual-push: %s\n' "$(date -Is)" "$1" >> "$LOG"; } + +MODE="${1:-}" +RC="${2:-}" +case "$MODE" in + morgenbrief) TOKEN_VAR="KUMA_PUSH_RITUAL_MORGENBRIEF" ;; + gate-batch) TOKEN_VAR="KUMA_PUSH_RITUAL_GATE_BATCH" ;; + wochen-review) TOKEN_VAR="KUMA_PUSH_RITUAL_WOCHEN_REVIEW" ;; + *) log "AUFRUFFEHLER: unbekannter Modus '$MODE' — kein Monitor zugeordnet, kein Herzschlag gesendet"; exit 2 ;; +esac +case "$RC" in + ''|*[!0-9]*) log "AUFRUFFEHLER: rc '$RC' ist keine Zahl"; exit 2 ;; +esac + +SECRETS="${RITUAL_SECRETS_FILE:-$HOME/.secrets/master.env}" +BASE="${KUMA_PUSH_BASE:-}" +TOKEN="$(eval printf '%s' "\${$TOKEN_VAR:-}")" + +# Env schlägt Datei; die Datei wird nur gelesen, wenn nötig (und nie geloggt). +if [ -z "$TOKEN" ] || [ -z "$BASE" ]; then + if [ -r "$SECRETS" ]; then + [ -z "$TOKEN" ] && TOKEN="$(grep -m1 "^${TOKEN_VAR}=" "$SECRETS" | cut -d= -f2- | tr -d '\r"' )" + [ -z "$BASE" ] && BASE="$(grep -m1 '^KUMA_PUSH_BASE=' "$SECRETS" | cut -d= -f2- | tr -d '\r"' )" + else + log "LÜCKE: $SECRETS nicht lesbar" + fi +fi +BASE="${BASE:-https://status.adas.jetzt/api/push}" + +if [ -z "$TOKEN" ]; then + log "HERZSCHLAG-LÜCKE: $TOKEN_VAR weder im Env noch in $SECRETS — Monitor '$MODE' bleibt ohne Beat (Ritual-rc war $RC)" + exit 3 +fi + +if [ "$RC" -le 1 ]; then STATUS="up"; else STATUS="down"; fi + +RESP="$(curl -sS -m 20 -w '\n%{http_code}' "$BASE/$TOKEN?status=$STATUS&msg=$MODE-rc$RC" 2>&1)" +CODE="$(printf '%s' "$RESP" | tail -n1)" +BODY="$(printf '%s' "$RESP" | sed '$d')" + +# HTTP 200 allein beweist nichts — Kuma antwortet mit {"ok":true|false}. +case "$CODE$BODY" in + 200*'"ok":true'*) log "$MODE rc=$RC -> Kuma $STATUS (HTTP $CODE)"; exit 0 ;; +esac +log "PUSH ABGELEHNT: $MODE rc=$RC status=$STATUS -> HTTP ${CODE:-?} ${BODY:-}" +exit 4 diff --git a/.empire/tools/ritual-task.cmd b/.empire/tools/ritual-task.cmd index 202100de3d..69050d42d7 100644 --- a/.empire/tools/ritual-task.cmd +++ b/.empire/tools/ritual-task.cmd @@ -33,12 +33,24 @@ set "HERE=%~dp0" "%GITBASH%" -lc "mkdir -p ~/.buzz; echo \"=== $(date -Is) ritual %~1 ===\" >> ~/.buzz/ritual.log; bash '%HERE:\=/%ritual.sh' %~1 --post --telegram --vault >> ~/.buzz/ritual.log 2>&1; rc=$?; echo \"exit=$rc\" >> ~/.buzz/ritual.log; exit $rc" set "RC=%ERRORLEVEL%" +REM Herzschlag an Uptime Kuma (buzz#101) — MUSS nach dem Sichern von RC stehen, +REM sonst ueberschreibt der Push den zu meldenden Exit-Code. Der Push bekommt RC +REM uebergeben und bildet ihn ab; er ueberschreibt ihn nie: ein gescheitertes +REM Ritual bleibt rot, auch wenn der Push gelingt. Umgekehrt darf ein +REM gescheiterter Push nicht still bleiben — ein Monitor ohne Beat ist derselbe +REM blinde Fleck, den dieses Ticket schliesst. +"%GITBASH%" -lc "bash '%HERE:\=/%ritual-push.sh' %~1 %RC%" +set "PUSHRC=%ERRORLEVEL%" + REM ritual.sh: 0 = alle Quellen geliefert · 1 = Brief zugestellt, mit benannten REM Luecken · 2 = kein Brief · 3 = Transport gescheitert · 64 = Usage. REM 0 und 1 heissen beide "der Brief hat Munir erreicht" -> gruen. Alles ab 2 REM heisst "er hat ihn NICHT erreicht" -> muss rot sein. Wuerde auch 1 rot REM melden, waere die Aufgabe an praktisch jedem Tag rot (Luecken sind der REM Normalfall) und niemand schaute mehr hin. -if "%RC%"=="0" exit /b 0 -if "%RC%"=="1" exit /b 0 -exit /b %RC% +REM Rangfolge: Ritual-Fehler schlaegt Herzschlag-Fehler. Ist das Ritual gruen, +REM aber der Herzschlag kaputt, meldet die Aufgabe 4 — sonst wuesste niemand, +REM dass der Waechter selbst blind ist. +if not "%RC%"=="0" if not "%RC%"=="1" exit /b %RC% +if not "%PUSHRC%"=="0" exit /b 4 +exit /b 0