TGV · Betrieb · Stand 19.07.2026 · UMG-1 (Register #33)

🧰 Umgebung & Einstellungen — der komplette Stack

Alles, woraus die TGV-Welt technisch besteht — Dienste, Arbeitsplatz, Server, Daten, Automatisierung, Kanäle — gemessen mit Fundstellen, Lücken ehrlich markiert, Secrets nur als ORTE (nie Werte).

🎯 Abnahme-Kriterium (JC): „Ein Super-Admin kann die TGV-WELT von scratch einrichten und bedienen." — Die Tag-1-Kette dafür steht in §10.
🛡 Stand-Stempel: Quelle UMGEBUNG_INVENTUR_2026-07-19.md @ 4fe221d · Seite gebaut 19.07.2026 · ⚠ Momentaufnahmen (teils 5–40 Tage alt) — vor jedem Handgriff gegen LIVE prüfen · Konsistenz: täglicher familie_guard-Lauf

§1 · Dienste-Landkarte — was wo läuft

DienstWasFundstelle
app.startact.aiCORE-App (Magic-Link-Login), nginx auf Hetznerdeploy.yml:33-56
gabriel.megabook.frgabriel-Cockpit + /api/skills (FastAPI)08_OPS/hetzner/README.md
skills.megabook.frkanonischer Skill-Host (LE-Zertifikat bis 30.08.)nginx-skills-megabook.conf
Hetzner-Server178.105.181.47 · CPX22 Nürnberg · Ubuntu 24.04 · 8,49 €/Monat08_OPS/hetzner
SupabaseProjekt egcdlozsvyhhvipylxky (DB + Auth + RLS)→ §5
n8njcbinetti.app.n8n.cloud, Projekt N0GxOHd4eVS1RsFY→ §6
GitHubjcbinetti/TGV (privat) · tgv-dashboard (public = ALTE v11 auf Pages)README.md
ContainerDocling (OCR) :5001 · Gotenberg (PDF) :3000 — auf dem Hetzner→ §4
IONOS10 Domains, FR-Kundennummer 457482269reference_domains_registry
Resend · DriveMail-Versand · Shared Drive „TGV-CORE" 0AB1RMdD8smYoUk9PVA→ §7
⚠ NICHT dokumentiert gefunden: eine aktuelle „alle URLs"-Tabelle als gepflegte Datei — die Landkarte oben ist aus verstreuten Quellen gemessen (reference-tgv-app-stack war 40 Tage alt). Diese Seite IST jetzt der Sammelort; Pflege-Ritual unten.

§2 · Der Arbeitsplatz (PC) — womit JC/Claude arbeiten

Werkzeuge: git · gh · PowerShell + Git-Bash · Python 3.13 (C:\Python313, verdrahtet in .mcp.json). Worktree-Modell: eine Session = ein Worktree unter .claude/worktrees/.

⛔ Die vier Arbeitsplatz-Fallen (alle real erlebt): ① SSH zum Hetzner NUR via Git-Bash (Windows-OpenSSH/PuTTY hängen am Host); JC-Key ~/.ssh/id_ed25519 → root. ② VPN AUS vor jedem Server-Schritt. ③ Absolute C:\Dev\TGV\repo-Pfade treffen den MAIN-Worktree, nicht deinen — Stashes sind repo-global. ④ Bash-Profil kann hängen → PowerShell nutzen; git nie in Start-Job; checkout master hängt im Worktree.

§3 · Repo & CI — die Betriebsregeln des Codes

↪ Konsolidiert: Was Worktrees/Branches/PRs/Freezes BEDEUTEN und wie der Weg einer Anforderung läuft, steht in der Gesamt-Arbeitsweise §3 — hier nur die konkreten Betriebs-Fakten.

Verzeichnis-Kanon README.md:5-30: 00_LEITDOKUMENT = Wahrheit … 99_ARCHIV; Credentials NUR 08_OPS/credentials (gitignored). Branch-Modell: master mit PR-Pflicht (Branch-Protection); Lanes auf eigenen Branches. ⚠ Staging deployt aus design02/native-screens-v3, nicht master.

CI-Workflow (8) — ✅ CI-PAUSE BEENDET (15.08.2026)Wacht über
deploy.ymlCockpits/CSS/i18n/Assets → Hetzner (Matrix, 443-Inhalts-Verify)
deploy-service.ymlFastAPI-Service (Bundle + Wrapper-Weg)
familie-guard.ymlDIESE Familie: C1–C9 + D1–D9, täglich per Cron + je Merge (Stempel im Index). ⭐ Lokal-Lauf bleibt Pflicht vor jedem Familien-PR — python 06_DASHBOARD/konzepte/familie_guard.py: der Cron findet den Bruch erst am NÄCHSTEN Morgen, der PR-Autor findet ihn sofort.
kanon-merge-freeze.ymlFreeze-Gate (misst Trigger, gilt auch gegen Tower)
kanon-quellen-ssot.ymlRegel-/Migrations-/Katalog-Divergenz
lint-wrappers.ymlSIGPIPE-Klasse (die Prüfer-lügen-Narbe)
skill-registry-ssot.ymlSkills-SSOT _registry.yaml
testdata-prod-guard.ymlTestdaten-in-Prod-Sperre (BR#04)
⭐ CI-Pause aufgehoben — die Wurzel war ein 0-USD-Budget, nicht ein kaputter Workflow (Register #448 → #453, 15.08.2026). Zwischen ~09.08. und 15.08. starben repo-weit ALLE Checks vor Step 1 (steps:[], 2–3 s) = Infra-Signatur, kein Guard-Befund. JC hat das Actions-Budget auf 30 USD/Stop-usage gesetzt; die übrigen Budgets bleiben bewusst 0. Beleg statt Behauptung: die familie-guard-Cron-Stempel liefen wieder durch — Commits aa64f0c0 (12.08.) · 61060a48 (13.08.) · 40412aef (14.08.) · 4ed6e286 (15.08.). Die Lehre bleibt betriebsrelevant: rote Checks vor jedem Merge EINMAL per Log-Blick klassifizieren — steps:[] = Infra (Merge legitim), echter Step-Fail = Guard-Befund (STOPP).

§3b · Deploy-Vollständigkeit — EIN Manifest statt Stückwerk #442b · 11.08.2026

Die Narbe: in der Nacht 10.→11.08. starb die Shell live. Ein Hand-Deploy war Stückwerk (engine/vendor/icons vergessen → 404), und eine Repo-nginx-conf überschrieb handgepflegte Live-Locations. Beides war nicht durch Sorgfalt zu verhindern, sondern nur durch eine Liste, die es vorher nicht gab.

Der SSOT: 08_OPS/deploy_manifest.json = die EINE Liste aller Serve-Artefakte (id · src · target · url · channel · ctype, teils marker für den Inhalts-Verify). Die Regel steht im Manifest-Kopf selbst (deploy_manifest.json:4): „Neues Serve-Artefakt = neue Zeile HIER + eigener CI-Job + eigene nginx-Location." Drei Prüfungen halten sie (08_OPS/deploy_guard.py): check-drift (Manifest ⊇ CI, rein lokal) · smoke (serven ALLE URLs 200 — read-only GET, EIN Report) · conf-diff (würde ein conf-Deploy handgepflegte Live-Zeilen clobbern? STOPP bei live-only, offline testbar via --live-file).

⛔ Was der Smoke sofort fand — und warum das der Beweis für das Manifest ist: vier Manifest-Zeilen ohne nginx-Location (404), eine Smoke-URL auf einen Host mit Cert-Mismatch, und abgleich2.html in einer Dreifach-Drift — Tür im Live-Menü, aber Datei in KEINEM der drei Register (Manifest · deploy.yml · nginx-Snippet), also nie deployt (Register #447). ⭐ Genau das ist die Klasse, die ein Mensch nicht findet: jedes Register für sich sah plausibel aus. Stand 15.08.: Smoke 38/38 (Register #453). ⛔ VPN-Falle: aktives GlobalProtect blockt 22/443 zu Hetzner → smoke/conf-diff scheitern mit Timeout, NICHT weil etwas kaputt ist.

⛔ Die Regel oben nennt DREI Orte — es sind VIER REGALE, und ein Merge erreicht nur eines. PLAT#10 · #548 · 22.08.2026, gemessen gegen master 7af15d5f /tgv_e4_labels.js war live 404, obwohl PR #1852 den Fix vollständig gemergt hatte. Der Grund stand nirgends im Code: eine Auslieferung braucht vier Regale, und drei davon berührt kein Merge.

RegalWoWird vom Merge bedient?
① Manifest08_OPS/deploy_manifest.json ✅ ja — und es stoppt den lokalen Voll-Deploy an der ersten Lücke (deploy_full.ps1:156, Design: kein stiller Teil-Deploy)
② CI-Trigger.github/workflows/deploy.yml paths: + changes-Job ⛔ nur scheinbar — der Job feuert ausschliesslich, wenn sich die QUELLDATEI ändert. Die e4-Datei lag seit 01.08. unberührt ⇒ der Job meldete in jedem Lauf skipped
③ Wrapper-Allowlistlive /opt/tgv-deploy/tgv-deploy-html.sh (root:root) ⛔ nein — nur 08_OPS/server/reinstall_wrappers.sh spiegelt ihn (root-Hand)
④ nginx-Locationlive /etc/nginx/snippets/tgv-skills.conf ⛔ nein — nur ein serve_*.sh setzt sie

Gegenmessung (warum es nicht „die Actions-Route macht es anders" war): die drei anderen .js-Artefakte sind live 200, weil sie alle vier Regale haben. ⭐ Die Wurzel ist nicht ein fehlender Fix, sondern ein Fix, der ein Regal von vier bediente — und dieses eine nur im Repo. Der Handgriff für ③+④ (08_OPS/server/serve_e4_labels_asset.sh) lag seit dem Merge da und war nie gelaufen: die Arbeit war getan, nur nicht zugestellt.

⭐ Der Wächter, der die Frage jetzt stellt: 08_OPS/check_deploy_route_traegt.py („die Route existiert, trägt aber nie") misst jedes Manifest-Artefakt gegen alle vier Regale, dazu die Gegenrichtung (Allowlist-Zeile ohne Manifest-Zeile = tote Tür) und die Drift der zwei Repo-Kopien der EINEN nginx-conf. Eingehängt in lint-wrappers.yml; die Auslöser umfassen bewusst Manifest, deploy*.yml und den nginx-Spiegel — sonst schläft er bei genau der Änderung, die ihn braucht. ⛔ Seine Reichweite steht in seinem Kopf — nach der Regel, dass eine Aussage über Nicht-Existenz nur so gross ist wie die Liste, die sie geprüft hat, und diese Liste deshalb mitführen muss: er misst ③+④ am REPO-Stand. „Repo grün" heisst das Regal ist gebaut, nicht es steht live — die Live-Frage beantwortet deploy_guard.py smoke (200 + Content-Type + Marker). Genau diese Lücke war die Wurzel.
⛔ Zweite, unabhängige Wurzel am selben Workflow: Gedränge an Port 22. PLAT#10 · #548 · 13 von 20 deploy-Läufen Rote Jobs wechselten (projekt2 · core_shell · rka · abgleich2 · fragekai2 · engine.js) und meldeten nie REFUSED, sondern Connection closed … port 22 (exit 255). Der Beweis ist die Uhr: zu jedem der 13 roten Läufe steht im journalctl -u ssh ein sekundengenaues drop connection #10 … Maxstartups. Wurzel: sshd -T = maxstartups 10:30:100, der Workflow öffnete aber bis zu 40 SSH-Verbindungen gleichzeitig (concurrency ist PRO DATEI und serialisiert nichts). ⇒ max-parallel je Matrix, Summe ≤ 10 (cockpit 3 · i18n 2 · css 1 · design01 1 · dataviz 1 · ux 1 · e4 1). Der Server wurde bewusst NICHT geschwächt — fail2ban zählt dort 8.499 Fehlversuche und 1.164 Banns. ⭐ Ein Workflow, der bei jedem zweiten Lauf aus Zufall rot ist, verdeckt echte Rots und trainiert das Wegsehen.
⭐ Drei Handgriffe, die aus dieser Messung Kanon werden — sie gelten über den Deploy hinaus: ① Eine Zahl ohne ihre Einheit ist eine Behauptung. „7 fehlende Locations" waren 4 Blöcke, die 7 URLs abdecken (location ^~ /DESIGN_01/ allein deckt drei). ② Vergleiche Mengen, nicht Zeilen. Ein diff der beiden nginx-Kopien meldete 33 Zeilen als „fehlend", die live nur an anderer Stelle stehen — ein Zeilen-diff hätte sie zerstört; der Mengen-Vergleich der location-Muster zeigte die wahre Lücke. ③ Miss einen Exit-Code nie hinter einer Pipe. python … | tail; echo $? liefert den Exit von tail — daraus entstand hier ein Falsch-Befund gegen zwei intakte Prüfer, der zurückgezogen werden musste.
⭐ Deploy-Wächter und Drift-Check: Abgleich mit der Expats-Ernte (EXPAT#01, 11.10., Register #4106). JCs Brief aus expatscoaching.de fordert vier Schritte: vergleichen, sichern, hochladen, Byte-Gleichheit prüfen. Dazu die Zuordnung der Live-Datei zu einem Commit. TGV hat davon: die Rückfall-Sperre (tgv-deploy-html.sh:256), den Kompendium-Austausch mit .old und index_sha256, und „was live verschwände“ nur für nginx (deploy_guard.py:197). deploy_guard.py parity läuft täglich über das Manifest. Es fehlt: eine Sicherung vor dem HTML-Überschreiben (tgv-deploy-html.sh:264). Die HTML-Prüfung vergleicht nur die Versionsmarke (deploy.yml:166: „NICHT VERIFIZIERT“). Der Drift-Abgleich normalisiert keine Zeilenenden und prüft gegen einen Ref statt gegen die Commit-Historie. ⇒ #4108, Vorschlag DEPLOY#04. Die Browser-Probe bei 375 und 1280 px über alle Flächen ist #4112; heute misst uxaudit_runner.py eine Breite je Lauf. Einzeltabelle mit Fundstellen: 00_LEITDOKUMENT/KONZEPT_WEBSITE_FACTORY.md §4.2b.

GitHub-Secrets (nur NAMEN — Werte leben in GitHub → Settings → Secrets): HETZNER_DEPLOY_KEY · HETZNER_HOST · HETZNER_DEPLOY_USER · HETZNER_KNOWN_HOSTS (optional) · HETZNER_SERVICE_DEPLOY_KEY deploy.yml:81-84 · INSTALL_DEPLOY.md:72-78.

§4 · Server-Runbook (Hetzner)

nginx: snippets/tgv-skills.conf + sites gotenberg/skills-megabook; Serving-Root /opt/tgv-skills/www. Deploy-Mechanik: /opt/tgv-deploy/*.sh = root-owned forced-command-Wrapper; ZWEI getrennte ed25519-Keys (html ohne sudo · service mit sudo NUR für systemctl restart|is-active tgv-skills); Deploy-User-Shell MUSS /bin/sh sein (nie nologin). Nach Wrapper-Änderung: bash 08_OPS/server/reinstall_wrappers.sh INSTALL_DEPLOY.md:34-107. User: tgvdeploy (Deploy) vs. root (JC-Admin). Container: docling-serve-cpu :5001 (mem 3g, Health /docling/health) + Gotenberg 8 :3000.

⛔ Wiederkehrende Server-Fallen: chat_engine-Verzeichnis root-owned → chown tgvdeploy · jedes neue www/<subdir> root-owned → chown · requirements.txt-Diff blockt Deploy (exit 7 → --apply --bootstrap) · Skill-Route ist /api/skills/ PLURAL.
⚠ Offen/NICHT gebaut: Container-OOM-Härtung (MemoryMax=3G) = offener Vorschlag (PLAT02_FOLLOWUP) · Media-Ebene 2 (Hetzner-Transcoding/ffmpeg) NICHT gebaut, keine Provisionierung dokumentiert — Ebene 1 (nativer <video>-Player + Range) ist der Stand.

§5 · Daten & KI-Dienste

↪ Konsolidiert: WIE Daten durch TGV fließen → Datenfluss/Touch-Once · Rollen × Stufen × RLS + EU-Stack → Sicherheit & Datenschutz First · Einstufung beim Intake → §Einstufung (Register #34). Hier nur die BETRIEBS-Fakten:

Migrations-Ritual: autoritativ ist NUR die Tabelle tgv_migrations — Datei-Header lügen (Kanon-Falle). DDL-Weg: Supabase Management-API POST /v1/projects/…/database/query mit Bearer-Mgmt-Token und User-Agent-Pflicht (sonst Cloudflare-403); Pooler/Direktverbindung unzuverlässig. Auth: Redirect-Allowlist MUSS app.startact.ai/** enthalten. SQL-Editor-Falle: leeres ARRAY = '{}'.

⛔ DEFINER-Falle: neue public-Funktionen bekommen automatisch EXECUTE für anon+authenticated — explizit revoken + verifizieren.
⛔⛔ Fortschreibung 16.08.2026 — der Ledger-Eintrag ist PFLICHT, auch in STAGED-Dateien (SCHEMA#01/TOWER#37, PR #1786, Register #460).
Gemessen: JC applizierte 252 → 251 → 240 → 250; alle vier meldeten „applied successfully". Danach zeigte public.tgv_migrations alle vier als NICHT APPLIED — 250/251/252 trugen gar keinen INSERT, 240 einen OHNE applied_at (die Spalte hat keinen Default → NULL-Zeile → jedes Werkzeug liest „nicht appliziert").
⭐ Die Wurzel war der Guard selbst, nicht die Dateien: er nahm STAGED-Dateien von der Ledger-Pflicht aus. Das war ein Denkfehler — der INSERT steht IN der Datei und feuert erst beim Apply; in einer STAGED-Datei ist er nicht falsch, sondern die EINZIGE Stelle, an der er später wirken kann. Und ein Guard, der die Pflicht auf den Apply-Moment verschiebt, prüft sie nie: beim Apply läuft keine CI.
Der Zustand jetzt: 08_OPS/check_migration_headers.py:175-186 — STAGED-Ausnahme gestrichen, dritte Frage applied_at, Selbsttest beidseitig (14 Fälle; gegen den alten master-Stand beisst er 13×, gegen den neuen 0×). Kopiervorlage: 05_SCHEMA/_LEDGER_BAUSTEIN.sql mit den drei Pflichten — ① existiert (auch STAGED) ② setzt applied_at ③ überschreibt ein Erst-Datum nicht (COALESCE). ⛔ ON CONFLICT DO NOTHING ist verboten: es lässt eine alte NULL-Zeile für immer NULL. Messwerkzeug (read-only, JC-Hand): 08_OPS/check_migration_ledger.py.
Die verallgemeinerte Lehre — sie gilt weit über Migrationen hinaus: Ein Ledger, das die Wahrheit nicht trägt, ist schlimmer als keines. Register und Memory behaupteten „251/252 APPLIED", das Werkzeug sagte „NICHT" — beide ungemessen, und der Ledger konnte niemanden widerlegen, weil ihm die Zeile fehlte.

KI-Dienste: Mistral mistral-embed 1024-dim, EU-Residenz executors_rag.py:13,90-101 · Anthropic: chat_engine DEFAULT_MODEL claude-sonnet-4-6, native Messages-API via urllib llm_client.py:29-31,93-154.

⚠ NICHT dokumentiert gefunden: Mistral-OCR — belegt sind nur Docling + Claude-Vision als OCR-Pfade. Falls Mistral-OCR im Einsatz sein soll: erst Beleg, dann Doku.

§6 · n8n — die Automatisierungs-Ebene

⭐⭐ LIVE-STAND 11.10.2026 (N8N#04, #4016) — KEIN n8n-WORKFLOW MUSS HEUTE NOCH LAUFEN. Gemessen 10:53–11:20Z, nur GET, mit den Werkzeugen des Hauses (08_OPS/mess_n8n_nutzung.py --tage 30 · 08_OPS/mess_n8n_offene_routen.py · 08_OPS/heimholung10/rufer_landkarte.py, alle drei Selbsttest grün vor dem Lauf) plus Einzel-Lesung der Ausführungen (Kopfzeile user-agent und Nutzlast-Schlüssel je Webhook-Lauf). Bestand 45 ohne Archiv · 31 aktiv · 14 inaktiv · 14 archiviert · 421 Läufe in 30 Tagen (älteste 11.09., Cursor erschöpft = alles, was n8n vorhält). 28 von 28 Routen aktiver Workflows sind ohne Anmeldung erreichbar (Negativkontrolle trägt). Dienst live 1.12.372: Verteiler "targets":{"local":20}, Router 0.9.7-py.
Antwort auf JCs Frage „was läuft noch, was muss laufen, wofür?“: es laufen noch 9 Workflows (8 Zeitgeber/Empfänger + 1 Fehlauslösung, s. u.); laufen müssen davon 0 — jeder hat einen Zwilling auf GitHub oder im Dienst, oder er tut nichts mehr. Die übrigen 22 aktiven sind still und haben keinen lebenden Rufer.
⛔⛔ DER EINZIGE NEUE LÄUFER DIESER WOCHE IST EINE FEHLAUSLÖSUNG — UND SIE HAT GESCHRIEBEN. TGV Skill Store File To Drive v0.7.3 (dT1VRLPnDdYxVIKH) lief 11-mal am 11.10. zwischen 01:10:10 und 01:10:59Z, Kopf Python-urllib/3.13, Nutzlast {doc_id, refile, target_root}. Die Beleg-Folge 6915 · 7234 · 7239 · 7221 · 7292 · 7293 · 7305 · 7306 · 7417 · 7423 · 7424 ist Zeichen für Zeichen die von 07_TESTS/iter38/a1_test_refile.py:26 (eine ID) plus 07_TESTS/iter38/a2_refile_batch.py:31 (zehn IDs) — zwei Testskripte vom Mai, die die n8n-Route fest eingebaut haben (a2_refile_batch.py:33). Gefahren wurden sie in der Sitzung BETRIEB#05 (deren git status zeigte beide Schnappschuss-Dateien geändert; dort inzwischen zurückgesetzt). Wirkung, an den Knoten gelesen: 11× Move File to Final Folder (Google Drive) und 11× Patch Document Storage Fields (PATCH auf documents: drive_file_id · drive_file_name · drive_folder_id · final_path · processing_state), alle storage_status=stored. ⛔ Ob sich dabei Werte geändert haben, ist NICHT gemessen — der Vorher-Stand steht nicht in der Ausführung. ⭐ Dieselbe Klasse wie #3964 (Prüfstand fährt Schreibwege scharf), nur über eine Tür, die kein Riegel im Repo sieht: die Route liegt in n8n, nicht im Dienst. ⇒ Die Kur ist der Schalter, nicht das Skript: solange die Route offen ist, kann jedes der Alt-Skripte (die Rufer-Landkarte zählt sie je Workflow, z. B. 08_OPS/agent_orch01/redispatch_bestand.py → Router, 08_OPS/tri02_e2e_harness.py → Orchestrator) live schreiben.
Workflow (ID)Auslöser30 d · 7 d · Fehler · zuletztZweck in einem Satz · wer ruftKlasseEmpfehlung
LM#07 Health Cron haD67MbvJcXUxGDIalle 6 h120 · 28 · 9 (24.–29.09.) · 11.10. 10:00ZFragt den Dienst nach seiner Gesundheit und meldet über Resend. Zwilling GitHub ① cron_health_check.py: Lauf 11.10. 05:34Z up v1.12.369, ALARMWEG-ANNAHME anbieter=brevo zustand=angenommen http=200② abgelöstErledigt — JC hat ihn am 11.10. abgeschaltet. Gegenprobe s. u.
Skill Periodic Engine tnuT3QN4AwnbYdSzCron 30 3 */2 + Webhook15 · 4 · 0 · 11.10. 01:30ZNabe: ruft per Webhook Cost Collector, Safety Net, Read-Back (Kopf n8n). Zwilling GitHub ④ cron_periodic_engine.py, Trockenlauf 11.10.: 4 von 4 Jobs via: local② abgelöstWelle 2, als erster
Skill Cost Collector DV1qyZgjSk7cTbimCron 0 3 + Webhook58 · 15 · 3 (13./14./22.09.) · 11.10. 01:30ZSammelt Kosten (n8n, Supabase, GitHub) in cost_metrics; liest dafür die n8n-Variable TGV_N8N_API_KEY. Zwilling GitHub ③ cron_cost_collector.py② abgelöstWelle 2
Contract Stub Safety Net eATEzPsbFjHUWcpDWebhook14 · 4 · 0 · 11.10. 01:30ZZieht Vertrags-Stubs nach. Einziger Rufer: Periodic Engine. Zwilling GitHub ⑤ + POST /api/skills/contract-stub-safety-net② abgelöstWelle 2
Entity Read-Back (R3) MkcSBtQersDjbtZqWebhook14 · 4 · 0 · 11.10. 01:30ZLiest Entitäten zurück. Einziger Rufer: Periodic Engine. Gegenstück /api/skill/classify-entity-readback über GitHub ④② abgelöstWelle 2
VAT Quality-Gate Poller 5MdBamTHJZE8yqBValle 6 h120 · 28 · 0 · 11.10. 10:00ZUSt-Qualitätstor auf documents.vat_quality_warnings. Zwilling GitHub ②a/②b (skill.py --apply + cron_vat_determine.py) — beide schreiben dieselbe Zelle② abgelöstWelle 2
Cron Mail Failure-Notify G9a0SNY1bMhPfIUStäglich30 · 7 · 0 · 11.10. 06:00ZSoll Mail-Ausfälle melden — meldet in die Route tgv-skill-send-mail, deren Workflow seit 05.09. inaktiv ist (#3794): kann nicht melden. Zwilling GitHub ⑧② abgelöstWelle 2
Iter 54 A6 Shadow Diff Cron O6WPZZWXpdG6MuDbtäglich30 · 7 · 0 · 11.10. 00:00ZVergleicht den A2-Schattenmodus; A2 ist inaktiv, SICHERHEIT#07 (#1918): „der Beobachter-Schalter schaltet keinen Weg“. Leser von shadow_diff: 0③ totWelle 1
Webhook Resend Bounce 8n3VhGkb4OqU7g8cWebhook, externer Rufer (Svix/Resend)9 · 0 · 0 · 21.09.Nimmt Rückläufer von Resend an. Dienst-Gegenstück POST /api/skills/mail/bounce (app.py:2742, seit 1.12.160, Signatur Pflicht). Brevo ist seit 10.10. Standard, und der letzte Resend-Sender (Health Cron, MAIL#06 #3936) ist seit 11.10. aus — ob wirklich nichts mehr über Resend geht, misst die Gegenprobe von Welle 3② abgelöstWelle 3 — nach dem Löschen des Webhooks in der Resend-Konsole
Skill Store File To Drive dT1VRLPnDdYxVIKHWebhook11 · 11 · 0 · 11.10. 01:10ZLegte Belege in Google Drive ab. Alle 11 Läufe = Fehlauslösung (Kasten oben). Gegenstück /api/skill/store-file-to-drive; der MCP ruft seit #1668 dorthin (tgv_mcp_server.py:2004)③ totWelle 1, als erster — heute
Cascade Trigger RKh1lq9ruckrpR24 → Skill Router mvOybw4cBU7y8X5Y → Orchestrator CbNkDkMss3YiHAmN → Pattern Engine RtTqaFYxiFtc9PdmWebhooks (Cascade-Zeitplan disabled)je 0 · 0 · 0 · —Die alte Verteiler-Kette. n8n-intern rufen nur inaktive Workflows (A2, WF B); Dienst /api/skills/dispatch und /api/skills/orch/plan③ totWelle 1, in dieser Reihenfolge
Image Vision Branch j5lervqxZggrEQUs → Fetch Drive Binary tBwu2D2vK8jIsU0BWebhooksje 0 · 0 · 0 · —Bildanalyse und Drive-Bytes. Rufer n8n-intern: nur WF B (inaktiv) bzw. Image Vision. Bytes kommen heute aus dem Bucket③ totWelle 1, in dieser Reihenfolge
Die 14 Skill-Blätter: PDF Invoice Extract · PDF Text Snippet · Parse Docling · RAG Ingest · Detect Duplicate · Resolve Supplier · Classify Party Role · Classify CSV · Import Banking CSV · Import Timetrack CSV · Tax Extract · contract-extract Wrapper · Merge Duplicates · PDF OCR FallbackWebhooksje 0 · 0 · 0 · —Die heimgeholten Skills; der Dienst bedient 20 von 20 lokal. Rufer ausserhalb: nur Alt-Skripte in 07_TESTS/ und 08_OPS/ und Rückweg-Zeilen in Docstrings. ⚠️ Parse Docling trägt ein Klartext-Credential (S2)③ totWelle 1, Reihenfolge frei
Skill Tax Send Package mZyMkagJPmMCNQqXWebhook0 · 0 · 0 · —Hat nie gesendet — Phase 1 erlaubt nur dry_run (#3152). Kein Rufer③ totWelle 1
Claude Proxy ceurPCxGciw1CN0vWebhook0 · 0 · 0 · —Alter Durchgang zu Anthropic; KAI läuft im Dienst. Rufer: Liste 1 KEINE, Liste 2 0 lebende. Steht unter JCs Schutz vom 16.08.③ totWelle 1 — nur mit JCs Ja zur Aufhebung des Schutzes

Die 14 inaktiven (KB Learn · B2 Body-Router · Docling Wake-Cron · Post Intake · Extract Contract PDF · Mail-Body-To-PDF · RAG Search · Reconcile Payment · Send Mail · Upsert Contract · WF A2 · WF B v2 · WF E · seit 11.10. Health Cron): 0 Läufe im Horizont, nichts zu tun. ⛔ Abschalten heisst Unpublish, nicht Löschen — der Rückweg bleibt ein Klick.

⭐ ABSCHALTPLAN IN DREI WELLEN — erst das Tote, dann das Doppelte, dann der externe Empfänger. WO: Chrome-Adresszeile → https://jcbinetti.app.n8n.cloud → Workflow öffnen → oben rechts Unpublish (dieselbe Stelle, an der JC am 11.10. den Health Cron abgeschaltet hat). n8n anfassen ist JCs Hand.
Welle 1 — 24 Schalter, Preis null (③): Store File To Drive zuerst (schliesst die Tür der Fehlauslösung) · dann Cascade → Router → Orchestrator → Pattern Engine · Image Vision → Fetch Drive Binary · die 14 Blätter · Tax Send · Shadow Diff · Claude Proxy (nur mit Ja). Gegenprobe — Git-Bash: cd /c/DEV/TGV && python 08_OPS/mess_n8n_offene_routen.py ⇒ jede abgeschaltete Route WEG, Negativkontrolle traegt; Dienst /api/skills/health weiter "targets":{"local":20}.
Welle 2 — 6 Schalter, Preis null (②, beendet das Doppelschreiben): Periodic Engine zuerst, dann Cost Collector · Safety Net · Read-Back · VAT Poller · Mail Failure-Notify. Vorbedingung erfüllt (11.10.): cron_periodic_engine.py --dry-run zeigt 4 von 4 Jobs via: local. Gegenprobe: der nächste betriebs-crons-Lauf je Schritt ②③④⑤⑧ success; in n8n danach 0 Läufe (mess_n8n_nutzung.py --tage 30 ⇒ 7d=0 nach sieben Tagen). Mit dem Cost Collector entfällt auch der letzte Leser der n8n-Variable TGV_N8N_API_KEY.
Welle 3 — 1 Schalter: erst in der Resend-Konsole den Webhook auf …/webhook/tgv-mail-bounce löschen, dann Resend Bounce aus. Gegenprobe: keine Resend-Sendung seit 11.10. in outbound_mails.
Danach ist n8n leer gelaufen: 0 aktive Workflows, 0 offene Routen. Ob das n8n-Konto bleibt (als Rückweg) oder gekündigt wird, ist eine Geschäftsfrage an JC, keine Bahn-Entscheidung.
🔑 n8n-API-Schlüssel — wo sie gebraucht werden (11.10., gemessen): 08_OPS/credentials/.env N8N_API_KEY (die lokalen Mess-Werkzeuge, zuletzt 11.10. 10:53–11:20Z durch N8N#04) · GitHub-Geheimnis N8N_API_KEY (gesetzt 08.08.; betriebs-crons.yml:77, betriebs-waechter.yml:60) · n8n-Variable TGV_N8N_API_KEY (nur der Cost Collector). Nicht im Dienst (deploy-service.yml und tgv-skills-service/*.py: 0) und nicht im MCP-Server (tgv_mcp_server.py:41, seit MCP#01 entfernt). Der n8n-Schlüssel im alten Manus-Zip (#3933) ist nicht der .env-Schlüssel (Gleichheitsprobe, kein Wert gelesen). ⛔ Welcher n8n-Name zu welchem Ort gehört, gibt die n8n-API nicht heraus (keine Schlüsselliste in der öffentlichen API). ⇒ Empfehlung: die zwei benutzten (TGV-2026-06, TGV-Claude-code) bleiben; die sieben nie benutzten (n8n 4 codex · Claude TGV · Manus TGV Session 2026 · Manus TGV API 2027 · Manus TGV Temp 2026 · n8n for Claude · Manus TGV) widerrufen — kein Rufer kann an ihnen hängen, sonst stünde dort ein Datum. WO: Chrome-Adresszeile → https://jcbinetti.app.n8n.cloud → links unten Settings → n8n API → an der Zeile das Menü ⋯ → Delete → bestätigen.
⛔ R-193 — Reichweite dieser Messung. „Kein Rufer“ heisst: nicht gefunden in n8n (alle 45 Definitionen, Knoten mit Webhook-URL), im Repo (02_SKILLS · 08_OPS · 06_DASHBOARD · 05_SCHEMA · .github · 03_WORKFLOWS · 07_TESTS · 01_ITERATIONS, Rufer-Landkarte), im Dienst (/api/skills/health + dispatch), im MCP-Server und in den Ausführungen der letzten 30 Tage (Kopfzeile je Webhook-Lauf). Nicht geprüft: externe Rufer ausser den in 30 Tagen beobachteten (Svix/Resend) · n8n-Zugriffsprotokolle · Werte von documents vor der Fehlauslösung · Routen inaktiver Workflows. Nichts wurde in n8n geschrieben.
⭐ NACHTRAG 11.10.2026 (N8N#05, #4056) — WELLE 1 UND 2 SIND GESCHALTET. JC hat am 11.10. Welle 1 (24 inkl. Claude Proxy) und Welle 2 (6 Zeitgeber) abgeschaltet. Gemessen von TOWER#71 mit mess_n8n_offene_routen.py: aktiv ist nur noch TGV Webhook Resend Bounce v0.1. Die GitHub-Zwillinge in betriebs-crons.yml liefen am 11.10. alle scharf und success (① ② ③ ④ ⑤ ⑧; Periodic 4/4 via local, ebenfalls TOWER#71).
Zwei Rückwege nach n8n sind damit tot — und beide sind jetzt laut statt still: ① cron_periodic_engine.py schickte einen Job ohne LOKALE_ZIELE-Eintrag an periodic_jobs.webhook_url (alle vier Zeilen: …n8n.cloud/webhook/…, live gemessen 11.10. ~15:15Z). Seit #4056 bricht er laut ab (kein_lokales_ziel, rot, mit Event). Die Spalte wird nur noch beschriftet: lokal://<job_name>, dieselbe Form wie bei BUCHHALTUNG#14 (#4047); die Vorlage dafür ist 05_SCHEMA/PROPOSED_n8nrest01_periodic_jobs_n8n_url_einziehen.sql, gefahren vom Tower auf JCs Wort. ② cron_health_check.py fiel bei nicht ladbarer Anbieter-Tabelle still auf api.resend.com zurück. Jetzt scheitert er laut. Der Alarm sendet heute über den eingeschalteten Anbieter Brevo (Annahme-Probe 11.10. 05:34Z angenommen); der letzte Resend-Versand in outbound_mails war am 21.09..
⛔ WELLE 3 UND DIE SIEBEN SCHLÜSSEL — DEINE SCHRITTE, JC.
Vorbedingung für Welle 3: MAIL#07 (Brevo-Rückläufer im Dienst) ist live. Vorher nicht — sonst hat TGV gar keinen Rückkanal mehr.
Schritt 1 — Resend-Webhook löschen. Chrome-Adresszeile: https://resend.com/webhooks → den Eintrag mit dem Ziel https://jcbinetti.app.n8n.cloud/webhook/tgv-mail-bounce öffnen → Delete → bestätigen.
Schritt 2 — den letzten n8n-Workflow aus. Chrome-Adresszeile: https://jcbinetti.app.n8n.cloud → TGV Webhook Resend Bounce v0.1 öffnen → oben rechts Unpublish. Nicht löschen: der Rückweg bleibt ein Klick.
Schritt 3 — die sieben nie benutzten API-Schlüssel widerrufen. Chrome-Adresszeile: https://jcbinetti.app.n8n.cloud → links unten Settings → n8n API → je Zeile Menü ⋯ → Delete → bestätigen, für: n8n 4 codex · Claude TGV · Manus TGV Session 2026 · Manus TGV API 2027 · Manus TGV Temp 2026 · n8n for Claude · Manus TGV. Behalten: TGV-2026-06 und TGV-Claude-code (die zwei mit einem „zuletzt benutzt“-Datum). Schritt 3 hängt nicht an MAIL#07 und geht sofort.
Gegenprobe nach Schritt 2 (fährt der Tower) — Git-Bash: cd /c/DEV/TGV && python 08_OPS/mess_n8n_offene_routen.py ⇒ ROUTEN aktiver Workflows: 0. Nach Schritt 3: in n8n stehen genau noch 2 Schlüssel, und die Mess-Werkzeuge laufen weiter (der .env-Schlüssel ist einer der beiden).
⭐⭐ LIVE-STAND 27.08.2026 — WAS FÄLLT, WAS IST FERTIG, WAS FEHLT. Gemessen mit 08_OPS/mess_n8n_nutzung.py --tage 30 gegen die n8n-API (nur GET) und gegen system_config; fortgeschrieben aus 08_OPS/heimholung09/MESSUNG_N8N_INFRASTRUKTUR_2026-08-22.md (#551). Bestand 45 Workflows (39 aktiv), 14 archiviert und nicht mitgezählt · 2.656 Executions. ⛔ HORIZONT, gemessen statt angenommen: n8n hält 30 Tage vor (älteste 28.07., Cursor erschöpft). Jede 0 unten heißt „0 im Fenster" — nie „lief nie". Ein monatlicher Auslöser sieht hier aus wie ein toter.
⛔⛔ DIE UMSCHALTUNG IST EIN WETTLAUF, KEIN SCHALTER — UND ER LÄUFT SEIT VIER TAGEN. (Die folgende Fassung war zu freundlich — siehe den Nachtrag darunter.) use_local_mail_intake = true steht seit 23.08. 22:26, und der lokale Poller arbeitet: 988 mail_intake.poll.done seit dem Umlegen, zuletzt 27.08. 11:16. ⛔ Und n8n-A2 läuft weiter: 8 Läufe NACH dem Umlegen, zuletzt 27.08. 10:20. Zwei Eingänge lesen dieselbe Mailbox. ⭐ Schaden bisher gemessen: keiner — 18 Belege seit dem Umlegen, davon 6 aus Mail, 0 Dubletten-Gruppen. Ein Wettlauf, den bisher immer derselbe gewinnt, ist von einer sauberen Umschaltung nicht zu unterscheiden — bis er es einmal nicht tut. ⇒ A2 abschalten ist der dringlichste der offenen Handgriffe, nicht der letzte.
⛔⛔ NACHTRAG 27.08. — DER LOKALE EINGANG IST SCHARF UND LIEFERT NICHTS. A2 GEWINNT DEN WETTLAUF JEDES MAL. Der Absatz darüber sagt „der lokale Poller arbeitet" — gemessen arbeitet er ins Leere: 1.011 mail_intake.poll.done · 0 mit Beleg, jüngste Ausgabe {"apply": true, "mails": 0, "seen": 0, "errors": 0}. Gegenprobe an den Belegen: von den 18 Zeilen seit dem Umlegen sind 6 aus Mail, und alle sechs tragen einen Drive-Dateinamen A2_2026-08-… samt drive_file_id — A2 hat jede einzelne geschrieben. ⭐⭐ Vermutete Ursache: A2 setzt die Mail zuerst auf \Seen, danach sieht der lokale Poller nur noch eine leere UNSEEN-Liste. ⛔ Damit ist „A2 ausschalten" kein Aufräum-Handgriff, sondern der Umschaltmoment selbst — und der erste Augenblick, in dem sich überhaupt zeigt, ob der lokale Eingang trägt. Ein Port, der nie eine echte Mail verarbeitet hat, ist nicht bewiesen, nur bereit.
⛔⛔ UND DAS STILLLEGE-WERKZEUG KANN A2 NICHT ABSCHALTEN — AUS BAUART, NICHT AUS FEHLER. Trockenlauf mit der gemessenen Grenze (TGV_FLIP_GRENZE=2026-08-23T22:26:00): A2 und Cascade sind ROT („8 Läufe nach dem Flip"), B2-Body-Router · Mail-Body-To-PDF · Triage Agent sind grün und würden stillgelegt. ⭐⭐ Der Frische-Guard ist ein Stillleger von Totem, kein Schalter für Lebendes — und ein Workflow, der abgeschaltet werden SOLL, kann die Bedingung „0 Läufe seit dem Flip" nie erfüllen. Das ist keine Fehlfunktion: der Guard schützt richtig. Er deckt nur den einen Fall nicht ab, der zählt. ⇒ A2 + Cascade sind JC-Hand in der n8n-Oberfläche, die drei grünen kann das Werkzeug übernehmen.
⛔⛔ UND DIE REIHENFOLGE DER ALTEN LANDKARTE DREHT SICH UM — WEGEN EINES ECHOS, DAS TGV SELBST ERZEUGT. --gruppe drive verweigert heute zu Recht: der Frische-Guard ist ROT, weil nach JCs Entscheid vom 22.08. eine neue Datei kam. Nachgesehen ist es #8264, Dateiname A2_2026-08-23_unknown_in… — ein A2-Mailanhang, den A2 selbst in den Drive-INBOX gelegt hat und den der Adapter am nächsten Morgen wieder aufsammelte. ⭐⭐ Die alte Karte sagte drive → null → a2 → drive-glieder. Richtig ist a2 → drive → null → drive-glieder: solange A2 läuft, füttert es den INBOX, der Guard bleibt rot — erst wenn A2 aus ist, verstummt das Echo von selbst.

Die exakten Namen, aus der n8n-API gelesen — damit geklickt wird, was auf dem Schirm steht: Ⓐ Workflow TGV WF A2 Mail Intake + Universal Triage v01.34 (fx9POuhD3ybynhat), oben rechts der Active-Schalter; der Rückweg ist derselbe Schalter. Ⓑ der kleinere Schritt: im selben Workflow den Knoten Cascade Trigger Fire (HTTP Request → /webhook/tgv-cascade-trigger) → Deactivate; ⛔ das stoppt nur die Kaskade, nicht den Mail-Eingang und ersetzt Ⓐ nicht. ⛔ Und nach Ⓐ muss binnen Minuten geprüft werden, ob ein mail_intake.poll.done mit mails > 0 erscheint — kommt keiner, A2 sofort wieder an. Ohne diese Prüfung ist das Abschalten fahrlässig.

⛔ Und „Drive-Adapter-Ziel umhängen" ist obsolet, nicht offen: JC am 22.08. 16:40 wörtlich „Inbox Drive nutze ich nicht mehr. Mail factures oder bald KAI." ⇒ der Kopf fällt ganz (#566). Wären es Umhängungen, wären es vier Knoten, nicht einer: POST Skill Router · POST Skill Router Docling · POST Orchestrator · POST Orchestrator Docling.

⭐ WAS FERTIG IST — der Dienst zeigt nirgendwo mehr nach n8n: SKILL_TARGETS in dispatch.py führt 0 Ziele der Art n8n; /api/skills/health meldet unabhängig "targets":{"local":20}. Die zehn ("n8n", …)-Zeilen, die eine Suche in app.py noch findet, stehen in Docstrings als benannter Rückweg — je eine Zeile, absichtlich wörtlich hinterlegt. ⛔ Zwei echte Bindungen bleiben, beide env-gebunden: N8N_BUCHHALTER_WEBHOOK und N8N_SEND_MAIL_WEBHOOK in app.py.

⭐ UND DER REPO-REST IST WEG: die Landkarte vom 22.08. nannte dispatch.py:496 als letzten Repo-Rufer des n8n-Orchestrators. Er steht heute als auskommentierter Rückweg da (dispatch.py:41), nicht als Aufruf. Das ist die einzige Position der alten Karte, die sich von selbst erledigt hat.

Workflow30dseit 22.08.zuletztStand
WF A2 Mail Intake fx9POuhD3ybynhat511027.08. 10:20⛔ DOPPELT — lokal längst scharf, n8n nie abgeschaltet
Cascade Trigger RKh1lq9ruckrpR24441027.08. 10:20⛔ feuert bei jeder Mail mit A2 — fällt mit A2
WF B Drive Adapter KRNRcSLSUiVC8nTW30627.08. 10:00⛔ JC hat am 22.08. „stilllegen" entschieden — es ist nicht geschehen
Health-Cron · VAT-Pollerje 120je 2227.08. 10:00⛔ Ports existieren (betriebs-crons), n8n läuft weiter
Cost Collector · Periodic Engine · Entity Read-Back · Contract Stub Safety Net62 · 16 · 16 · 1712 · 3 · 3 · 327.08. 01:30⛔ dieselbe Lage: portiert, nicht abgelöst
Skill Router mvOybw4cBU7y8X5Y356223.08. 10:00lebt nur noch über den Drive-Adapter
Skill Orchestrator CbNkDkMss3YiHAmN116223.08. 10:00ebenso — kein Repo-Rufer mehr
B2 Body-Router · Mail-Body-To-PDF56 · 26011.08.still seit 16 Tagen, fällt mit A2
Triage Agent bzbnkMvWF0FcWYI800—⭐ seit dem 22.08. INAKTIV — der einzige vollzogene Schritt
Post Intake · KB Learn · WF E v1.7.2 · Docling Wake-Cron00—inaktiv, gefallen
RAG Search · Claude Proxy00—aktiv ohne Lauf; Claude Proxy ist JC-geschützt
⛔⛔ WAS BIS ZUR UNABHÄNGIGKEIT FEHLT — UND ES IST NICHT MEHR PORTIER-ARBEIT, SONDERN ABSCHALT-ARBEIT. ⭐ Das ist der Unterschied zur Karte vom 22.08., und es ist der ganze Befund: in fünf Tagen ist ein einziger Workflow abgeschaltet worden (Triage Agent), während acht weiterlaufen, deren lokaler Ersatz fertig, ausgeliefert und gemessen ist. Es fehlt nichts Gebautes mehr. Es fehlt, dass jemand den Schalter umlegt — und Abschalten ist JC-Hand (n8n-Schreibzugriff). ⇒ Die n8n-Unabhängigkeit hängt heute an vier Handgriffen, nicht an vier Bauvorhaben.

Die vier Handgriffe, in der Reihenfolge, die das Werkzeug erzwingt (WO: Git-Bash, Repo-Wurzel; ohne --apply schreibt nichts): ① python 08_OPS/heimholung09/stilllegen_null_laeufer.py --gruppe drive — der Drive-Kopf, JCs Entscheid vom 22.08. ② --gruppe null — die Null-Läufer. ③ A2 + Cascade abschalten (Gruppe a2) — beendet den Wettlauf um die Mailbox. ④ --gruppe drive-glieder — erst danach, der Riegel misst selbst, ob der Kopf wirklich still ist. ⛔ Der Riegel meldet vor Schritt ③ zu Recht „Cascade Trigger ruft den Router" — das ist kein Fehler, sondern die Reihenfolge.

Heimholungs-Bilanz — die Antwort auf „sind wir fertig?" (30.08. abends)

⭐⭐ 30 VON 35 AKTIVEN WORKFLOWS KÖNNEN STERBEN. 5 NICHT — UND NUR 2 DAVON SIND ECHTE ARBEIT. ⛔ Die Achse ist bewusst „kann es weg?" und nicht „welche Klasse?": ein Workflow kann heimgeholt UND tot sein (Merge Duplicates hat einen lokalen Ersatz und 0 Läufe), die Klassen überschneiden sich also. JCs Frage ist die nach dem Wegkönnen.
Kann sterben — 30BelegLäufe 30d/7d
Router · PDF-Invoice-Extract · Detect-Duplicate · RAG-Ingest · Classify-Party-Role · Store-File · Resolve-Supplier · PDF-Text-Snippet · Image-Vision · Parse-Docling · contract-extract · Classify-CSV · Import-Banking-CSV · Import-Timetrack · Tax-Extract · Merge-Duplicates · RAG-Search · Tax-Send-Package18 · Ziel in SKILL_TARGETS356 … 0 / alle 0
Orchestrator/api/skills/orch/plan116 / 0
Health · Cost Collector · Periodic Engine · Contract Stub Safety Net · Entity Read-Back5 · Port in betriebs-crons.yml⛔ laufen noch 28·13·3·3·3
Cascade Trigger · Fetch Drive Binary · PDF OCR Fallback3 · ⛔ fallen mit ihrem AUSLÖSER, nicht weil ein Ersatz da wäre8 / 0 / 0
⛔ KANN NICHT STERBEN — 5, mit Namen: Claude Proxy (JC-geschützt seit 16.08.) · Webhook Resend Bounce (24/19 — eingehender Webhook, n8n ist hier der EMPFÄNGER; der aktivste Workflow im Haus) · VAT Quality-Gate Poller (120/28 — der Prüfer liegt lokal, der Cron nicht) · Mail Failure-Notify (30/7) · A6 Shadow Diff Cron (30/7 — shadow_runner.py ist verwandt, als Ersatz nicht belegt).
⛔⛔ ZWEI EHRLICHKEITEN ZUR ZAHL, damit sie nicht glatter wirkt als sie ist. ① 30 = 28 sicher + 2 wahrscheinlich. Bei Send Mail (7/0) ist nicht gemessen, ob der lokale Weg den Versand trägt — app.py kennt ihn achtmal, das ist eine Fundstelle und kein Beweis. ② ⭐⭐ „Fällt mit seinem Auslöser" ist NICHT dasselbe wie „hat einen Ersatz". Für Pattern Engine, Fetch Drive Binary und PDF OCR Fallback fand ich in keinem Regal einen Ersatz; sie stehen bei den Sterbekandidaten, weil ihr Auslöser still ist — ein anderer Grund, und er darf nicht für denselben gehalten werden.
⛔ Und eines ist KEIN Sterbekandidat, sondern eine Baustelle mit Datum: WF-E Buchhalter-Versand, 0 Läufe in 30 Tagen — die 0 heisst MONATLICH, und er ist zum 01.09. fällig. Umbau auf den Zeiger-Kanon, keine Stilllegung. ⭐ JC-Entscheid 30.08.: erst an ihn, der Buchhalter bekommt nichts vor seiner Abnahme.
⛔ REICHWEITE DIESER BILANZ (R-193): „hat einen lokalen Ersatz" ist an zwei Regalen gemessen — SKILL_TARGETS und betriebs-crons.yml. Ein Ersatz an einem dritten Ort wäre mir entgangen. Gemessen mit 08_OPS/mess_n8n_nutzung.py --tage 30, nur GET.

Der Ablöse-Wellenplan — Stand 30.08., und die Messung hat ihn kleiner gemacht

⭐⭐ DER BEFUND, DER DEN PLAN ÄNDERT: DIE GANZE LESER-FAMILIE IST SCHON STILL. Gemessen 30.08. (mess_n8n_nutzung.py --tage 30): Skill Router · PDF Invoice Extract · Detect Duplicate · RAG Ingest · Orchestrator · Classify Party Role · Resolve Supplier · Pattern Engine · PDF Text Snippet — alle 7d=0, zuletzt am 23.08. ⭐ Nicht weil jemand sie abgeschaltet hätte, sondern weil ihr Auslöser nichts mehr liefert. ⇒ Es geht nicht darum, Verkehr umzuleiten, sondern stille Workflows zu schliessen — das ist eine andere, viel kleinere Arbeit als „Ablöse".
⛔ WELLE 0 — DAS NUTZLAST-MAPPING, VOR ALLEM ANDEREN (#917). Sechs Leser bekommen bei den Belegen, die nur im Speicher liegen, keine Adresse — ein vorhandenes input_mapping.fields schaltet den reparierten Fallback ab. ⭐ Jede Welle, die einen n8n-Leser vor dieser Korrektur abschaltet, schaltet auf einen Ersatz um, der diese Belege nicht sieht. Ausführung ist JCs Hand (Registry-SQL mit beidseitigem Gegenbeweis).
WelleWasLäufe 30d / 7dBeleg der TragfähigkeitUmschaltmoment · Rückweg
1 · KopfWF B v2 Drive Adapter31 / 7⛔ kein Ersatz nötig: JC 22.08. „Inbox Drive nutze ich nicht mehr“, und ab 01.09. gibt es die Drive-INBOX nicht mehrersatzlos stilllegen · Active-Schalter
2 · LeserRouter · Orchestrator · Invoice-Extract · Snippet · Docling · Detect-Duplicate · RAG · Party-Role · Resolve-Supplier · Pattern-Engine · Image-Vision · Fetch-Drive-Binary · CSV-Familie · Tax-Extractje 1–356 / je 0⭐ seit 23.08. still — sie hängen alle am Kopf aus Welle 1nach Welle 1, mit Riegel je Glied · Active-Schalter
3 · SchreiberStore File To Drive113 / 0⛔ braucht ABLAGE#02: erst wenn der Bucket-Ausgang misst, dass er trägt⛔ nicht vorher — ein Ausgang ohne gemessenen Ersatz ist ein Abriss, kein Umhängen
4 · VersandWF-E Buchhalter-Anhänge0 / 0⛔⛔ die 0 heisst hier MONATLICH, nicht tot — das August-Paket ist zum 01.09. fälligUmbau auf den Zeiger-Kanon, nicht Stilllegung
⛔⛔ WELLE 4 IST DIE EINZIGE MIT EINEM DATUM — UND IHRE NULL IST DIE GEFÄHRLICHSTE ZAHL IM PLAN. wf_e_live_fetch_attachments.js:52 lautet if (!d.drive_file_id) { continue; } — der Versand hängt ausschliesslich Drive-Dateien an. Vier August-Belege liegen nur im Speicher und kämen stumm ohne Anhang durch. ⭐ Ein Workflow mit 0 Läufen in 30 Tagen sieht aus wie ein toter und ist hier ein monatlicher — das Werkzeug warnt selbst davor, und genau diese Zeile hätte man beim Aufräumen mitgenommen.
⭐ DIE KALENDER-VORBEDINGUNG IST HINFÄLLIG, UND DAS SPART EINEN BAU. Der lokale INBOX-Poller hat nur workflow_dispatch und keinen Kalender — solange der Drive-Adapter ersetzt werden sollte, war das eine Vorbedingung. ⛔ Er wird aber nicht ersetzt, sondern fällt weg: nach dem 01.09. gibt es keine Drive-INBOX mehr zu pollen. ⇒ Kein Cron, kein Secret, kein PR. Eine Vorbedingung, die aus einer Annahme stammt, fällt mit der Annahme.
⛔ NICHT IN DEN WELLEN, und das ist Absicht: Health-Cron (121/28) · VAT-Poller (121/28) · Cost Collector (60/13) · Shadow-Diff (30/7) · Mail-Failure-Notify (30/7) · Resend-Bounce (21/16) · Periodic Engine · Entity Read-Back · Contract Stub Safety Net. Sie laufen, sie reden nicht mit Drive, und sie gehören zur Cron-Ablöse — einer anderen Sache mit einem anderen Beweis. Sie hier mitzuzählen hätte den Plan grösser aussehen lassen, als er ist. Claude Proxy bleibt JC-geschützt.
⛔ WAS DIESER PLAN NICHT MISST: ob ein Leser nach dem Umschalten die Bytes wirklich holt (nur, ob er die Adresse bekommt — Welle 0) · ob ein Workflow fachlich entbehrlich ist (das entscheidet die besitzende Lane, nicht die Lauf-Zahl) · den Bestand in Drive selbst. Und jede Stilllegung ist JC-Hand — der Riegel stilllegen_null_laeufer.py misst je Glied selbst, ob der Kopf wirklich still ist.
⛔ R-193 — was diese Karte NICHT gemessen hat: das ausgelieferte Bundle auf Hetzner (ssh, JC-Hand) · ob ein n8n-Workflow fachlich entbehrlich ist (das entscheidet die besitzende Lane, nicht die Lauf-Zahl) · die 14 archivierten Workflows (kein Bestand). Und nichts wurde auf n8n geschrieben — alle Zahlen stammen aus GET.
⛔ Batch-Regel: Cascade-Batches 5–10, NIE 50 (Worker-Crash deaktiviert den ganzen Workflow; SplitInBatches 5).

Workflows (Stand-Liste 16.05. in TGV_CORE_V1_ARCHITECTURE_CURRENT.md:23-59 — 2 Monate alt, live nachprüfen): A2 Mail-Intake · WF-B Drive (KRNRcSLSUiVC8nTW) · Cascade · Skill-Router · Orchestrator · Post-Intake · WF-D Reclass · Send-Mail · WF-E Buchhalter · Failure-Notify · Resend-Bounce; JSON-Backups in 03_WORKFLOWS/. Drive-Zugang: Service-Account tgv-n8n-service@… (Credential RLAzXGpz6RT5OW3q) statt OAuth. Trigger: WF-B-Webhook + MCP run_inbox_cascade; API per X-N8N-API-KEY. Schedules: WF-B täglich 12:00 UTC; die 3:00-Cascade ist DISABLED.

$vars (nur ORTE — Werte leben in n8n → Variables): TGV_SUPABASE_URL · TGV_SUPABASE_SERVICE_ROLE_KEY · TGV_SUPABASE_MGMT_PAT · TGV_N8N_API_KEY · TGV_ANTHROPIC_API_KEY · TGV_OPENAI_KEY · TGV_RESEND_API_KEY · TGV_RESEND_WEBHOOK_SECRET · TGV_DOCLING_URL.

Die Zeitgeber — von GitHub nach Hetzner (ZEITGEBER#01, 14.09., #1931 · #1934 · #1935)

⭐⭐ DER EHRLICHE PUNKT (N8N#03 §4 U1): die sieben Zeitgeber-Zwillinge liefen auf GitHub Actions, nicht auf Hetzner. Schritt A der Abschaltliste beendet nur das Doppel und erfüllt JCs Ziel „Kein Stress mit n8n und alle Workflows in Hetzner" zur Hälfte. Gemessen 13.09. (#1931): der 03:00-Cron lief um 08:11 UTC, der 03:30-Cron um 08:27 — rund fünf Stunden Verzug, weil GitHub Zeitpläne unter Last verschiebt. Und: von sechs Cron-Skripten in 08_OPS/betrieb/ fuhr nur eines im Dienst-Bundle mit (deploy_lm04_2:278-283) — ein Umzug ohne diese Zeile wäre ein ImportError gewesen.

Die Bauart (Rang 1, #1934) — nichts neu erfunden: ein Baustein 08_OPS/betrieb/zeitgeber.py (Kalender in UTC, zeichengleich zu betriebs-crons.yml · Takt · Meldeweg) und ein Thread in app.py nach dem Muster _mail_intake_loop: erst warten, dann arbeiten; ein Fehler wird Zustand, nie der Tod des Takts; Zustand im /api/skills/health unter zeitgeber. ⭐ EIN Weg, zwei Rufer: jedes Cron-Skript trägt zeitgeber_lauf(apply, herkunft) — dieselbe Funktion ruft main() auf dem GitHub-Runner und der Scheduler auf Hetzner. Sonst wäre die Zeilengleichheit, die vor dem Umschalten gemessen wird, durch Bauart verschieden. Read-Back und Safety-Net laufen im Dienst in-Prozess (kein HTTP-Selbstaufruf über nginx).

Zeitgeber (Stempel)n8n-OriginalGitHub-ZwillingKalender UTCHetzner-Schalter system_config.*Stand
cron-cost-collectorCost Collector DV1qyZgjSk7cTbim③0 3 * * *zeitgeber_hetzner_cost_collectorgebaut; Quelle n8n enthält sich benannt, wenn N8N_API_KEY nicht in der Dienst-.env steht
cron-periodic-enginePeriodic Engine tnuT3QN4AwnbYdSz (ruft Cost, Safety-Net, Read-Back)④30 3 */2 * *zeitgeber_hetzner_periodic_enginegebaut; Read-Back MkcSBtQersDjbtZq läuft mit (in-Prozess)
cron-contract-stub-safety-netSafety Net eATEzPsbFjHUWcpD⑤30 1 * * *zeitgeber_hetzner_safety_netgebaut; Rahmen lag schon im Dienst (/api/skills/contract-stub-safety-net), es fehlte nur der Takt
cron-vat-determine → cron-vat-pollerVAT Quality-Gate Poller 5MdBamTHJZE8yqBV②a → ②b5 */6 * * *zeitgeber_hetzner_vat_poller (beide Hälften)gebaut; Reihenfolge erst bestimmen, dann prüfen (FES-AP4)
cron-mail-failure-notifyMail Failure-Notify G9a0SNY1bMhPfIUS (tot seit 05.09.)⑧5 */6 * * *zeitgeber_hetzner_mail_failure_notifyRang 2 (#1935) zuerst repariert: from/to in der Nutzlast, Transport = Buchführung, Stempel gegen Ausnahme, mailfail/ausgeliefert/vatdet im Rot-Block
cron-health-checkLM#07 Health haD67MbvJcXUxGDI①5 */6 * * *—⛔ bleibt auf GitHub: „der Zeuge muss ein anderer sein als der Beobachtete"
⛔ ZWEI SCHALTER, beide fail-closed, beide ohne Deploy umlegbar — und keiner davon schaltet n8n oder GitHub AUS. ① TGV_ZEITGEBER_TICK_SEC in /etc/tgv-skills/.env (rendert _build_env_file; 0 oder leer = kein Thread) startet den Takt. ② system_config.zeitgeber_hetzner_<name> = true lässt den einzelnen Zeitgeber laufen; fehlt die Zeile, steht er im /health als „übersprungen". Ein Neustart des Dienstes holt keinen verpassten Slot nach (sonst liefe bei jedem Deploy alles sofort doppelt). Umschalten — n8n-Original aus, GitHub-Zwilling aus — ist JC-Wort je Zeitgeber, mit der Zeilengleichheits-Zahl daneben.

Die Messung vor dem Umschalten (08_OPS/mess_zeitgeber_zwillinge.py --tage 7): jeder Lauf schreibt seit 14.09. eine Zeile agent_events.event_type = zeitgeber.lauf mit tool_output.herkunft (github-actions · hetzner-scheduler · cli) — ein Feld, kein Textmuster (#1658, Messfehler ①). Das Werkzeug paart je Zeitgeber und Slot beide Seiten und zählt GLEICH · UNGLEICH · nur GitHub · nur Hetzner. Beim Cost Collector zählt nur die Art (Metrik-Zahl, Fehler, Enthaltung), weil die Werte zeitabhängig sind. Die Gleichheit der Tabellen selbst hat #1658 gemessen; beide Zahlen zusammen sind die Vorlage. Reihenfolge beim Umschalten (#1908 §3): Periodic Engine zuerst — sie ruft die drei anderen um 01:30, umgekehrt fielen sie in 404.

⛔ R-193 — was gebaut und gemessen ist, und was nicht: gebaut und netzfrei geprüft (Selbsttests 18 · 15 · 9 Fälle, 07_TESTS/zeitgeber01, CI-Schritt ⑱g in konformitaets-guards.yml); Guards grün: Bundle, Env-Namen, Deploy-Filter, Versandweg, Konsole. Nicht gemessen: der erste Lauf auf Hetzner (braucht Deploy = JC-Wort, dann TGV_ZEITGEBER_TICK_SEC und die Schalter), die Zeilengleichheit selbst (frühestens nach zwei Kalendertagen Doppelbetrieb), und ob die n8n-Originale in dieser Zeit weiterlaufen (Schritt A, JC).

§7 · Kanäle — Mail, Drive, Domains

Mail: EINE Quelle source='email' (imap/gmail = Legacy) · Zustell-Kanon: SPF/DKIM/DMARC zusammen + Multipart + BCC + Rechnungsnummernkreis · Beleg-Eingang factures@convis.fr; Reply tgv@; Versand c3@/c4@/kivisai@ via Resend BETRIEBSHANDBUCH:78-81 · DNS-Prüfbefehle je Domain: reference_ionos_domain_inventory:70-75.

Drive-Ordner-IDs: 00_TGV-CORE 1wBII7… · 01_TGV-PROJECTS 1ZJ_kv… · 02_ARCHIV 1rtOVJ…; Service-Account ist fileOrganizer (bewusst KEIN Manager). Domains: Tabelle kb_domains (Mig 185) + IONOS-Register.

§8 · Claude-Code-Betrieb

MCP „tgv": .mcp.json → C:\Python313 + TGV -App files/tgv_mcp_server.py; env braucht APPDATA+USERPROFILE (pywin32). Memory-Pfad: C:\Users\jcbin\.claude\projects\C--Dev-TGV-repo\memory — projekt-, nicht worktree-gebunden. Auto-Mode-Klassifikator: blockt Subagenten bei Live-PII/Service-Role (nur Hauptsession mit JC-Consent, read-only) + Massen-n8n ohne allow-Regel.

⚠ NICHT verifiziert: ob die APPDATA/USERPROFILE-env im AKTUELLEN .mcp.json nachgezogen ist — offene Prüf-Frage.

§9 · Funktions-Checkliste je Komponente

KomponenteHängt anPrüfen mitStatus
Video-PlayerEbene 1: nativer <video>+Range (MP4/H.264); Ebene 2 (ffmpeg) NICHT gebaut—Konzept
i18ndeploy.yml-Matrix listet 6 Locales (de/fr/en/it/pt/ru) — es.json fehlt = Diskrepanz zur 7-Sprachen-Ansage (deckt sich mit dem PLAT#02-Wrapper-Fund) · Übersetzungs-Skill geplant, nicht live200 + JSON je /i18n/<loc>.json🔨
TTS/STTOpenAI tts + Whisper via Skills (kein Web-Speech — iOS)Skill-Callgebaut, Bau-Gate
PDF-VorschauDrive-iframe-Mechanik — in der OPS-Doku NICHT dokumentiert (lebt nur im Shell-Code design02 openDocPanel)Staging, Quelle-TabDoku-Lücke
OCRDocling (de+fr; schwach bei Handschrift/FR) + Claude-Vision-Pfad; media_type=scan → Vision/docling/healthlive

§10 · Die Tag-1-Kette — TGV von scratch

⭐ Der Kernfund dieser Inventur: Es existierte keine zusammenhängende „Tag-1"-Bootstrap-Anleitung — diese Kette musste aus 5 Dokumenten rekonstruiert werden (INSTALL_DEPLOY.md · INSTALL_DEPLOY_SERVICE.md · PLAT02_FOLLOWUP_JC_ACTIONS.md · reference_supabase_mgmt_api · reference_credentials_location). JCs Auftrag traf eine echte Lücke; ab jetzt lebt sie HIER.
  1. Hetzner-Server provisionieren (CPX22, Ubuntu 24.04).
  2. Docker-Container (Gotenberg, Docling) + nginx-Grundkonfiguration.
  3. Deploy-User tgvdeploy anlegen — Shell MUSS /bin/sh sein.
  4. Forced-command-Wrapper nach /opt/tgv-deploy/ (root-owned).
  5. Zielverzeichnisse anlegen + chown tgvdeploy.
  6. authorized_keys mit forced-command, Rechte 600.
  7. GitHub-Secrets setzen (§3-Namen).
  8. Branch-Protection auf master.
  9. Erster html-Deploy + 443-Inhalts-Verify.
  10. Service-Phase: .env → /etc/tgv-skills · systemd-Unit · ZWEITER Wrapper+Key · gescoptes sudoers.
  11. SERVICE-Secret setzen + deploy-service.yml testen.
  12. Supabase: Projekt · RLS-Baseline · tgv_migrations als Migration 1 · Mgmt-API-Token · Auth-Redirect-Allowlist.
  13. n8n-$vars füllen (braucht Schritt 12 + Docling-URL) — parallel zu 12 möglich.
  14. Lokale 08_OPS/credentials/.env.
  15. Domains/DNS/Mail: SPF/DKIM-Test VOR dem Aktivieren der Versand-Workflows.
  16. Offene Enden ehrlich markieren: Media Ebene 2 · i18n-Skill · es.json (siehe §9).

§11 · Verwaltungs-Landkarte — an alles rankommen, was wir schon haben

JC, 30.08.2026: „Es gab auch Bedarf an der ‚Backend-Verwaltung' mit User, System-Prompt, Agenten-Script, Rechten usw. … wie wir an alles, was wir schon haben, rankommen in der Administration. Wir erfinden nicht, wir verdrahten."

§1–§10 beantworten „was läuft wo". Dieser Paragraph beantwortet die Frage eine Ebene höher: wo liegt die Wahrheit, wer darf sie sehen oder ändern, und über welche Fläche komme ich ran. Gemessen am 30.08.2026 (ADMIN#03, Register #962).

⭐ Der Kernfund dieser Messung: Es fehlt fast keine Fähigkeit — es fehlen Flächen. Elf fertige Rechte-Endpunkte stehen im Dienst und werden von der Shell kein einziges Mal gerufen. JCs Satz ist wörtlich zu nehmen: verdrahten, nicht erfinden.
GegenstandWo die Wahrheit liegt (Fundstelle)Wer darfFläche heuteWas zum Rankommen fehlt
System-Promptsprompt_config — versioniert, Single-Active-Trigger 05_SCHEMA/151_lm13_prompt_config.sql · Loader 08_OPS/credentials/_prompts.pyRLS: mandant_admin des Mandanten, Betreiber✅ LIVE — secPrompts core_shell.html:9650 über prompt-katalog app.py:5878, Detail :9613. Kam mit PR #2170 (27.08.)Nur der Schreibweg: gated POST /api/skill/prompt-config mit Dry-Run-Pflicht (prompt-preview app.py:2798 existiert bereits) + _audit_event. ⛔ hartes JC-Gate
Skills / Fähigkeitenv_skill_definitions_public 05_SCHEMA/063 (gehärtet 081/160) · SSOT 02_SKILLS/_registry.yaml (985 Z., generiert 6 Listen)View ist _public → per User-JWT lesbar, RLS-ehrlich (kein service_role)❌ keineEin Read-Endpunkt auf die View per User-JWT. Fläche danach trivial: fxRender("data-table") wie secPrompts
Agenten⛔ existiert als Tabelle nicht. Geprüfte Regale: 05_SCHEMA/*.sql (agents/agent_definitions/agent_registry) und alle *.py — kein Treffer. Verhalten lebt in Prompts + Code, Spuren in agent_events—❌ keineDas echte DB-Register — JC-Entscheid vom 13.07.2026, bis heute unumgesetzt. Einziger echter Neubau dieser Landkarte, deshalb zuletzt
Rechte / Useruser_mandant_roles (Rechte-SSOT) · entities · Audit in agent_eventsScope aus der Mitgliedschaft des Aufrufers, Default-Deny (serverseitig)⚠️ Hülle — ADMIN_IA core_shell.html:3141 führt admin.members; jeder Unterpunkt rendert nur card.native_note („native Sektion folgt", :16797)⭐ Nichts im Backend. Elf fertige Endpunkte: invite-user:5326 · change-role:5397 · remove-user:5427 · list-members:5453 · assign-mandant-role:5544 · revoke-mandant-role:5589 · create-mandant:5632 · account-status:5662 · list-mandanten:5701 · mandant-detail:5737 · audit-log:5774. Die Shell ruft davon nur account-status
Audit / Verlaufagent_events · Endpunkt audit-log app.py:5774, scopet serverseitig nach user_mandant_rolesVerwalter des Mandanten⚠️ verdrahtet, aber am falschen Ort — nur im Dokument-Weg gerufen core_shell.html:16460, nicht als Verwaltungs-ScreenFast geschenkt: dieselbe Quelle als /verwaltung/audit rendern
Regeln & Tabellen08_OPS/entity_regeln.json (gelernte Zuordnung) · 08_OPS/ablage_kanon.yaml · ⛔ ZWEI SCHICHTEN, nicht eine Tabelle (Korrektur 31.08., #992): PLAN = policies.taxonomy in prompt_config → orch_apply schreibt documents.skills_needed — das ist der Agenten-Weg, und es gibt ihn · NETZ = LEGACY_MAP dispatch.py, greift nur, wenn kein Plan vorliegt · entities (bucket, intake_mail)Repo-Dateien: wer schreiben darf. prompt_config/system: is_system_admin. entities: RLS nach Mandant✅ TEILWEISE — /verwaltung/regeln (#964) zeigt Taxonomie + Entitäten und benennt die drei unerreichbaren Quellen mitsamt GrundDie zwei Repo-Dateien sind vom Browser nicht erreichbar — deploy.yml liefert 08_OPS/* nicht aus (gemessen 30.08.) ⇒ Deploy-Job + nginx-Location oder Lese-Endpunkt. LEGACY_MAP steht im Programmtext, hat keinen Endpunkt — sie zu lösen ist eine Routing-Änderung (JC/Tower), kein Lese-Screen. ⛔ Die Zwei-Tabellen-Wunde wird gezeigt, nicht gemessen: 08_OPS/check_routing_zwei_tabellen.py
Modelle je Skillbereits kartiert in Register #958 (Reise-Karte Fassung 3): Modellname + Pflegeort je Etappe; Wahl teils aus prompt_config (ohne Auslieferung umstellbar), teils fest im Programmtext (pdf-invoice-extract/skill.py:116, vision-extract/skill.py:78)Betreiber❌ keineAnzeige „welches Modell an welcher Etappe", Quelle = #958. Umschaltbarkeit nur dort, wo sie schon in prompt_config liegt

Das Muster hinter der Tabelle

Dreimal dieselbe Lage — und keine davon heißt „uns fehlt eine Fähigkeit":

  1. Daten da, Endpunkt da, Fläche fehlt → Rechte/User, Audit. Reine Verdrahtung.
  2. Daten da, Endpunkt und Fläche fehlen → Skills, Regeln, Modelle. Ein Read-Endpunkt je Gegenstand.
  3. Die Wahrheit existiert nicht → Agenten. Der einzige echte Neubau.
⭐ Die Reihenfolge folgt daraus von selbst: erst (1), dann (2), zuletzt (3). Was schon gebaut ist und nur nicht gerufen wird, ist immer billiger als das, was noch nicht existiert.

Reihenfolge nach Nutzen — was JC am schnellsten Selbstbedienung bringt

#SchrittAufwandWarum hier
1Mitglieder & Rechte /verwaltung/members — read-only aus list-members + list-mandanten + mandant-detailklein (nur Fläche)Höchster Nutzen, null Backend-Arbeit. Beendet die elffache Nicht-Nutzung; JC sieht endlich, wer was darf
2Audit /verwaltung/audit — audit-log als eigener Screensehr kleinEndpunkt läuft bereits, wird nur am falschen Ort gerufen
3Fähigkeiten /verwaltung/skills — Read-Endpunkt auf v_skill_definitions_public + TabellemittelErste echte Backend-Ergänzung, aber klein und RLS-ehrlich
4Prompts schreiben — gated prompt-config + Dry-Run + AuditmittelFläche steht read-only bereits. ⛔ hartes JC-Gate vor jedem Live-Write
5Regeln & Tabellen — Anzeige; vorher LEGACY_MAP aus dem Programmtext lösenmittelDer Prüfer existiert; die Zwei-Tabellen-Wunde zuerst
6Agenten-Register — echte DB-TabellegroßEinziger echter Neubau, deshalb zuletzt
⛔⛔ ZWEITE KORREKTUR (31.08., #992) — und diese hat JC eine Frage gekostet, die zur Hälfte schon beantwortet war. Dieses Blatt beschrieb LEGACY_MAP als die Stream-Zuordnung, die „im Programmtext steht und ohne Auslieferung nicht änderbar“ ist. Jeder Halbsatz stimmt — das Ganze führt in die Irre. Gemessen: plan_skills nimmt documents.skills_needed (vom Orchestrator aus der Taxonomie) und fällt nur dann auf LEGACY_MAP zurück, wenn dort nichts steht. ⭐ JCs Frage „sollte das nicht ein Agent übernehmen?“ war also mit Ja zu beantworten — der Agenten-Weg existiert, mein Text hat ihn nur unsichtbar gemacht. ⛔ Beschreiben ist nicht lösen — aber falsch beschreiben ist schlimmer als schweigen: es erzeugt Arbeit an einer Frage, die keine war.
⛔ Und was WIRKLICH offen ist, steht jetzt daneben: Plan und Netz laufen auseinander (2 Streams, 08_OPS/check_routing_zwei_tabellen.py, erstmals gefahren am 31.08. — neun Tage nachdem der Befund entstand). Ob das im Betrieb etwas kostet, ist NICHT gemessen: der betroffene BANKING-Eintrag trägt JCs Entscheid vom 21.08., und seither kam kein BANKING-Beleg. ⭐ Seit dem Entscheid ungetestet — nicht „läuft“ und nicht „läuft nicht“.
⛔⛔ KORREKTUR AN DIESEM BLATT (30.08., ADMIN#03 #964) — zwei Angaben in der Zeile „Regeln & Tabellen“ waren falsch, und zwar in derselben Klasse, die dieses Blatt anderen vorwirft: ich hatte sie aus dem Gründungsauftrag übernommen, statt sie zu messen.
① entities.ablage_struktur stand hier als Gegenwart — die Spalte existiert nicht. Die Live-Tabelle führt id, mandant_id, slug, name, kind, bucket, intake_mail, created_at, test_run, ist_geteilt; die Migration liegt als 05_SCHEMA/PROPOSED_entities_ablage_struktur.sql — PROPOSED, nicht angewandt.
② prompt_config.worker_set las sich wie eine Spalte; es ist keine. Der Wert liegt unter policies.taxonomy.doc_types[…].worker_set bzw. structural_routes in den Zeilen domain_taxonomy_*.
⭐ Beides an der laufenden Datenbank nachgemessen (08_OPS/db_query.py gegen information_schema/pg_policies), nicht wieder nur gelesen. Eine Landkarte, die ihre eigenen Felder nicht misst, ist eine Behauptungssammlung.
⛔ Was dieses Blatt NICHT behauptet (R-193). Die Suche nach einem früheren secSkillsAdmin-Bau lief bei 2141 Ref-Tips in den Timeout. Geprüft und nicht gefunden in: origin/master · Branch claude/strange-hypatia-ed9846 · lokale Branch-Liste · git ls-remote origin · gh pr list --state all (Suchen „skills admin", „prompt admin verwaltung"). Nicht geprüft: die vollständige Ref-Menge. Es ist also „dort nicht gefunden", nicht „existiert nicht".
Pflege-Ritual: Diese Seite ist der Sammelort für Umgebungs-Wahrheit — jede Stack-Änderung (neuer Dienst, neues Secret-ORT, neue Falle) wird HIER nachgezogen, im selben PR wie die Änderung. Quelle der Erstbefüllung: Umgebungs-Inventur @ 4fe221d ↗ (Momentaufnahmen — vor Übernahme in Handgriffe gegen Live prüfen). Verwandt statt gedoppelt: 🏗 Arbeitsweise (Betriebsmodell + Werkzeuge) · 📄 Datenfluss · 🔒 Sicherheit (EU-Stack, RLS, Einstufung) · 🗼 Tower.