Dateien
AegisSight-Monitor/CLAUDE.md
claude-dev 0154c0f575 fix(eu): Kapazitaetsengpaesse bei Bedrock aussitzen statt Schritte verlieren
Am 01.08.2026 lieferte Bedrock waehrend eines Laufs ServiceUnavailableException.
Botocore versuchte es dreimal in fuenf Sekunden und gab auf, die
Web-Source-Selektion fiel ersatzlos aus. Im Bericht war davon nichts zu sehen,
der Lauf galt als vollstaendig. Gemessen sind es zwei betroffene Aufrufe bei
rund 340, also 0,6 Prozent, aber jeder Vorfall kostet einen ganzen
Pipeline-Schritt.

Der Fehler ist auch keiner unserer Quote. ServiceUnavailableException ist ein
503, AWS hat also gerade keine Kapazitaet fuer das Modell. Unsere Meldung
sagte trotzdem "Rate-Limit", weil der Code beide Faelle in einen Topf warf.

Aendert sich damit:

1. Geduldiges Wiederholen. Kapazitaets- (503), Kontingent- (429) und
   Verbindungsfehler werden nach 5, 15 und 30 Sekunden erneut versucht. Diese
   Dellen dauern typischerweise unter einer Minute, unsere bisherigen fuenf
   Sekunden lagen genau im ungeguenstigsten Fenster. Stufen ueber
   BEDROCK_RETRY_WAITS einstellbar.

2. Zwei Bremsen gegen lange Laeufe. Gewartet wird nur, wenn die Wartezeit ins
   Zeitbudget des Aufrufs passt (die Wartezeit zaehlt gegen dasselbe
   asyncio-Budget, und die Haiku-Planungsaufrufe haben nur 120 Sekunden), und
   nur solange das Wartebudget des Refreshs reicht (BEDROCK_RETRY_BUDGET_S,
   Vorgabe 120 Sekunden). Ein Lauf kann sich dadurch um hoechstens zwei
   Minuten verlaengern.

3. Ursachen werden getrennt benannt. Im Log steht jetzt "hat keine freie
   Kapazitaet" oder "meldet ausgeschoepftes Kontingent" statt pauschal
   "Rate-Limit". Nach aussen bleibt beides die Kategorie rate_limit, damit die
   Retry-Steuerung des Orchestrators unveraendert greift.

4. Stoerungen werden sichtbar. Waehrend einer Wartezeit meldet die Oberflaeche
   "KI-Dienst hat keine freie Kapazitaet, neuer Versuch in 15 Sekunden", sonst
   sieht die stehende Anzeige wie ein Haenger aus. Nach dem Lauf steht eine
   Zusammenfassung im Refresh-Protokoll, auch wenn der Lauf sonst glatt
   durchlief.

5. botocore laeuft im Modus 'adaptive' statt 'standard'. Es bremst sich bei
   Drosselung selbst ein und deckt die Sekunden ab, unsere Schleife die
   Zehnersekunden.

Zeitliche Wirkung, gemessen am EU-Lauf der Ceuta-Lage (252 Sekunden, 15
Bedrock-Aufrufe): im stoerungsfreien Fall null, im betroffenen Lauf rund eine
Minute mehr. Bei 0,6 Prozent Fehlerrate je Aufruf trifft das etwa jeden
elften Lauf, im Mittel ueber alle Laeufe rund zwei Prozent.

Gegenprobe mit einem echten Bedrock-Aufruf ueber den geaenderten Pfad: Antwort,
Verbrauch und Kosten unveraendert, keine Stoerung protokolliert.

Neu sind 23 Pruefungen, insgesamt laufen 179 ohne Netzzugriff und ohne Kosten.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-01 22:58:50 +02:00

432 Zeilen
26 KiB
Markdown

