TicketFlow gibt deinen Junior-Technikern den Blick eines erfahrenen Senior. Sentinel stellt sicher, dass das Bild, das TicketFlow zeigt, nicht auf veralteten oder verfälschten Daten basiert. Zusammen schließen sie die Lücke zwischen “wir haben ein Ticket” und “wir kennen den Sachverhalt wirklich”.
Techniker öffnen ein Ticket. Sie sehen Symptom, nicht Ursache. Die relevante Information liegt verteilt in RMM, PSA, CMDB, Monitoring — und keiner hat Zeit, alles zusammenzusuchen.
Ein Junior-Techniker sieht im RMM was der Senior auch sieht — aber er weiß nicht, was er priorisieren soll. Das Wissen sitzt bei wenigen Personen, nicht im System.
Keine KI-gestützte Diagnose benennt, ob die Daten im CMDB noch stimmen. Eine Netzwerkkarte taucht als „online" auf, obwohl seit zwei Stunden VLAN-Drift vorliegt.
KI-Werkzeuge, die eine Antwort “hinknallen”, erzeugen Anchoring: Der Techniker prüft die erste Hypothese — nicht die trennschärfste. Fehlerdiagnosen werden schneller, nicht besser.
Ohne explizites Mandanten-Scoping korreliert das System Ereignisse zweier Kunden miteinander. Kausal falsch — und ein Datenschutzrisiko, das regulatorisch relevant wird.
TicketFlow ist der Diagnose-Assistent. Sentinel ist die Daten-Integritätsschicht. Keines der beiden Systeme ist ohne das andere vollständig.
Sentinel überwacht kontinuierlich — TicketFlow arbeitet reaktiv auf Ticket-Eingang. Sentinel-Observations fließen als stark typisierte Provenienz-Objekte in TicketFlow ein.
| Dimension | TicketFlow | Sentinel |
|---|---|---|
| Primärfunktion | Reaktive Diagnose-Unterstützung auf Ticket-Eingang — zeigt Junior-Technikern das vollständige Bild, das ein Senior in der verfügbaren Zeit zusammensuchen würde. | Kontinuierliche Überwachung der Quelldaten-Integrität — erkennt, wenn CMDB, RMM oder IPAM in den Minuten/Stunden eines offenen Tickets von der Realität abweichen. |
| Entscheidungsmodell | Kein Urteil, kein Ranking. ACH-Matrix zeigt Trennschärfe — der Mensch entscheidet. Anti-Anchoring (Schutz vor kognitiver Fixierung auf die erste Hypothese) ist architekturell, nicht kosmetisch. | Binäre Verdikt-Klassen: CLEAN · DRIFT_MINOR · DRIFT_CRITICAL · CONTAMINATION · INTEGRITY_VIOLATION · INCONCLUSIVE. Maschine bewertet Datenqualität, kein Geschäftsprozess. |
| Zeitmodell | Einmalig beim Ticket-Eingang, optional volatile Daten-Refresh beim Öffnen. Snapshot-getrieben. | Kontinuierliches Background-Snapshotting (alle 5 min, asset-unabhängig von Jobs). Beim Ticket-Eingang: Rückblick in die Asset-Timeline bis ticket_opened_at — Sentinel sieht den Drift, der den Vorfall verursacht haben könnte, bevor TicketFlow die Analyse angefordert hat. Zusätzlich: Drift-Watch (30s-Polling) während laufender Analyse. |
| Mandanten-Scoping | end_customer_id als Korrektheitsgrenze: Korrelation ausschließlich innerhalb eines Kunden (kausal, nicht nur als Datenschutz-Maßnahme). | end_customer_id auf jedem emittierten CloudEvent — Drift-Events, Cluster-Events und Verdikten tragen immer den Kunden-Scope. |
| Provenienz-Modell | Jede Observation trägt source_system, source_strength (strong / medium / weak), observed_at, fetched_at. Herkunft (Provenienz) beim ACH-Scoren maskiert — verhindert Anchoring. | Jede Quelldaten-Differenz trägt Quelle, Schwere (CRITICAL/MINOR), Zeitstempel. source_strength: CMDB/RMM/IPAM/NinjaOne = strong, Firewall = medium. |
| Was das System ohne das andere nicht kann | Kann nicht erkennen, ob die Daten aus dem CMDB, die eine Hypothese “stützen”, noch aktuell sind. Würde mit veralteten Daten volle Konfidenz signalisieren. | Kann nicht erkennen, ob ein Drift kausal zu einem offenen Ticket gehört. Gibt keine Hypothesen, kein ACH, keinen Kontext aus Alttickets oder Dokumentation. |
Sentinel meldet: „CMDB-Daten für Kunde X haben seit 47 Minuten VLAN-Drift." TicketFlow vergewichtet die entsprechenden Observations als MEDIUM statt STRONG — die ACH-Matrix spiegelt die echte Evidenzlage.
Driften gleichzeitig 3 Assets desselben Kunden auf demselben VLAN, erkennt Sentinel ein Netzwerksegment-Cluster. TicketFlow sieht dieses Signal als eigenständige Hypothesen-Quelle — „Infrastruktur-Incident, nicht Endgerät".
Sentinel erkennt, wenn ein Feldwert im Analyse-Lineage (Herkunftspfad aller Diagnosedaten von der Quelle bis zur Hypothese) zu einem anderen Asset gehört (cross_asset_contamination). TicketFlow-Hypothesen, die auf diesem Wert basieren, werden als kausal verdächtig markiert.
Das System erzwingt, dass keine Diagnose ohne Provenienz-Angabe möglich ist. Nicht als UI-Hinweis, sondern als Pflichtfeld im Datenmodell. Schlechte Quelldaten führen zu INCONCLUSIVE — nicht zu falscher Konfidenz.
Jede Diagnose-Entscheidung ist auf Feldebene rückverfolgbar: Welche Quelldaten, von welchem System, zu welchem Zeitpunkt, mit welcher Integrität — für Compliance, Post-Mortems und Kundennachweise.
TicketFlow analysiert einen Vorfall, der in der Vergangenheit lag. Der relevante Drift — „seit zwei Stunden VLAN-Drift" — passierte vor dem Ticket, nicht während der Analyse. Sentinels kontinuierliche Asset-Timeline ermöglicht den Rückblick: Beim Eingang von tf.analysis.started mit ticket_opened_at liefert Sentinel sofort den pre_ticket_drift — das vollständige Bild dessen, was sich seit Ticketöffnung in der Infrastruktur verändert hat. Das ist nur möglich, weil Sentinel außerhalb von TicketFlow kontinuierlich beobachtet, ohne auf einen Job-Trigger zu warten.
Sentinel speichert vendor/product/role/version per Job als Fingerprint. TicketFlow nutzt diese Signatur für Pattern-Matching: Ähnliche Incidents auf identischen Stacks werden trennscharf gekennzeichnet — ohne false Confidence durch Softwareversion-Abweichung.
| Sentinel-Verdikt | Bedeutung | TicketFlow-Konsequenz |
|---|---|---|
| CLEAN | Alle Quellen stabil, keine Abweichung seit Ticket-Eingang. | Observations mit voller source_strength → hohe ACH-Konfidenz möglich. |
| DRIFT_MINOR | Nicht-kritische Felder geändert (z. B. owner, location). | Betroffene Observations mit MEDIUM source_strength. Ursachenhypothesen bleiben möglich. |
| DRIFT_CRITICAL | Kritische Felder geändert (ip, vlan, firewall_zone, subnet). | Netzwerk-Observations als WEAK eingestuft. Techniker wird auf Stale-Data-Risiko hingewiesen. |
| CONTAMINATION | Feldwert gehört kausal zu einem anderen Asset. | Betroffene Lineage-Einträge werden als Hypothesen-Generator degradiert, nicht als Evidenz. |
| INTEGRITY_VIOLATION | Wert existiert in keinem bekannten Asset-Snapshot (Phantom-Wert). | Betroffene Lineage wird aus ACH-Evidenzmasse entfernt. Offene Frage im „Was würde es klären"-Block. |
| INCONCLUSIVE | Quelldaten-Qualität nicht bewertbar (Circuit Breaker, keine Daten). | TicketFlow markiert Observations aus dieser Quelle explizit als unverified. Techniker muss manuell prüfen. |
Beide Systeme sind von Grund auf für MSP-Betrieb ausgelegt: kein Cloud-Vendor-Lock-in, keine Daten in fremde Hände. Die folgenden vier Deployment-Topologien decken alle Compliance- und Risikoklassen ab.
Alle Daten, alle Komponenten im eigenen Haus. Keine Cloud-Abhängigkeit.
Sentinel und TicketFlow laufen auf MSP-eigener Hardware oder im selbst verwalteten Hypervisor (Proxmox, ESXi). Quellsysteme (NinjaOne, ConnectWise, IT-Glue) sind SaaS — ihre Daten verlassen das MSP-Netz nur über interne API-Calls und werden lokal in SQLite (Sentinel) und PostgreSQL/TimescaleDB (TicketFlow) gespeichert. Kein Kundendatenstrom geht an Dritte.
LLM-Inference für TicketFlow: entweder lokal (Ollama + Llama 3.1 / Qwen 2.5 auf dedizierter Hardware) oder über API mit explizitem Daten-Verarbeitungsvertrag (DPA) mit dem LLM-Anbieter. Anonymisierungs-Gateway (§12 TicketFlow-Konzept) filtert PII und Kundenkennzeichen vor Weiterleitung.
Datenfluss: NinjaOne/CMDB/PSA → Sentinel API (Port 3009, intern) → CloudEvents → TicketFlow API (intern) → PostgreSQL (intern). LLM-Calls: internes Ollama oder externe API über Anonymisierungs-Gateway.
MSP-managed Kubernetes, ein Namespace pro End-Kunde. Skalierbar.
Der MSP betreibt einen Kubernetes-Cluster (Hetzner Dedicated, OVH, eigene Bare-Metal-Nodes — kein US-Hyperscaler ohne EU-DPA). Für jeden End-Kunden wird ein eigener Namespace mit dedizierter Sentinel-Instanz und eigenem Datenbankschema angelegt.
TicketFlow-Core läuft mandantenübergreifend (Pattern-Store ist bewusst de-identifiziert). Connector-Instanzen sind pro End-Kunde konfiguriert, Secrets in HashiCorp Vault (MSP-betrieben). Network Policies verhindern Namespace-Cross-Traffic auf Netzwerkebene.
Datenisolation: DB_PATH=/var/sentinel/<customer_id>/sentinel.db pro Instanz. end_customer_id auf jedem CloudEvent als Routing-Key. Kubernetes NetworkPolicy blockiert Namespace-Cross-Traffic.
Für Kunden mit Compliance-Vorgaben (DSGVO, ISO 27001, BSI C5).
Kunden mit strikten Datenresidenz-Anforderungen (z. B. Behörden, Kliniken, Finanzdienstleister) erhalten eine dedizierte Sentinel-Instanz in ihrer eigenen Region oder sogar On-Premise beim Kunden. TicketFlow-Analyselogik läuft im MSP-Rechenzentrum — nur aggregierte, de-identifizierte Observations werden übertragen.
Sentinel läuft als systemd-Service beim Kunden (oder in dessen privater Cloud). Eingehende Quell-Daten verlassen die Kunden-Infrastruktur nicht. Sentinel sendet ausschließlich normalisierte Observations (keine Rohdaten) via verschlüsseltem CloudEvent-Webhook an TicketFlow.
Datenfluss: Quellsysteme → Sentinel@Kunde (nur Observations emittiert) → VPN/mTLS → TicketFlow@MSP. Rohdaten: niemals außerhalb Kunden-Infrastruktur.
Für kritische Infrastruktur ohne Außenverbindung.
Vollständig offline: Sentinel läuft lokal, TicketFlow-Core lokal. Kein Webhook nach außen. Diagnose-Reports werden manuell oder über einen Daten-Dioden-Transfer in die MSP-Plattform überführt. LLM-Inference ausschließlich lokal (Ollama).
Sentinel-Verdikt und Observations werden in lokale SQLite-DB geschrieben und über einen signierten Export-Job (täglich/auf Abruf) an das MSP-Ticket-System übermittelt. Keine Echtzeitverbindung.
end_customer_id ist ein internes Scoping-Attribut, kein personenbezogenes Datum im DSGVO-Sinne. Authentifizierung über Bearer-Token (SENTINEL_AUTH_TOKEN). Rate Limiting eingebaut. Keine Telemetrie an Dritte. TLS für alle Webhooks obligatorisch.
Voraussetzungen: Linux-Server (Debian 12 / Ubuntu 22.04), Node.js 20+, Python 3.11+. Sentinel läuft als systemd-Service, TicketFlow als separater Service oder Container.
Sentinel läuft als eigenständiger Node.js-Dienst. Kein Build-Schritt notwendig — reines ESM.
git clone https://forgejo.kurvenschule.cloud/andre/ticketflow-sentinel
cd ticketflow-sentinel
npm install --production
Kopiere die Vorlage und fülle alle Pflichtfelder. MOCK_SOURCES=true darf in Produktion niemals gesetzt sein.
cp .env.example .env
# Pflichtfelder:
SENTINEL_AUTH_TOKEN=<sicheres-token-min-32-zeichen>
DB_PATH=/var/sentinel/sentinel.db
TICKETFLOW_VERDICT_URL=https://<ticketflow-host>/api/sentinel/verdict
TICKETFLOW_EVENTS_URL=https://<ticketflow-host>/api/sentinel/events
# Quellsysteme:
CMDB_URL=https://cmdb.intern.msp.de
CMDB_TOKEN=<readonly-token>
RMM_URL=https://rmm.intern.msp.de
RMM_TOKEN=<readonly-token>
IPAM_URL=https://ipam.intern.msp.de
IPAM_TOKEN=<readonly-token>
# Optional NinjaOne OAuth2:
NINJARMM_URL=https://eu.ninjarmm.com
NINJARMM_CLIENT_ID=<client-id>
NINJARMM_CLIENT_SECRET=<client-secret>
Erstelle /etc/systemd/system/sentinel.service — Service läuft unter eigenem User ohne root-Rechte.
[Unit]
Description=ticketflow-sentinel
After=network.target
[Service]
Type=simple
User=sentinel
WorkingDirectory=/opt/ticketflow-sentinel
EnvironmentFile=/opt/ticketflow-sentinel/.env
ExecStart=/usr/bin/node src/server.js
Restart=on-failure
RestartSec=5
[Install]
WantedBy=multi-user.target
useradd -r -s /bin/false sentinel
chown -R sentinel:sentinel /opt/ticketflow-sentinel
systemctl daemon-reload
systemctl enable --now sentinel
systemctl status sentinel
Sentinel exponiert GET /health (offen, kein Auth) und GET /metrics für Prometheus.
curl https://sentinel.intern.msp.de/health
# Erwartet:
# {"status":"ok","active_jobs":0,"circuit_breakers":{},"uptime_s":12}
Python 3.11+, FastAPI, PostgreSQL/TimescaleDB als Event-Store. Detaillierte Anleitung im TicketFlow-Repo.
cd /opt/ticketflow
python3 -m venv .venv && source .venv/bin/activate
pip install -r requirements.txt
# Sentinel-Integration in TicketFlow-Config:
SENTINEL_WEBHOOK_SECRET=<selber-token-wie-SENTINEL_AUTH_TOKEN>
SENTINEL_EVENTS_URL=https://sentinel.intern.msp.de/events
Schicke ein Test-Analyse-Event an Sentinel und prüfe, ob das Verdikt bei TicketFlow ankommt.
curl -X POST https://sentinel.intern.msp.de/events \
-H "Authorization: Bearer <SENTINEL_AUTH_TOKEN>" \
-H "Content-Type: application/cloudevents+json" \
-d '{
"specversion": "1.0",
"type": "tf.analysis.started",
"id": "test-job-001",
"data": {
"asset_id": "PC-TESTGERÄT",
"end_customer_id": "kunde-001",
"snapshot": {"ip": "10.10.1.50", "vlan": "VLAN-10"}
}
}'
# Erwartet: {"accepted":true,"jobId":"test-job-001"}
Sentinel exponiert GET /metrics im Prometheus-Format. Empfohlene Alert-Rules:
# prometheus.yml scrape config:
- job_name: sentinel
static_configs:
- targets: ['sentinel.intern.msp.de:3009']
# Empfohlene Alert-Rules:
# - sentinel_circuit_breaker_state{source="CMDB"} == 1 → CMDB Circuit OPEN
# - sentinel_active_jobs_tracked > 100 → Zu viele offene Jobs
# - rate(sentinel_drift_events_total[5m]) > 10 → Drift-Sturmrate erhöht
Sentinel bereinigt Snapshots alter abgeschlossener Jobs automatisch. Empfehlung: täglicher Cron-Job.
# /etc/cron.d/sentinel-prune
0 3 * * * sentinel curl -s -X POST \
https://sentinel.intern.msp.de/admin/prune \
-H "Authorization: Bearer <SENTINEL_AUTH_TOKEN>" \
-d '{"days_old":30}'
Sentinel kommuniziert ausschließlich lesend mit Quellsystemen. TicketFlow liest und kann optional über den PSA-Write-Back-Connector Kommentare schreiben — keine automatische Remediation.
| System | Typ | Sentinel-Integration | TicketFlow-Integration | Richtung |
|---|---|---|---|---|
| NinjaOne | RMM | OAuth2 Client Credentials (NINJARMM_URL / CLIENT_ID / CLIENT_SECRET). Normalisierung auf Asset-Schema inkl. vendor/product/role/os. | Pull-Inventory (Assets, Dependencies), Pull-Events (Alerts, IPS-Updates), Query (Live-Ist-Daten). | Read |
| ConnectWise Manage | PSA | — | Webhook-Trigger bei Ticket-Eingang. Pull-Ticket inkl. Verlauf. Write-Back als Kommentar (Diagnose-Zusammenfassung). | Webhook Read Write |
| Autotask / HaloPSA | PSA | — | Analog ConnectWise. PSA-Connector ist austauschbares Plug-in (Interface-basiert, kein Kern-Code-Änderung). | Webhook Read Write |
| ServiceNow | PSA / CMDB | CMDB: REST-API, Asset-Daten (ip, vlan, location). Token-Auth. | PSA: vollständige bi-direktionale Integration (Port aus Keep ServiceNow-Provider). CMDB: Asset-Graph-Quelle. | Read Write |
| IT-Glue | Doku | — | Search + Fetch pro End-Kunde (strikt isoliert). Netzpläne, Kunden-Runbooks als Kontext-Quelle. | Read |
| Notion (MSP-intern) | Doku | — | MSP-eigene Prozess-Runbooks. Mandantenübergreifend lesbar (geistiges Eigentum des MSP). | Read |
| Sophos Central / M365 | Monitoring | — | Events + Live-Query (Threat-Events, Token-Refresh, Conditional-Access-Logs). source_strength: strong. | Read |
| Generisches CMDB | CMDB | REST-Endpoint /api/asset/{id} mit Bearer-Token (CMDB_URL + CMDB_TOKEN). Retry-Backoff, Circuit Breaker. |
Asset-Graph-Quelle (Inventar, Abhängigkeitskanten). | Read |
| Generisches IPAM | IPAM | REST-Endpoint inkl. Subnet-Nachbarn (/api/ipam/neighbors/). Für Kontaminations-Erkennung. |
Topologie-Daten für Netz-Hypothesen. | Read |
Administration → Apps → Client App anlegen. Grant Type: Client Credentials. Scope: monitoring. Notiere Client ID und Secret.
NINJARMM_URL=https://eu.ninjarmm.com
NINJARMM_CLIENT_ID=<deine-client-id>
NINJARMM_CLIENT_SECRET=<dein-client-secret>
Sentinel holt automatisch einen Token (OAuth2 Client Credentials), cached ihn mit Refresh vor Ablauf und fährt bei 401 sofort neu auf. Circuit Breaker bei 3 Fehlern → 60s cooldown.
curl https://sentinel.intern.msp.de/health
# circuit_breakers.NINJARMM.state sollte "CLOSED" sein nach erstem Job
TicketFlow exponiert POST /api/webhooks/psa. Der Endpoint validiert die PSA-Signatur und emittiert intern ein tf.analysis.started-CloudEvent.
System → Callbacks → New. Event: Ticket Created. URL: https://ticketflow.intern.msp.de/api/webhooks/psa. Secret: in TicketFlow-Config hinterlegen.
TicketFlow löst den PSA-Company-Record auf die interne end_customer_id auf. Das Mapping liegt in der Connector-Config (JSON oder DB-Tabelle). Ohne Mapping: Job ohne Kunden-Scope — Korrelation wird mandantenübergreifend und damit fehlerhaft.
{
"connectwise_company_id": "KUNDE-ABC-42",
"end_customer_id": "eci-msp-kunde-abc",
"sentinel_instance": "https://sentinel-abc.intern.msp.de"
}
Für automatischen Diagnose-Kommentar im Ticket: PSA-API-Key mit Schreibrecht auf Tickets anlegen. TicketFlow schreibt nur Kommentare — keine Ticket-Status-Änderungen, keine automatische Zuweisung. Das Urteil trifft der Techniker.
tf.analysis.started an Sentinel mit asset_id, end_customer_id, ticket_opened_at
T+1s
Sentinel: Pre-Ticket-Drift-Lookup — getTimelineStateAt(asset, ticket_opened_at) vs. jetzt. Drift, der vor dem Ticket entstand, ist sofort sichtbar.
T+1s
Sentinel: Pre-Flight-Snapshot von CMDB / RMM / IPAM / NinjaOne. Aktueller Basis-Zustand gesichert.
T+1s
Sentinel: Drift-Watch startet (30s-Polling). Nachbar-Assets für Kontaminations-Referenz geladen.
T+1—N
TicketFlow: ACH-Matrix aufgebaut. Hypothesen aus Asset-Graph, Alttickets, Doku, Monitoring. Beobachtungen gesammelt.
T+30s+
Sentinel: bei Drift → tf.sentinel.drift.detected an TicketFlow. Betroffene Observations werden source_strength-degradiert.
Cluster
Bei ≥2 Assets gleicher VLAN/Subnet-Drift in 5min → tf.sentinel.cluster.detected. TicketFlow ergänzt Infrastruktur-Incident-Hypothese.
Schluss
TicketFlow sendet tf.analysis.concluded → Sentinel liefert finales Verdikt. Drift-Watch stoppt.
Output
Techniker sieht: ACH-Matrix mit Trennschärfe, Drift-Chronologie, Verdikt-Badge, offene Fragen — kein Urteil, nur vollständiges Bild.
Jede abgeschlossene Sentinel-Analyse erzeugt automatisch ein Markdown-Dokument: das vollständige Diagnose-Protokoll des Tickets — Verdikt, Drift-Chronologie, Integritätsbefunde, Asset-Fingerprint. Gespeichert in einem Git-Repository beim MSP. Manipulationssicher durch die kryptographische Hash-Kette von Git.
src/audit-store.js) ist ab Commit 1871c27 Teil von Sentinel. Zwei ENV-Variablen genügen zur Aktivierung — kein separater Dienst, keine Datenbank.
Kein proprietäres Format, kein Viewer nötig. Jeder Editor, jede IDE, jeder Browser öffnet die Datei. Für Post-Mortems, Kundennachweise und Behördenanfragen sofort nutzbar.
Jeder Git-Commit enthält den SHA-256-Hash des Vorgängers. Eine nachträgliche Änderung an einem Diagnose-Protokoll bricht die gesamte Hash-Kette ab diesem Punkt — ohne spezielle Software erkennbar.
Das Audit-Git-Repo liegt auf MSP-eigener Infrastruktur. Kein Cloud-Dienst, kein Dritter. Das Repo kann auf Kunden-Namespaces aufgeteilt werden — ein Repo pro End-Kunde für maximale Isolation.
Git-History ist ein nativer, unveränderlicher Zeitstempel-Log mit Autor, Zeitpunkt und Inhalt. git log --author sentinel liefert die vollständige Diagnose-Chronologie eines Kunden.
audit-store/ ← git init hier
kunde-abc/
2026-06/
tf-job-001.md ← CLEAN · 2026-06-10
tf-job-002.md ← DRIFT_CRITICAL · 2026-06-10
kunde-xyz/
2026-06/
tf-job-003.md ← INCONCLUSIVE · 2026-06-10
unknown/
2026-06/
tf-job-004.md ← ohne end_customer_id
# Sentinel Audit Report
**Job-ID:** `tf-job-001`
**Asset:** `SRV-PROD-07`
**Kunde:** `kunde-abc`
**Analyse gestartet:** 2026-06-10 10:00:15 UTC
**Bericht erstellt:** 2026-06-10 10:01:03 UTC
**Analyse-Latenz:** 47.8 s
---
## Verdikt: DRIFT_CRITICAL
| Eigenschaft | Wert |
|----------------|------------------|
| Aktion | `REANALYZE` |
| Drift-Status | `DRIFT_CRITICAL` |
| Analyse-Modus | `manifest` |
| Konfidenz | `HIGH` |
| Schwere-Score | 8 |
---
## Drift-Ereignisse
| Zeitpunkt | Quelle | Feld | Alt | Neu | Schwere |
|----------------------|--------|--------|-----------|-----------|----------|
| 2026-06-10 10:00:52 UTC | CMDB | `vlan` | `VLAN-10` | `VLAN-20` | **CRITICAL** |
---
## Asset-Fingerprint
| Eigenschaft | Wert |
|-------------|---------------|
| `vendor` | `cisco` |
| `product` | `catalyst` |
| `role` | `core-switch` |
| `version` | `16.12.4` |
Ein leeres Git-Repository an einem gesicherten Ort (eigener Server, NAS, verschlüsseltes Volume). Pro End-Kunde ein eigenes Repo für maximale Isolation — oder ein gemeinsames Repo mit Kunden-Unterordnern.
mkdir /var/sentinel/audit-store && cd /var/sentinel/audit-store
git init
git config user.name "sentinel"
git config user.email "sentinel@local"
AUDIT_STORE_GIT_AUTO=true aktiviert den automatischen Commit nach jedem abgeschlossenen Job. Ohne diesen Flag werden die Markdown-Dateien geschrieben, aber nicht committed — nützlich wenn ein externer Cron-Job die Commits übernimmt.
AUDIT_STORE_PATH=/var/sentinel/audit-store
AUDIT_STORE_GIT_AUTO=true
Nach dem ersten abgeschlossenen Job liegt ein Markdown-Report im Repo. Git-Log zeigt den automatisch erstellten Commit.
git -C /var/sentinel/audit-store log --oneline
# → a3f91bc audit(kunde-abc): CLEAN tf-job-001
# → 7d2e104 audit(kunde-abc): DRIFT_CRITICAL tf-job-002
cat /var/sentinel/audit-store/kunde-abc/2026-06/tf-job-001.md
Ein Vorfall entsteht in der Vergangenheit. TicketFlow öffnet das Ticket erst danach. Die Frage ist: Was hat sich zwischen Vorfall-Beginn und Analyse-Start in der Infrastruktur verändert? Ohne externe, kontinuierlich messende Instanz ist diese Frage nicht beantwortbar.
Sentinel pollt alle Assets in seinem Watch-Set alle 5 Minuten — unabhängig davon, ob gerade ein Job läuft. Jede Feldänderung landet mit Zeitstempel in der asset_timeline-Tabelle. Delta-Modell: nur Änderungen werden geschrieben.
TicketFlow übergibt ticket_opened_at im tf.analysis.started-Event. Sentinel fragt: „Wie sah dieses Asset zum Zeitpunkt der Ticketöffnung aus?" — und vergleicht mit dem Jetzt.
Das finale Verdikt enthält einen separaten Block pre_ticket_drift mit Status, Zeitfenster und Feld-Events. TicketFlow kann diesen Block als eigenständige Hypothesen-Quelle verwenden: „VLAN-Drift zwei Stunden vor Ticket" ist eine starke Kausal-Hypothese.
Schickt TicketFlow einen Job für ein Asset, das noch nicht im Watch-Set ist, registriert Sentinel es automatisch. Ab diesem Zeitpunkt wird es kontinuierlich beobachtet — der nächste Job hat bereits eine Timeline.
TicketFlow selbst könnte seine Quelldaten zur Ticketöffnungszeit abfragen — aber das wäre eine Momentaufnahme, keine kontinuierliche Beobachtung. Der entscheidende Unterschied: Sentinel läuft unabhängig vom Ticket-Lifecycle. Es beobachtet die Infrastruktur, auch wenn keine Analyse angefordert wurde, auch wenn TicketFlow nicht erreichbar ist, auch wenn niemand daran denkt. Diese operationelle Unabhängigkeit ist kein technisches Detail — sie ist die Grundlage der Verlässlichkeit. Eine Prüfinstanz, die nur auf Anfrage misst, misst immer dann nicht, wenn es darauf ankommt.
src/continuous-snapshotter.js, asset_timeline-Tabelle, getTimelineStateAt(), pre_ticket_drift im Verdikt. 132 Tests grün. ENVs: SNAPSHOT_INTERVAL_MS (Default 300 s), WATCH_ASSETS (kommagetrennte Asset-IDs), TIMELINE_RETENTION_DAYS (Default 90).
Mit v2.4 erweitert Sentinel seine Kernaufgabe um fünf Module, die aus einem reaktiven Daten-Checker eine proaktive, lernfähige Qualitätssicherungsinstanz machen. Dazu kommen ein vollständiges Report-Dashboard und ein kontextsensitives Hilfe-System.
Rollierendes 30-Tage-Scoring pro Datenquelle. Score = Verfügbarkeitsrate − Drift-Penalty. Drei Tiers: HEALTHY (≥ 0,85), DEGRADED (0,60–0,84), UNRELIABLE (< 0,60). Circuit-Open-Events fließen als Unavailability in den Score. Sichtbar unter GET /quality und im Report-Dashboard.
Sentinel lernt, welche Felder eines Assets üblicherweise stabil sind. Ab 10 Beobachtungen und < 10 % Änderungsrate gilt ein Feld als stabil. Ändert es sich trotzdem, erhält das Drift-Event das Flag anomaly: true mit dem erwarteten Normalwert. Baselines werden lazy berechnet und gecacht.
Tägliche Cross-Asset-Prüfung um 02:00 UTC. Erkennt drei Muster: ip_conflict (mehrere Assets teilen dieselbe IP), ghost_asset (kein Timeline-Eintrag seit > 7 Tagen), vlan_overlap (gleiches VLAN-Label in mehreren /24-Subnetzen). Findings unter GET /findings.
Wartungsfenster mit optionalem Kunden-Filter. Drift, der innerhalb eines aktiven Fensters erkannt wird, erhält context: "maintenance_window". API: POST/GET/DELETE /maintenance. Ein leeres customers-Array gilt global für alle Mandanten.
TicketFlow sendet nach Ticket-Schluss ein tf.analysis.resolved-Event mit Resolution (TRUE_POSITIVE / FALSE_POSITIVE / RESOLVED_EXTERNAL) und Root-Cause-Feldern. Ab 5 Einträgen liefert GET /audit/correlations eine Rangliste der häufigsten Ursachenfelder.
GET /report: HTML-Dashboard mit Stats-Karten, Qualitätsbars, Filter-Sidebar, Detail-Modal und CSV-Export. GET /help: 11-thematisches Hilfe-System mit Sticky-Nav — kein Auth, direkt im Browser nutzbar.
src/source-quality.js · src/anomaly-baseline.js · src/integrity-scheduler.js · src/maintenance-window.js · src/feedback-handler.js · src/audit-query.js · src/report-ui.js · src/help.js
· 5 neue DB-Tabellen (source_quality_events, field_baseline, scheduled_findings, maintenance_windows, feedback_log)
· CRA RFC-9301AC9C OVERRIDDEN (risk-04 FP: bounded DELETE auf Konfigurationsdaten)
Nach Abschluss von v2.4 wurde ein vollständiger Multi-Agenten-Code-Review über die gesamte Sentinel-Codebase durchgeführt (9 Finder-Winkel, Verifikation, Gap-Sweep). Alle 10 bestätigten Findings sind in v2.5 behoben.
audit-store.js: exec() mit Template-String war anfällig für Shell-Injection über jobId und storePath. Ersetzt durch execFile() mit Argument-Array — kein Shell-Interpreter, keine Expansion.
report-ui.js: escHtml() escapte kein ' — kritisch im onclick-Kontext. Fix: '-Encoding + onclick="openModal(this.dataset.job)" eliminiert JS-String-Interpolation vollständig.
audit-query.js: jobId aus req.params wurde direkt in path.join() verwendet. Fix: resolve() + startsWith(allowedBase + sep) — jeder Pfad außerhalb AUDIT_STORE_PATH wird verworfen.
app.js: GET /report und GET /report/summary waren ohne Auth erreichbar und gaben Audit-Statistiken, Quelldaten-Scores und Maintenance-Windows preis. Beide Routen erfordern jetzt auth-Middleware.
feedback-handler.js: glob aus node:fs/promises ist erst ab Node 22 verfügbar. package.json deklariert ≥18. Ersetzt durch manuellen readdir()-Loop — lauffähig auf Node 18, 20 und 22.
continuous-snapshotter.js: parseInt("invalid") ergibt NaN — setTimeout(fn, NaN) feuert sofort und erzeugt eine Tight-Loop. Fix: isNaN(parsed) || parsed < 1000 → DEFAULT_INTERVAL_MS.
integrity-scheduler.js: VLAN-Overlap-Query verwendete HAVING a.snapped_at = MAX(a.snapped_at) auf nicht-aggregierten Spalten — SQLite gibt willkürliche Zeilen zurück. Ersetzt durch korrelierte Subqueries, die den echten MAX-Datensatz selektieren.
snapshot-manager.js: fetchAsset → null (Quelle hat keinen Eintrag) wurde still verworfen — onSnapshotMiss() nie aufgerufen, Quelldaten-Score damit zu optimistisch. Fix: Miss-Metrik wird auch bei null-Rückgabe gezählt.
app.js: Ungültige tf.analysis.resolved-Events (fehlende jobId, unbekannte Resolution) wurden von handleFeedback() geworfen, aber als 500 zurückgegeben. Fix: try/catch mit explizitem 400-Status.
app.js: GET /quality?window=999999 konnte einen unkontrollierten Abfragebereich über die gesamte DB-Historie erzwingen. windowDays wird auf 1–365 geclampt; NaN fällt auf Default 30 zurück.
src/audit-store.js · src/report-ui.js · src/audit-query.js · src/app.js · src/feedback-handler.js · src/continuous-snapshotter.js · src/integrity-scheduler.js · src/snapshot-manager.js
· CRA RFC-F1D3881D APPROVED (Score 5 — Medium)
Alle im Dokument verwendeten Fachbegriffe — von Diagnose-Methodik über Datenmodell bis Infrastruktur.
specversion, type, source, id, time. TicketFlow und Sentinel kommunizieren ausschließlich über dieses Format — die Schnittstelle ist damit vendor-agnostisch und unabhängig vom Transport-Protokoll (HTTP, Kafka, MQTT)./health.src/audit-store.js), das nach jedem abgeschlossenen Analyse-Job ein Markdown-Protokoll schreibt. Enthält Verdikt, Drift-Chronologie, Integritätsbefunde und Asset-Fingerprint. Aktiviert über AUDIT_STORE_PATH (Pfad zum Git-Repo) und AUDIT_STORE_GIT_AUTO=true (automatischer Commit). Das Modul ist fire-and-forget — ein Fehler beim Schreiben blockiert nicht die Verdikt-Auslieferung an TicketFlow.getTimelineStateAt(assetId, timestamp) lässt sich der vollständige Zustand eines Assets zu einem beliebigen Zeitpunkt in der Vergangenheit rekonstruieren. Retention-Schwelle: 90 Tage (konfigurierbar über TIMELINE_RETENTION_DAYS).ticket_opened_at) und dem Zeitpunkt des Analyse-Starts. Während der reguläre Drift-Watch Veränderungen während einer laufenden Analyse erfasst, zeigt pre_ticket_drift das, was sich vor dem Ticket verändert hat — der diagnostisch wichtigere Zeitraum, da viele Vorfälle durch Konfigurationsänderungen verursacht werden, die Stunden vor dem Symptom stattfanden. Wird als separater Block im finalen Verdikt-CloudEvent geliefert, enthält Status (NONE / DRIFT_MINOR / DRIFT_CRITICAL / NO_HISTORY), Zeitfenster und Feld-Events.tf.analysis.started an Sentinel. Sentinel nutzt es als Rückblick-Anker: Es fragt die asset_timeline ab, um den Asset-Zustand zum Zeitpunkt der Ticketöffnung zu rekonstruieren und mit dem aktuellen Zustand zu vergleichen. Ohne ticket_opened_at ist kein Pre-Ticket-Drift möglich — Sentinel fällt auf das Forward-Polling-Modell zurück.