Plexer is the event gateway and delivery relay for bounded operational events in the new operator ecosystem. Target v2 doctrine: Chronik is the critical append-only sink for operational ledger events; Bureau owns tasks and claims; Grabowski owns local execution and receipts; Leitstand, Heimgeist and hausKI are observers or consumers. Legacy /events may still route unknown events to Heimgeist during migration; that is compatibility behavior, not the target architecture. Plexer is also not the only communication path.
Plexer ist das Event Gateway und Delivery Relay für begrenzte operative Ereignisse im Heimgewebe-Operator-Ökosystem.
- Nimmt Events über
POST /eventsim Heimgewebe-Format entgegen - Prüft Minimalstruktur (
type,source,payload;type/sourcemax. 256 Zeichen) - Loggt eingehende Events
- Leitet sie an Heimgeist und weitere konfigurierte Konsumenten (Chronik, Leitstand, hausKI) weiter
Plexer wird in Richtung Event Gateway und Delivery Relay neu zugeschnitten. Der bestehende Router bleibt während der Migration kompatibel, aber die Zielrolle ändert sich:
- Chronik ist die kritische append-only Senke für operative Ledger-Ereignisse.
- Plexer validiert, klassifiziert, queued und liefert aus.
- Heimgeist, Leitstand und hausKI sind Beobachter- oder Analyseflächen, nicht die primäre Wahrheit.
- Grabowski und Bureau dürfen nicht von Plexer-Verfügbarkeit abhängen.
- Der erste v2-Scope bleibt bewusst klein:
agent.run.started,agent.run.completed,agent.run.blocked.
Details: docs/architecture/plexer-v2-gateway.md, docs/migration/plexer-v2-execution-plan.md und docs/proofs/agent-run-proof-of-use.md.
Der wiederholbare Runtime-Nutzennachweis liegt in docs/proofs/runtime-usefulness-proof.md und kann mit pnpm run proof:runtime-usefulness ausgeführt werden. Er prüft bewusst nur den engen Pfad agent.run.* -> Plexer -> Chronik agent.ledger -> Read-back; er ist kein Producer-Gate und keine Erweiterungsfreigabe für neue Eventfamilien.
Plexer kümmert sich ausschließlich um Eventtransport.
Plexer tut:
- Events entgegennehmen (
POST /events) - Minimalstruktur prüfen
- Events protokollieren
- Events an Konsumenten weiterreichen (Fanout-Pattern)
- Legacy
/events: fehlgeschlagene Weiterleitungen an Heimgeist zwischenpuffern und wiederholen. V2/v1/events: Chronik ist die kritische Senke.
Plexer tut nicht:
- PR-Kommentare entgegennehmen
- PR-Kommandos parsen
- mit der GitHub-API sprechen
- als Bot oder Reviewer agieren
- Chat- oder Dialogflüsse steuern
Der aktuelle Zweck, Lifecycle-Status und die Beziehungen dieses Repositories zu anderen Heimgewebe-Systemen werden im Systemkatalog geführt. Die gerenderte Systemübersicht ist die lesbare Gesamtsicht; die maschinenlesbare Inventur ist die Quelle für Automatisierung.
Repositoryeigene Betriebs-, Daten- und Implementierungswahrheit bleibt in diesem Repository. Gemeinsame Contracts bleiben bei ihrer jeweiligen Primärquelle.
- Node.js >= 20
- pnpm (via Corepack)
- CI uses
pnpm/action-setupto ensure consistent pnpm versions.
npm is not supported.
PORT(default: 3000)HOST(default:127.0.0.1)PLEXER_TOKEN: Pflicht-Credential fürPOST /eventsundPOST /v1/events(Authorization: Bearer …). Ohne Token bleibt Ingress fail-closed (503).PLEXER_ALLOW_NON_LOOPBACK(default:false): muss exakttruesein, wennHOSTnicht Loopback ist; zusätzlich ist ein aktivesPLEXER_TOKENerforderlich.NODE_ENV(default: development)PLEXER_DATA_DIR: Pfad zum Verzeichnis, in dem die Queue für fehlgeschlagene Events persistiert wird (default:./data).- Hinweis für WGX: Die Flow-Definition in
.wgx/flows.jsonerwartet die Queue unterdata/failed_forwards.jsonl. WennPLEXER_DATA_DIRgeändert wird, muss der Flow-Pfad angepasst oder ein Symlink verwendet werden.
- Hinweis für WGX: Die Flow-Definition in
| Variable | Default | Beschreibung |
|---|---|---|
RETRY_CONCURRENCY |
5 |
Anzahl gleichzeitiger Forward-Versuche beim Retry. Erhöht den Durchsatz, belastet aber Zielsysteme stärker. |
RETRY_BATCH_SIZE |
50 |
Maximale Anzahl gleichzeitig aktiver Retry-Tasks im Sliding Window (Backpressure Control). Empfehlung: RETRY_BATCH_SIZE >= RETRY_CONCURRENCY. |
PLEXER_INGRESS_RATE_WINDOW_MS |
60000 |
Festes Rate-Limit-Fenster; 429 liefert die deterministische Restzeit via Retry-After. |
PLEXER_INGRESS_PER_CLIENT_RATE_LIMIT |
120 |
Requests pro direkter Client-Adresse und Fenster. |
PLEXER_INGRESS_GLOBAL_RATE_LIMIT |
1200 |
Globale Requests pro Fenster. |
PLEXER_INGRESS_PER_CLIENT_MAX_IN_FLIGHT |
8 |
Gleichzeitige Ingress-Arbeiten pro Client. |
PLEXER_INGRESS_GLOBAL_MAX_IN_FLIGHT |
64 |
Globale gleichzeitige Ingress-Arbeiten. |
PLEXER_INGRESS_MAX_CLIENTS |
1024 |
Harte Obergrenze der im Speicher gehaltenen Client-Zähler. |
FAILED_FORWARDS_MAX_BYTES |
16777216 |
Harte Byte-Gesamtgrenze über aktive Queue und Retry-Archive. |
FAILED_FORWARDS_MAX_ENTRIES |
10000 |
Harte Eintrags-Gesamtgrenze über aktive Queue und Retry-Archive. |
FAILED_FORWARDS_MAX_AGE_MS |
604800000 |
Maximales Alter seit lastAttempt; ältere Einträge werden deterministisch verworfen. |
Alle URL-Variablen müssen vollqualifiziert sein (inkl. Schema https://…).
| Service | URL Variable | Token Variable | Auth Methode |
|---|---|---|---|
| Heimgeist | HEIMGEIST_URL |
HEIMGEIST_TOKEN |
X-Auth: <token> |
| Chronik | CHRONIK_URL |
CHRONIK_TOKEN |
X-Auth: <token> |
| Leitstand | LEITSTAND_URL |
LEITSTAND_TOKEN |
Authorization: Bearer <token> |
| hausKI | HAUSKI_URL |
HAUSKI_TOKEN |
Authorization: Bearer <token> |
Plexer wendet automatisch den korrekten Auth-Header je nach Zielsystem an.
Plexer nutzt eine persistente, dateibasierte Queue (failed_forwards.jsonl), erstklassige Retry-Archive (processing.*.jsonl) und atomar geclaimte retrying.*.jsonl-Dateien, um Events auch bei temporären Ausfällen der Konsumenten zuzustellen. Byte-, Eintrags- und Altersgrenzen gelten atomar über aktive Datei, Archive und Claims. Bestehende Retry-Daten haben Vorrang; neue Einträge werden an der exakten Quota-Grenze explizit abgelehnt. Beim Start werden korrupte/abgelaufene Zeilen entfernt, Crash-Claims wieder retrybar gemacht und ein vorbestehender Überhang in stabiler Datei-/Zeilenreihenfolge gekürzt. Retry ersetzt seinen Claim nur in-place und nur ohne Wachstum; bei Fehler bleibt das Original erhalten. Details und Producer-Migration: docs/ingress-security-and-retention.md.
Aktuelle geteilte Policy: Legacy /events behält Heimgeist als kritischen Kompatibilitätskonsumenten. V2 /v1/events nutzt Chronik als kritische Senke für operative Ledger-Ereignisse.
Die Unterscheidung erfolgt primär anhand des Konsumenten und sekundär per Event-Override:
-
Heimgeist (Legacy Critical Consumer für
/events):- Zielsystem für persistente Datenhaltung.
- Events, die an Heimgeist nicht zugestellt werden können, werden gequeued und via Exponential Backoff wiederholt.
- Ausnahme: Events in
BEST_EFFORT_EVENTS(z.B.integrity.summary.published.v1) werden auch für Heimgeist nicht gequeued.
-
Andere Legacy-Konsumenten (Leitstand, hausKI, Chronik):
- Fire-and-Forget / Best-Effort.
- Fehlschläge werden geloggt (als Warning), aber niemals gequeued.
- Dies verhindert, dass ein einzelner langsamer Konsument den Plexer blockiert oder die Queue füllt.
-
Best-Effort Events Override:
- Events wie
integrity.summary.published.v1(Pull-based hints) oderplexer.delivery.report.v1(Ephemeral Status) sind inBEST_EFFORT_EVENTSdefiniert. - Diese werden niemals gequeued, auch nicht für Heimgeist.
- Events wie
Die verwendeten Schemas zur Validierung von Queue-Einträgen und Status-Reports liegen in src/vendor/schemas/.
Wichtig: Diese Dateien sind Kopien (Vendoring) der kanonischen Definitionen aus dem Metarepo (heimgewebe/metarepo/contracts/plexer/). Änderungen dürfen nicht hier, sondern nur im Metarepo erfolgen und müssen dann synchronisiert werden.
Plexer ist Functionality-first ausgelegt: Zustellung und Robustheit stehen im Vordergrund. Um Datenabfluss zu vermeiden, gelten dabei folgende Schutzmaßnahmen:
- Beide schreibenden Ingress-Routen verlangen
Authorization: Bearer $PLEXER_TOKEN;X-Authwird dort nicht akzeptiert. Auth-Prüfung verwendet fixed-length constant-time comparison und läuft vor JSON-Parsing. - Standard-Bind ist
127.0.0.1. Non-Loopback benötigt explizitesPLEXER_ALLOW_NON_LOOPBACK=trueund aktives Auth. - Rate-Limits und In-flight-Backpressure sind pro direkter Client-Adresse und global begrenzt;
429ist mitRetry-Afterdeterministisch retrybar. - Eingehende Event-Payloads werden nicht geloggt; geloggt werden nur Metadaten sowie
payload_sizeundpayload_size_kind(wenn berechenbar/sonst unavailable). - Tokens und Authorization-Header werden nie geloggt.
- Fehlgeschlagene kritische Events werden lokal gepuffert (Queue-Datei im
dataDir). Der Betrieb muss sicherstellen, dass dieses Verzeichnis geschützt ist (z. B. Dateirechte oder verschlüsseltes Volume).
GET /status: Liefert Metriken zur Delivery-Queue.- Payload folgt dem Contract:
plexer.delivery.report.v1. - Felder:
pending(in-flight),failed(in queue),retryable_now(fällig),next_due_at(nächster Retry).
- Payload folgt dem Contract:
GET /health: Liveness. Solange der Prozess läuft,200 {"status":"ok"}. Reflektiert nicht den Zustand nachgelagerter Konsumenten.GET /readiness: Operator-Probe für die kritische Chronik-Senke (agent.ledger) — bewusstescurl -f/Uptime-Signal, kein Infrastruktur-readinessProbe(dafür/health; für Dashboards/diagnostics/critical-sink). Zeigt die kritische Teilmenge der Queue isoliert von Best-Effort-/Legacy-Fehlern.status:ready(Senke konfiguriert, keine gequeuten agent.ledger-Events),degraded(konfiguriert, aber agent.ledger-Events warten) oderunconfigured(keinCHRONIK_URL).- HTTP:
200beiready, sonst503— damit eincurl -f/Uptime-Probe eine Beeinträchtigung des kritischen Pfads sichtbar macht. - Response-Felder (alle):
status,critical_sink,status_basis,active_probe,configured,queued,retryable_now,next_due_at,due_now,last_error,last_delivered_at. status_basis: "queue_state"/active_probe: false: Der Status wird aus Plexers lokalem Queue-Zustand abgeleitet, nicht aus einem aktiven Erreichbarkeits-Check gegen Chronik.readyheißt „kein agent.ledger-Rückstau gepuffert", nicht „Chronik ist erreichbar".retryable_nowist die Anzahl fälliger kritischer Einträge zum Zeitpunkt des letzten Queue-Scans (Snapshot, kann nachlaufen).due_nowwird dagegen live ausnext_due_atberechnet und zeigt auch zwischen Retry-Läufen an, ob der nächste Retry bereits fällig ist.last_errorist der Fehler eines aktuell offenen kritischen Queue-Eintrags — bevorzugt der des zuletzt versuchten (lastAttempt) offenen Eintrags. Er wird aus der Queue rekonstruiert (auch nach Neustart) und bei leerer kritischer Queue bereinigt (null). Type-safe: nicht-String-Fehler korrupter Zeilen werden nie zulast_error.last_delivered_atist prozesslokal und wird nach einem Neustart nicht aus persistenter Historie rekonstruiert (Prozessdiagnose).configuredprüft bewusst nurCHRONIK_URL(Senke „verdrahtet"). Ein fehlendesCHRONIK_TOKENist ein Auth-Detail und äußert sich alsdegraded(401 → gequeued), nicht alsunconfigured.- Abgrenzung (Doktrin):
/readinessist Plexers eigenes Diagnostik-Signal, nicht derplexer.delivery.report.v1-Contract, kein Producer-Gate und kein Kubernetes-/Load-Balancer-readinessProbe. Für Infrastruktur-Liveness/Traffic-Gating ist/healthzu verwenden;/readinessist ausschließlich Operator-/Leitstand-Diagnostik (bewusstescurl -f/Uptime-Probe-Signal). Eindegraded/unconfiguredZustand heißt nicht, dass Producer aufhören sollen zu senden oder Plexer aus der Rotation genommen werden soll — Plexer puffert die operativen Events weiter für den Retry (Relay degradiert, ohne die Task-Wahrheit zu ändern).
GET /diagnostics/critical-sink: Kanonischer Dashboard-Endpunkt mit demselben Payload wie/readiness, aber immer HTTP 200 (Status nur im Body). Für Standard-Monitoring, das nicht durch den503von/readinessfälschlich Traffic-Gating auslösen darf. Wahl der Endpunkte:/health= Infra-Liveness,/diagnostics/critical-sink= Dashboard/Monitoring (200),/readiness= bewusstes Probe-Signal (200/503).