# AegisSight-Monitor
> OSINT-Lagemonitoring mit KI-gestützter Nachrichtenanalyse
## Übersicht
```yaml
projekt: AegisSight-Monitor
url: https://monitor.aegis-sight.de
server: ssh monitor (46.225.141.13, User: claude-dev)
pfad: /home/claude-dev/AegisSight-Monitor
quellcode: /home/claude-dev/AegisSight-Monitor/src/
datenbank: /home/claude-dev/osint-data/osint.db (SQLite WAL, geteilt mit Verwaltungsportal + Globe; data/ im Projekt ist ein Symlink darauf)
gitea: https://gitea-undso.aegis-sight.de/AegisSight/AegisSight-Monitor
service: aegis-monitor.service (systemd, Port 8891, Nginx Reverse Proxy, EIN uvicorn-Prozess - Orchestrator und WebSockets halten Zustand im Speicher, niemals --workers setzen)
venv: /home/claude-dev/.venvs/osint/ (Python 3.12)
```
## Technologie-Stack
```yaml
backend:
framework: FastAPI + Uvicorn
datenbank: SQLite WAL (aiosqlite, async)
auth: Magic-Link-Login per E-Mail (JWT HS256, 24h)
scheduler: APScheduler (PDF-Ingest 1min, Auto-Refresh 1min, Cleanup 1h, Health-Check taeglich 04:00, Telegram-Status taeglich 04:30)
websocket: FastAPI native (Echtzeit-Updates an Clients)
ki: Claude CLI als Subprocess (WebSearch + WebFetch Tools)
ki_backend_eu: |
EU-Umbau Phase 1 (seit 2026-07-29): zweiter Modellweg ueber AWS Bedrock
(EU-Inferenzprofile Frankfurt, agents/bedrock_client.py). Umschaltbar je
Lage (incidents.ai_backend) > Organisation (org_setting 'ai_backend') >
global (ENV AI_BACKEND, Default 'cli'). Aufloesung je Refresh im
Orchestrator via ContextVar _ai_backend_var (gemeinsame Aufloesung mit der
Lane-Wahl, siehe _resolve_ai_backend). Seit Phase 2 (2026-07-30) laeuft im
EU-Modus auch die RECHERCHE europaeisch: agents/eu_researcher.py steuert
eine Schleife aus Bedrock-Planung und staan-Suchen (services/staan_client.py)
und liefert dasselbe JSON wie die CLI-WebSearch. Werkzeuglose Aufrufe gehen
direkt ueber bedrock_client. Seit Phase 3 (2026-07-30) laufen auch
Faktencheck und Lagebild im EU-Modus komplett ueber Bedrock: der
Faktenchecker bekommt vorab eine staan-Stuetzsuche (eu_call_with_search in
eu_researcher.py, Haiku plant Verifikations-Queries, Treffer als
Kontextblock im Prompt), das Lagebild arbeitet rein auf dem
Meldungsbestand. Im EU-Modus gibt es damit KEINE CLI-Aufrufe mehr.
Doppelspur: eine Orchestrator-Lane je
(Organisation, Backend) — CLI- und EU-Lauf derselben Org laufen gleichzeitig,
gleiche Backends seriell. Kosten aus Token x BEDROCK_PRICING plus
STAAN_COST_PER_QUERY_USD, Credits-System unveraendert. AWS-Zugang +
STAAN_API_KEY liegen in der Staging-.env. boto3 muss im venv installiert
sein (beim Promote nach Live auch dort nachziehen!).
ki_modelle:
schnell: CLAUDE_MODEL_FAST (Haiku) — Feed-Selektion, Topic-Filter, Geoparsing, Uebersetzung, Chat, QC
mittel: CLAUDE_MODEL_MEDIUM (Sonnet) — nur Netzwerkanalyse (entity_extractor hat aktuell keinen Aufrufer in der App)
standard: CLAUDE_MODEL_STANDARD (Opus) — Recherche, Lagebild, Faktencheck
email: aiosmtplib (smtp.ionos.de:587 TLS)
frontend:
typ: Vanilla JS (kein Framework, kein Build-Step)
design: AegisSight Dark/Light Theme (Navy/Gold)
fonts: Poppins (Titel), Inter (Body)
layout: Reiter-Ansicht je Lage (layout.js; gridstack wurde abgeloest, nur Legacy-Stubs uebrig)
karte: Leaflet + MarkerCluster (lokal in vendor/, Kacheln von tile.openstreetmap.de)
echtzeit: WebSocket mit Auto-Reconnect und Ping/Pong
```
## Projektstruktur
```yaml
src/:
main.py: "FastAPI App, WebSocketManager, Scheduler, Lifespan, statische Routen"
config.py: "Konfiguration (JWT, Claude-Modelle, SMTP, RSS-Feeds, Zeitzone)"
auth.py: "JWT erstellen/verifizieren, Magic-Link/Code, get_current_user Dependency"
database.py: "SQLite Schema (25+ Tabellen), Migrationen, init_db(), get_db()"
models.py: "Pydantic Request/Response-Schemas"
source_rules.py: "Domain-Kategorisierung, RSS-Feed-Discovery, Claude-Feed-Bewertung"
report_generator.py: "PDF (WeasyPrint) + DOCX (python-docx) Export"
routers/:
auth.py: "Magic-Link-Login, Token-Verify, /api/auth/me"
incidents.py: "CRUD Lagen, Refresh, Artikel, Snapshots, Faktenchecks, Export, E-Mail-Abos, Refresh-Log, Beschreibung generieren (Prompt Enhancement)"
sources.py: "CRUD Quellen, Discovery (Single/Multi), Domain sperren, Telegram-Validierung"
chat.py: "KI-Assistent (Haiku), Injection-Schutz, Tech-Leak-Filter"
public_api.py: "API-Key Auth, Globe-Feed (GeoJSON), Globe-Ingest, Snapshot-Abruf"
notifications.py: "CRUD Benachrichtigungen, Unread-Count, Mark-Read"
feedback.py: "E-Mail-Feedback mit Bild-Anhaengen"
tutorial.py: "Tutorial-Fortschritt pro User"
routes/:
version_router.py: "GET /api/version + /api/release-notes (OHNE Auth, fuer das Update-Fenster im Frontend). Historischer Zweitordner neben routers/"
agents/:
orchestrator.py: "Queue-basierte Refresh-Steuerung (eine Lane je Organisation), Research Multi-Pass (3 Durchlaeufe), Retry, Cancel, Credits-Buchung. _run_refresh ist ~1450 Zeilen, Aenderungen dort nur mit Vorsicht"
stage_runners.py: "Studio-Bausteine: analyze/factcheck einzeln auf vorhandenem Datenbestand, bewusst NEBEN _run_refresh gebaut"
researcher.py: "WebSearch-Recherche (Standard + 4-Phasen-Tiefenrecherche), Feed-/Kanal-/Account-Selektion, Keyword-Extraktion, Google-News-Suchfeeds je Sprache"
analyzer.py: "Analyse-Agent (Lagebild/Briefing, Erst- + inkrementell, Inline-Zitate, Topic-Filter, Neueste Entwicklungen, Stimmungsbild)"
factchecker.py: "Faktencheck (Erst/Inkrementell/Zwei-Phasen mit Haiku-Triage ab 25 Fakten), Claim-Matching, Dedup, Schutz gegen Massen-Downgrades. Belegtiefe: zaehle_medienhaeuser zaehlt unabhaengige Verlage statt Fundstellen, pruefe_belegtiefe stuft Fakten ab, deren Status mehr Beleg behauptet als vorliegt (confirmed 2, established 3, Primaerbeleg 1), bewertungsregeln() haengt den verbindlichen Regelblock an jeden Auftrag"
geoparsing.py: "Haiku-basierte Ortsextraktion, Geocoding offline via geonamescache"
entity_extractor.py: "Netzwerkanalyse (Sonnet). ACHTUNG: aktuell ohne Aufrufer in der App, nur regenerate_relations.py im Repo-Root nutzt es"
translator.py: "Haiku-Uebersetzung in Batches a 5 (groessere Batches rissen den JSON-Output ab)"
claude_client.py: "Shared Claude CLI Client, Usage-Tracking (Token, Kosten), Rate-Limit-Erkennung, Cancel via ContextVar. Routet je nach _ai_backend_var werkzeuglose Aufrufe zu bedrock_client"
bedrock_client.py: "EU-Modellweg ueber AWS Bedrock Converse (nur eu.-Inferenzprofile, Token-zu-USD-Umrechnung ueber BEDROCK_PRICING, gleiche Fehlerkategorien wie das CLI). EU-Umbau Phase 1. Wiederholt Kapazitaets- (503) und Kontingentfehler (429) nach BEDROCK_RETRY_WAITS (5/15/30s), begrenzt durch das Zeitbudget des Aufrufs und BEDROCK_RETRY_BUDGET_S je Refresh; Stoerungen landen im Refresh-Protokoll (refresh_log.error_message) und waehrend der Wartezeit als status_update in der Oberflaeche"
eu_researcher.py: "EU-Recherche-Schleife (Phase 2): Bedrock-Modell schlaegt Suchanfragen vor, Code fragt staan, Modell entscheidet weiter/fertig. Bildet die 4-Phasen-Tiefenrecherche nach, liefert denselben JSON-Array-Text wie die CLI-WebSearch (Einstieg in researcher.search), akzeptiert NUR URLs aus echten staan-Treffern"
feeds/:
rss_parser.py: "RSS-Feed-Parsing (feedparser + httpx), adaptive Keyword-Schwelle, Frische-Bonus, Domain-Cap"
telegram_parser.py: "Telethon-basierter Telegram-Parser (eine gemeinsame Session), Kanal-Validierung"
x_parser.py: "X/Twitter via twscrape (Account-Store ~/.x-scraper/accounts.db, optional Mobilfunk-Proxy)"
podcast_parser.py: "Podcast-Feeds inkl. Transkript-Extraktoren (transcript_extractors/)"
services/:
pipeline_tracker.py: "Die 11 Pipeline-Schritte (DE/EN, Laien-Tooltips), schreibt refresh_pipeline_steps + WebSocket-Events"
post_refresh_qc.py: "Post-Refresh Quality Check: Faktencheck-Duplikate, Location-Korrektur, Umlaut-Normalisierung"
fact_consolidation.py: "Periodisches Haiku-Clustering. ACHTUNG: als 6h-Job dokumentiert, aber NICHT im Scheduler eingeplant"
source_health.py: "Quellen-Health-Checks (Erreichbarkeit, Feed-Validitaet, Stale, fetch_strategy-Logik)"
source_suggester.py: "KI-Quellen-Vorschlaege via Haiku + Karteileichen-Heuristik"
pdf_ingest.py: "Minutenjob: hochgeladene PDFs einlesen (pdfplumber + OCR-Fallback), uebersetzen, als Pool-Artikel ablegen"
org_settings.py: "Key-Value-Einstellungen je Organisation (output_language etc.) mit 60s-Cache"
media_registry.py: "Kanonische Medien-Identitaet je Domain (registrable_domain, namen_je_domain, Aggregator-Liste, Sprache aus der Adresse). Sorgt dafuer, dass ein Verlag im Bericht unter einem Namen gefuehrt und einmal gezaehlt wird"
staan_client.py: "staan.ai Such-Client (EU-Umbau Phase 2): Suche + Volltexte (full_content=markdown) ueber den europaeischen Index, Pflicht-Domain-Ausschlussliste je Anfrage, max 10 Ausschluss-Domains"
license_service.py: "Lizenz-Pruefung, Credits-Buchung (charge_usage_to_tenant), Periodenwechsel, Budget-Warnung. expire_licenses() existiert, hat aber KEINEN Scheduler-Job"
middleware/:
license_check.py: "Dependencies: require_active_license, require_writable_license"
email_utils/:
sender.py: "Async SMTP Versand"
templates.py: "HTML-Templates (Magic-Link, Benachrichtigungen)"
rate_limiter.py: "Rate-Limiting Magic-Links"
migration/:
migrate_to_multitenancy.py: "Einmal-Migration Single->Multi-Tenant"
report_templates/:
report.html: "HTML-Template fuer PDF/DOCX-Export"
static/:
index.html: "Login-Seite (Magic-Link), JS inline"
dashboard.html: "Hauptdashboard (Sidebar + Reiter-Ansicht + 8 Modals + Chat-Widget)"
studio.html: "Studio (3-Spalten-Werkstatt, online nur fuer info@ sichtbar - Gating rein clientseitig)"
css/:
style.css: "AegisSight Design System (Dark/Light Theme, alle Komponenten)"
studio.css: "Studio-spezifische Styles"
js/:
api.js: "REST-API-Client (fetch, Auth-Header, 30s Timeout, 403 -> Nur-Lese-Modus)"
app.js: "Hauptlogik: ThemeManager, NotificationCenter, App-Objekt (181 KB)"
components.js: "UI-Rendering: Sidebar, Faktenchecks, Toasts, Progress, Karte (geteilt mit Studio)"
studio.js: "Kompletter Studio-Controller (109 KB)"
chat.js: "Chat-Assistent Widget (Bedienfragen)"
pipeline.js: "Grafische Analysepipeline (11 Schritte, Live-Animation)"
layout.js: "Reiter-Umschalter je Lage (merkt letzten Reiter; enthaelt noch gridstack-Legacy-Stubs)"
tutorial.js: "32-Schritte-Rundgang (140 KB). AKTUELL DEAKTIVIERT, alle Einstiege auskommentiert"
a11y.js: "Barrierefreiheits-Panel (identische Datei wie im Verwaltungsportal)"
update-system.js: "Was-ist-neu-Modal aus RELEASES.json + Update-Banner"
ai-disclaimer.js: "KI-Haftungshinweis beim ersten Besuch"
i18n.js: "Mini-Uebersetzung de/en (nur teilweise verdrahtet, Studio/Login komplett deutsch)"
ws.js: "WebSocket-Client (Reconnect, Ping/Pong)"
vendor/:
leaflet.js: "Karten-Bibliothek"
leaflet.markercluster.js: "Marker-Clustering"
tests/:
hinweis: "Laufen ohne Netzzugriff und ohne Kosten, Modell, Suche und Datenbank sind durch Testdoubles ersetzt. Aufruf aus dem Projektstamm, zum Beispiel venv/bin/python tests/test_eu_factcheck.py"
test_eu_factcheck.py: "EU-Faktencheck, Nachhak-Runde, Quellenzuordnung, plus Regressionsschutz fuer den Anthropic-Weg"
test_eu_belegsuche.py: "Gezielte Belegsuche ueber staan, Planung, Grenzen, Entdopplung, Ausfallverhalten"
test_qc_und_runden.py: "Absicherung der Duplikatpruefung und Abbruch der Suchrunden bei erreichter Treffergrenze"
test_mehrfachantwort.py: "Antworten, in denen sich das Modell selbst korrigiert und mehrere Fassungen liefert, die ausfuehrlichste muss gewinnen"
test_faktencheck_belegtiefe.py: "Belegzaehlung nach Medienhaus, Nachbedingung fuer bestaetigte Fakten, Sperre gegen das Hochstufen von developing, getrennte Bezeichnungen fuer Widerspruch und Widerlegung"
test_bedrock_wiederholung.py: "Wiederholung des EU-Wegs bei Kapazitaets- und Kontingentfehlern, Staffelung, Zeit- und Wartebudget, Sichtbarkeit im Refresh-Protokoll"
test_quellenausgabe.py: "Ausgabetreue des Quellenverzeichnisses: Nummern aus dem Lagebild statt Zeilennummern, keine Kappung, ein Verlag ein Name, Statistik deckungsgleich mit dem Verzeichnis"
```
## Architektur
```yaml
incident_typen:
adhoc:
label: "Live-Monitoring"
quellen: "RSS + WebSearch + optional Telegram"
analyse: "Fliesstext-Lagebild"
faktencheck_status: "confirmed/unconfirmed/contradicted/false/developing (contradicted = Belege widersprechen einander, false = nachweislich widerlegt)"
refresh: "Manuell oder automatisch (Intervall konfigurierbar)"
research:
label: "Recherche"
quellen: "Nur WebSearch 4-Phasen-Tiefenrecherche (kein RSS)"
analyse: "Strukturiertes Briefing (Ueberblick, Hintergrund, Akteure, Lage, Einschaetzung, Quellenqualitaet)"
faktencheck_status: "established/unverified/disputed/false/developing"
refresh: "Immer manuell, erster Refresh automatisch 3 Durchlaeufe (Multi-Pass)"
multi_pass:
durchlaeufe: 3
labels: ["Breite Erfassung", "Vertiefung", "Konsolidierung"]
bedingung: "Nur beim ersten Refresh (kein Summary vorhanden)"
cancel: "Zwischen und innerhalb der Durchlaeufe moeglich"
refresh_pipeline:
hinweis: "Die 11 nutzersichtbaren Schritte definiert services/pipeline_tracker.py. Interner Ablauf in _run_refresh:"
1: "Feed-/Kanal-/Account-Selektion (Haiku) + dynamische Keywords je Sprache"
2: "Parallel sammeln: RSS + Google-News-Suchfeeds + WebSearch (Opus) + optional Telegram + X + Podcasts (nur adhoc)"
3: "URL-Verifizierung (HEAD-Requests, tote URLs werden zu site:-Suchlinks repariert)"
4: "Duplikaterkennung (URL + Headline, dann gegen DB-Bestand)"
5: "Relevanz-Scoring + semantischer Topic-Filter (Haiku)"
6: "Geoparsing (Haiku + geonamescache offline)"
7: "Faktencheck ZUERST (liefert Faktenkontext), DANN Lagebild/Briefing (Opus). Optional Stimmungsbild aus Foren-Quellen"
8: "Uebersetzung fehlender DE-Texte (Haiku, nur wenn TRANSLATOR_ENABLED) + Neueste Entwicklungen (nur adhoc)"
9: "Post-Refresh QC (Fakten-Dubletten, Karten-Kategorien, Umlaute)"
10: "Notifications (DB + E-Mail + WebSocket), Credits-Buchung (flat: adhoc 45 / research 40 je Durchlauf)"
11: "Background: Source-Discovery + Executive Summary"
multi_tenancy: |
Mandantentrennung ueber tenant_id-Filter je Abfrage (keine Middleware). Bei Lagen zentral
ueber _check_incident_access. ACHTUNG, nicht alle Tabellen haben tenant_id
(source_health_checks, source_suggestions, incident_subscriptions, billing_tariff),
und der WebSocket-Broadcast filtert Stand 07/2026 NICHT nach Mandant.
lage_reiter:
- "Neueste Entwicklungen (research: Zusammenfassung)"
- "Lagebild (research: Recherchebericht, Markdown + Inline-Zitate)"
- "Ereignis-Timeline (horizontale Achse, Bucketing, Filter)"
- "Geografische Verteilung (Leaflet, Kategorie-Marker, Legende)"
- "Faktencheck (Status-Icons, Evidence, Filter)"
- "Oeffentliche Stimmung (nur bei Foren-Quellen)"
- "Analysepipeline (grafisch, 11 Schritte)"
- "Quellenuebersicht (nach Domain gruppiert)"
```
## Datenbank (30+ Tabellen)
```yaml
kern: "organizations, licenses, users, magic_links, organization_settings, portal_admins (gehoert dem Portal)"
lagen: "incidents, articles, incident_snapshots, fact_checks, incident_events, fact_check_runs"
pipeline: "refresh_log, refresh_pipeline_steps"
quellen: "sources, source_alignments, source_health_checks, source_suggestions, user_excluded_domains, podcast_transcripts"
geo: "article_locations"
netzwerk: "network_analyses, network_analysis_incidents, network_entities, network_entity_mentions, network_relations, network_generation_log (alle leer, kein Endpoint im Monitor)"
abrechnung: "token_usage_monthly, billing_tariff, user_activity_days"
system: "notifications, incident_subscriptions, system_status"
hinweise:
- "Es gibt KEINE feedback-Tabelle, Feedback geht direkt per E-Mail raus"
- "Portal-Tabellen (portal_audit_log, portal_magic_links, source_health_history u.a.) liegen in derselben Live-DB, werden aber nur vom Verwaltungsportal angelegt/genutzt"
- "users.is_global_admin/globe_access/network_access legt NUR das Portal an. Eine frisch per init_db erzeugte Monitor-DB kann daher keinen Login (no such column)"
```
## Verwandte Projekte (gleicher Server)
```yaml
verwaltungsportal:
pfad: /home/claude-dev/AegisSight-Monitor-Verwaltung
url: https://monitor-verwaltung.aegis-sight.de
service: verwaltungsportal.service (Port 8892)
geteilte_db: ja
globe:
pfad: /home/claude-dev/AegisSight-Globe
url: https://globe.aegis-sight.de
service: globe.service (Port 8890)
geteilte_db: ja
netzwerkanalyse:
pfad: /home/claude-dev/AegisSight-Netzwerkanalyse
url: https://netzwerkanalyse.aegis-sight.de
service: netzwerkanalyse.service (Port 8893)
```
## Regeln
```yaml
regeln:
- "Jede Aenderung MUSS sofort committed und nach Gitea gepusht werden"
- "Echte Umlaute in UI-Texten (ue, ae, oe, ss), keine Umschreibungen"
- "Keine Passwoerter oder Secrets in den Code committen"
- "Service nach Backend-Aenderungen: sudo systemctl restart aegis-monitor (Staging startet der Auto-Deploy selbst neu)"
- "Frontend-Aenderungen (HTML/JS/CSS) brauchen keinen Neustart"
- "Backup-Dateien (.bak) nicht committen, vor Push loeschen"
```
## UI-Sync mit dem Lokal-Fork (verbindlich)
> Seit 2026-07-25. Die Oberflächen von Online-Monitor und Lokal-Fork bleiben angeglichen.
```yaml
ui_sync:
regel: "Jede Änderung unter src/static wird noch in derselben Sitzung ins jeweils andere Repo portiert."
lokal_repo: "AegisSight/AegisSight-Monitor-Local (Branch main, läuft auf dem Windows-Rechner des Nutzers)"
portieren:
lokal_nach_online: |
In einem temporären Klon dieses Repos (Branch develop) den Lokal-Fork als Remote
hinzufügen, fetchen, git cherry-pick <sha> (gemeinsame Historie, Drei-Wege-Merge
funktioniert), dann push origin develop. NIE auf main pushen, Live nur per Promote-UI.
online_nach_lokal: "Im Lokal-Fork: git fetch online && git cherry-pick <sha> (Remote 'online' ist dort eingerichtet, fetch-only)."
drift_check: "Im Lokal-Fork: bash scripts/ui-drift-check.sh (vergleicht src/static beider Repos, Zeilenenden ignoriert)"
wortlaut: "Die Verbrauchseinheit heißt in BEIDEN Monitoren 'Credits' (Entscheidung Nutzer 2026-07-25). Nicht Guthaben, nicht Einheiten."
gewollte_unterschiede:
- "Online: Studio nur für info@aegis-sight.de freigegeben (Gating in studio.js init, Studio-Link im Dashboard-Header versteckt). Lokal ohne Gating, Knopf immer sichtbar."
- "Lokal: X-Zugänge-Oberfläche (twscrape) in Sidebar/Modal/Quellenübersicht. Online bewusst nicht vorhanden (kein x-Router im Online-Backend)."
- "Lokal: Auto-Login über /api/auth/dev-login (Demo-Modus). Online ausschließlich Magic-Link."
- "Lokal: Kostenvorschau im Anlege-Dialog (updateIntervalCostHint, Fork-Commit 5b0b578). Online noch nicht portiert."
- "Online: Umschalter 'KI-Verarbeitung' im Anlege-/Bearbeiten-Dialog (Auswahl inc-ai-backend: Standard/Anthropic/EU) plus EU-Kennzeichen in der Lagen-Kopfzeile (incident-eu-badge). Bewusst NICHT im Lokal-Fork, dort gibt es kein Bedrock-Backend und das Feld waere funktionslos (Stand 2026-07-30)."
bekannter_drift_stand_2026_07_26:
- "Die Takt-Untergrenze ist inzwischen AUCH online (dort _getMinIntervalMinutes, 30 Min; lokal _intervalMinMinutes mit Vorgabe 12 Std). Ältere Angaben, sie fehle online, sind überholt."
- "Online-only, noch nicht in den Fork portiert: Reiter 'Öffentliche Stimmung' (renderPublicMood) und die Fall-Chat-Rückfrage /clarify."
- "Echter Inhalts-Drift in rund 11 Dateien (app.js, components.js, api.js, studio.js, style.css u.a.), weitere ~7 Dateien unterscheiden sich nur durch Umlaute in Kommentaren."
stolperfalle_zeilenenden: |
Die Frontend-Dateien (src/static) sind CRLF, die Python-Dateien dieses Repos sind LF
(im Lokal-Fork teils anders). Beim Portieren keine Werkzeuge einsetzen, die Zeilenenden
pauschal umschreiben (z.B. sed -i unter Git Bash), und den Diff vor dem Commit auf
Plausibilität prüfen. Ein Riesen-Diff ist fast immer ein Zeilenenden-Unfall.
```
## Staging-Umgebung
```yaml
staging:
url: https://staging.monitor.aegis-sight.de
server: 46.225.141.13 (gleicher Host wie Live)
pfad: /home/claude-dev/AegisSight-Monitor-staging
branch: develop
port: 18891 (Live: 8891)
service: aegis-monitor-staging.service (systemd)
venv: /home/claude-dev/AegisSight-Monitor-staging/venv (eigenes venv)
zugriff: Magic-Link-Login an info@aegis-sight.de (Cookie 30 Tage)
datenbank:
pfad: /home/claude-dev/osint-data-staging/osint.db (per DB_PATH in .env gesetzt)
achtung: "~/AegisSight-Monitor-staging/data/osint.db ist eine UNBENUTZTE Altkopie"
initial: einmalige Kopie der Live-DB
drift: gewollt - Aenderungen in Staging beeinflussen Live nicht
reseed_von_live: |
sudo systemctl stop aegis-monitor-staging
cp /home/claude-dev/osint-data/osint.db /home/claude-dev/osint-data-staging/osint.db
sudo systemctl start aegis-monitor-staging
besonderheiten_env:
JWT_SECRET: eigener fuer Staging (nicht Live-JWT)
MAGIC_LINK_BASE_URL: https://staging.monitor.aegis-sight.de (sonst leitet App zu Live)
DB_PATH: /home/claude-dev/osint-data-staging/osint.db (Live setzt KEIN DB_PATH, dort greift der Default data/ = Symlink auf ~/osint-data)
STAGING_MODE: 1 (unlimited_budget, kein Credits-Hard-Stop)
TRANSLATOR_ENABLED: false
TELEGRAM_SESSION_PATH: ~/.telegram-staging/ (eigene Session, dort ist NIEMAND eingeloggt - Telegram-Recherche auf Staging liefert nichts, die Status-Karte im Portal zeigt das ehrlich an)
AI_BACKEND: "(optional) 'cli' oder 'bedrock', globaler Default fuer den EU-Modellweg. Ohne Eintrag gilt 'cli'"
AWS_ACCESS_KEY_ID_usw: "AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY, AWS_REGION=eu-central-1 und STAAN_API_KEY liegen seit 2026-07-29 in der Staging-.env (IAM-Benutzer nur fuer Bedrock, staan-Suchschluessel fuer Phase 2)"
auth_service:
pfad: /opt/aegis-staging-auth
service: aegis-monitor-staging-auth.service
port: 127.0.0.1:8095
cookie_domain: staging.monitor.aegis-sight.de
cookie_name: aegis_monitor_staging_auth
code_quelle: identisch zum Service auf 46.225.225.49 (eigene Konfig)
```
### Workflow Staging -> Live
Der gueltige Ablauf steht unten unter "Vollstaendiger Workflow": develop pushen,
Auto-Deploy aktualisiert Staging von selbst, auf Staging pruefen, Promote ueber
https://deploy.aegis-sight.de, Live-Check. Manuelles git pull auf dem Server ist
NICHT noetig und sollte unterbleiben (der Deploy-Listener macht git reset --hard,
lokale Aenderungen in den Server-Verzeichnissen gehen dabei verloren).
Stolperfalle bei der Warteschlangen-Pruefung: das Staging-Verzeichnis fetcht per
Fetch-Regel NUR develop. Fuer "wie viele Commits warten auf Promote" immer erst
git fetch origin main ausfuehren, sonst zeigt main..develop veraltete Zahlen.
### Bekannte Baustellen (Stand 2026-07-26)
- WebSocket-Broadcast filtert nicht nach Mandant (main.py broadcast_for_incident bekommt tenant_id, nutzt ihn nicht)
- POST /api/public/globe-ingest schreibt ohne Eigentuemer-Pruefung in beliebige Lagen, Artikel ohne tenant_id
- fact_consolidation und expire_licenses existieren, haben aber keinen Scheduler-Job
- entity_extractor/Netzwerkanalyse ohne Aufrufer in der App (nur regenerate_relations.py)
- tutorial.js deaktiviert (Einstiege auskommentiert), laedt aber weiter bei jedem Login
- dashboard.html laedt nicht existierende cluster-data.js (404) und ungenutztes d3.js vom CDN
- Reiter "Oeffentliche Stimmung" fehlt in layout.js TAB_ORDER und ist dadurch nicht anklickbar
- X-Schalter im Anlege-Dialog hat online keine Funktion (kein X-Backend online)
- i18n unvollstaendig (Studio und Login nur deutsch), Begriffs-Mix Fall/Lage/Vorfall, zweimal "Token-Budget" statt "Credits" in app.js
- Unter 768px verschwindet die Sidebar ersatzlos (mobil unbenutzbar)
- Budget-Warnung geht nur in die Glocke, nicht per E-Mail (siehe docs/ABRECHNUNG.md)
## Auto-Deploy + Promote-UI
```yaml
auto_deploy:
listener_service:
pfad: /opt/aegis-staging-deploy
service: aegis-staging-deploy.service
port: 127.0.0.1:8096
deployments:
staging: develop -> ~/AegisSight-Monitor-staging (restartet aegis-monitor-staging)
live: main -> ~/AegisSight-Monitor (restartet aegis-monitor)
endpoints:
"POST /__deploy": staging via Gitea-Webhook (HMAC)
"POST /__deploy/live": live via Promote-UI (HMAC)
secrets: /opt/aegis-staging-deploy/.env (nicht im Repo)
gitea_webhook:
repo: AegisSight/AegisSight-Monitor
url: https://staging.monitor.aegis-sight.de/__deploy
branch_filter: develop
live_systemd:
service: aegis-monitor.service
hinweis: |
Live-Monitor laeuft seit 2026-04-26 als systemd-Service (vorher loser
uvicorn-Prozess). Manueller Restart bei Backend-Aenderungen:
sudo systemctl restart aegis-monitor
Beim Promote via UI passiert das automatisch.
promote_ui:
url: https://deploy.aegis-sight.de
laeuft_auf: 46.225.225.49 (zentral fuer alle Services)
zugriff: Magic-Link-Login an info@aegis-sight.de
funktion: |
Live- vs. Staging-Stand pro Service inkl. Liste der ausstehenden Commits.
Promote-Knopf -> Gitea-PR develop->main wird auto-gemerged + Live-Listener
pullt main + restartet aegis-monitor.
```
### Vollstaendiger Workflow (Aenderung am Monitor)
1. **Entwickeln in develop**:
```bash
cd ~/AegisSight-Monitor-staging
git checkout develop
# Aenderung
git add . && git commit -m "..." && git push origin develop
# Auto-Deploy pullt automatisch + restartet aegis-monitor-staging
```
2. **Auf https://staging.monitor.aegis-sight.de pruefen**
3. **Promoten via https://deploy.aegis-sight.de** (Klick auf Monitor-Karte)
→ Gitea merged develop→main → Listener pullt main → `systemctl restart aegis-monitor`
4. **Live-Check auf https://monitor.aegis-sight.de**