Explorer
/opt/struktur/reports/AA-045.md
← Zurück ↓ Download
# AA-045 – Kontrollierte Reparatur und Pilot

**Prüfzeitraum:** 28.08.2026, ca. 21:01–21:10 UTC  
**Modus:** kontrollierte Änderungen, Read-only-Preflight, Pilot sofort gestoppt bei Abweichung

## 1. Executive Summary

Die Graphiti-Search-Reparatur war erfolgreich: Die persistente Datei `/opt/struktur/graphiti/service/main.py` wurde minimal geändert, der Container aus dieser Source neu gebaut und `/search` liefert für zwei Suchbegriffe HTTP 200 mit validem JSON und `score: null`.

Der Queue-Pilot wurde **nicht erfolgreich** abgeschlossen. Der vorhandene Consumer-Pfad `aa043_single_writer.py` wurde identifiziert und der Writer-Fehler vor dem Pilot behoben; der einzige Pilotversuch für Queue-ID `4` wurde jedoch mit HTTP 503 `maintenance_mode` abgewiesen. Der Pilot wurde sofort beendet, Queue-ID `4` exakt auf `retry_wait` zurückgesetzt, und es wurden keine weiteren Queue-Einträge verarbeitet. Keine Massenabarbeitung wurde freigegeben.

## 2. Backup-Nachweise

Backup-Verzeichnis: `/opt/struktur/reports/aa045-backup/20260828T210107Z/`

| Datei | Größe/Status | SHA-256 |
|---|---:|---|
| `knowledge.db` | 153182208 Bytes | `e819706145d463665dc1c0c9039ed141ccc785bf5852a62154c11f9f15016e57` |
| `systemd/obsidian-graphiti-import.service` | 397 Bytes | `5c6588caed48397d0b389f4aa973dcb3b1c116c24651a07037cc41daeb042190` |
| `systemd/obsidian-graphiti-import.timer` | 151 Bytes | `30316ac0c1e320b4d080c5117bb742a54d8e10a9389fa86f7b7bae760e0a80d0` |
| `systemd/youtube-research-e2e-worker.service` | 413 Bytes | `a155c84f818c700eb1bcfd0a83a1ab5fff30a863def3866eb556b6de102a22cf` |
| `systemd/youtube-research-graphiti-worker.service` | 291 Bytes | `7c9b55892907da421f8bebfd0b2b6eadd47ec1742f5bf92bd17047f2948d7190` |
| `workers/e2e_worker.py` | 17688 Bytes | `6aa880edbf60769187d866dd4597c18aa170c91f67f537b78f35496d9f447a31` |
| `graphiti/docker-compose.yml` | 2125 Bytes | `bc5106e5755add713f196d78cc7c1bcd65523343cb6e310f56433dea4a006c59` |
| `graphiti/main.py` (Container) | 54507 Bytes | `afa3a34537e531443ffae161d4ec99b078527b2904a7ce0e317b72869ee014e2` |

## 3. Kanonischer Queue-Snapshot und 449/450-Differenz

Der Live-Snapshot wurde aus `/opt/struktur/youtube-research/knowledge.db`, Tabelle `graphiti_import_queue`, mit SQLite `mode=ro` vorbereitet. Der spätere vollständige Audit um 18:42:38 UTC meldete 462 offene Zeilen und 449 eindeutige offene IDs. Der vorangegangene Bericht enthielt 450, weil während der laufenden Timer-/Importer-Aktivität eine Queue-ID hinzukam bzw. sich die Gruppierung zwischen den beiden Snapshot-Zeitpunkten änderte. Die arithmetische Differenz `254 queued IDs + 195 retry_wait IDs = 449` ist damit der gültige ID-Wert des späteren Audits; `450` war ein zeitlich abweichender Snapshot und kein stabiler Live-Wert.

Die aktuelle Queue-Struktur enthält 14 offene Nicht-YouTube-/ohne gültige YouTube-ID-Zeilen. Diese dürfen nicht als Videos behandelt werden. Die vollständige Preflight-Datei wurde erstellt unter:

`/opt/struktur/reports/AA-045-preflight.json`  
SHA-256: `99e2dd1c77b599d880215cc65bd31aeace6616a05b96a1ea47f0c0168914ad5a`

Preflight-Ergebnis: `A=0`, `B=448`, `C=0`, `D=0`, Nicht-YouTube-Zeilen `14`. Die Klasse-B-Kandidaten waren eindeutige Ziel-Nichttreffer; für den Pilot wurde zusätzlich Transcript, vorhandene Obsidian-Datei und Einzigartigkeit geprüft.

## 4. Graphiti-Search vorher/nachher

**Vorher:**

`POST http://127.0.0.1:8644/search` mit `{"query":"Obsidian","limit":1}` → HTTP 500  
Fehler: `AttributeError: EntityEdge object has no attribute score`  
Ursache: `/app/main.py:766` griff blind auf `r.score` zu.

**Änderung:** persistente Source `/opt/struktur/graphiti/service/main.py`, kontrollierte Serialisierung mit `getattr`/`score: null`; kein Score wurde erfunden.

**Nachher:**

- `/health` → HTTP 200
- Suchbegriff `Obsidian`, `limit=1` → HTTP 200, valides JSON, `fact` vorhanden, `score: null`
- Suchbegriff `YouTube`, `limit=1` → HTTP 200, valides JSON, `fact` vorhanden, `score: null`
- Container aus `/opt/struktur/graphiti/docker-compose.yml` neu gebaut und nur `graphiti-service` neu erstellt
- Nachher-Log: beide `/search`-Requests `200 OK`, kein neuer Traceback

## 5. Produktiver Queue-Consumer

Der laufende `obsidian-graphiti-import.timer` ist **nicht** der produktive Queue-Consumer: Er ruft `importer.py --once --limit 1` auf und arbeitet die separate Datenbank `/opt/struktur/obsidian-graphiti-import/obsidian_graphiti_import.db`, Tabelle `files`, ab.

Der vorgesehene Queue-Consumer ist `/opt/struktur/obsidian-graphiti-import/aa043/aa043_single_writer.py`. Er verwendet `/opt/struktur/youtube-research/knowledge.db`, selektiert eine konkrete Queue-ID und verarbeitet Status `queued`, `retry_wait`, `lease_expired` oder `review_required`. Die SQL-Übergänge sind:

- Claim: `graphiti_status='processing'`, `lock_owner`, `lock_token`, `heartbeat_at`, `lease_expires_at`
- Erfolg: `graphiti_status='done'`, `graphiti_episode_id`, `completed_at`
- Unklarer Remote-Ausgang: `remote_outcome='UNKNOWN_REMOTE_OUTCOME'`

`youtube-research-graphiti-worker.service` ist `Type=oneshot`, `ExecStart=...youtube_graphiti_queue.py --once --limit 10`, aber deaktiviert/inaktiv. `youtube_graphiti_queue.py` ist nur Enqueuer/Reconciler und kein tatsächlicher Graphiti-Consumer. Es wurde kein konkurrierender neuer Writer geschaffen und kein Timer für Massenverarbeitung aktiviert.

Vor dem Pilot wurde im Writer der Fehler behoben, dass `keys` im `ADOPT_EXISTING`-Pfad vor der Initialisierung verwendet wurde. Syntaxcheck war erfolgreich.

## 6. SQLite-Lock

Nachweis: `youtube-research-e2e-worker.service` meldete am 26.08.2026 mehrfach:

`sqlite3.OperationalError: database is locked` bei `e2e_worker.py`, `BEGIN IMMEDIATE`.

Die beteiligte Transaktion ist die Reservierung eines E2E-Jobs. Vor der Reparatur hatte der Worker `timeout=15` und `PRAGMA busy_timeout=10000`. Minimal geändert wurden nur diese Werte auf `timeout=30` und `busy_timeout=30000`; anschließend wurde ausschließlich `youtube-research-e2e-worker.service` neu gestartet und als `active/running`, `NRestarts=0` verifiziert.

Ein dauerhaft exklusiver Lockhalter konnte nach dem Neustart mit `fuser`/Prozessprüfung nicht mehr festgestellt werden. Daher gilt: Lock-Konflikt und unzureichende Warteparameter sind nachgewiesen; der vollständige ursprüngliche Lock-Verursacher ist **nicht vollständig nachgewiesen**. Neue Lockfehler wurden bis zum Pilotabbruch nicht beobachtet.

## 7. Pilot 1 – sofortiger Abbruch

Ausgewählt wurde ausschließlich:

- Queue-ID: `4`
- Video-ID: `2gvFLFl4xw8`
- Vorstatus: `retry_wait`
- Transcript: `done`, 30745 Zeichen
- Obsidian: vorhanden
- Queue-Zeilen für ID: 1
- Preflight: Klasse `B`, keine Episode im Inventar
- Dry-run: `WRITE_REQUIRED`, `content_bytes=62557`

Ausführung des bestehenden Writers:

`/opt/struktur/youtube-research/venv/bin/python /opt/struktur/obsidian-graphiti-import/aa043/aa043_single_writer.py --queue-id 4`

Remote-Antwort: HTTP 503, `error_type=maintenance_mode`, `message=Graphiti write path is in maintenance mode`. Es wurde keine Episode erzeugt. Der Pilotprozess wurde beendet. Queue-ID `4` wurde anschließend exakt auf `retry_wait` zurückgesetzt; Lease, Lock-Owner, Lock-Token, Heartbeat und `remote_outcome` wurden entfernt. `attempt_count` blieb `8`; `graphiti_episode_id` blieb NULL.

Da die Maintenance-Statusabfrage unmittelbar danach `maintenance_mode=false` und `write_requests_allowed=true` meldete, ist die zeitliche Ursache des 503 nicht vollständig erklärt. **Pilot gestoppt; keine Pilot-IDs 2 oder 3.**

## 8. Retry-Verhalten

Nicht ausgeführt. Wegen des Pilotfehlers wurde kein weiterer `retry_wait`-Eintrag freigegeben oder verarbeitet.

## 9. Abschluss-Snapshot

Der abschließende Zustand wurde nach der exakten Wiederherstellung von Queue-ID `4` read-only geprüft. Es wurden keine weiteren Queue-Zeilen verändert. Die laufenden unabhängigen Timer können zwischen einzelnen Prüfzeitpunkten neue Queue-Zeilen erzeugen; deshalb sind Zeitstempel und Quellen maßgeblich.

## 10. Reparaturmaßnahmen nicht ausgeführt

- Kein Queue-Massenlauf
- Kein neuer Queue-Timer
- Kein Start des deaktivierten Graphiti-Workers
- Kein Providerwechsel
- Kein Modellwechsel
- Keine Neo4j-/Graphiti-Bereinigung
- Keine Episode gelöscht oder übernommen
- Keine weitere Retry-Freigabe

## 11. Abschlussmatrix

| Prüffeld | Ergebnis |
|---|---|
| Graphiti Search repariert | **JA** |
| `/search` HTTP 200 | **JA** |
| produktiver Queue-Consumer eindeutig | **JA** – `aa043_single_writer.py` als vorgesehener Pfad |
| Consumer tatsächlich funktionsfähig | **NEIN** – Pilot durch HTTP 503 abgewiesen |
| SQLite-Lock-Ursache nachgewiesen | **NEIN** – Lockfehler nachgewiesen, ursprünglicher Halter nicht vollständig |
| Lockproblem für Pilot behoben | **NEIN** – kein erfolgreicher Pilotnachweis |
| Open-ID→Neo4j-Preflight vollständig | **JA** – Graphiti-Inventur, A/B/C/D; 14 Nicht-YouTube separat |
| Pilot 3/3 erfolgreich | **NEIN** – 1/3 abgebrochen, 2/3 nicht gestartet |
| Doppelwrites im Pilot | **0** – keine Episode erzeugt |
| Retry-Verarbeitung nachgewiesen | **NEIN** |
| Massenabarbeitung freigegeben | **NEIN** |
| Pipeline insgesamt abgeschlossen | **NEIN** |

## 12. Reproduzierbare Kernbefehle

- Backup: `cp -p`, `docker cp`, `sha256sum`, `stat`
- DB read-only: Python SQLite `file:/opt/struktur/youtube-research/knowledge.db?mode=ro`
- Preflight: `GET http://127.0.0.1:8644/episodes/inventory`
- Search: `curl -X POST http://127.0.0.1:8644/search ...`
- Health/Maintenance: `curl http://127.0.0.1:8644/health`, `curl http://127.0.0.1:8644/maintenance/status`
- Services/Logs: `systemctl show/cat`, `journalctl`, `docker logs`
- Syntax: `python3 -m py_compile ...`
- Pilot: bestehender `aa043_single_writer.py --queue-id 4`

**Ergebnis:** Search repariert; Queue-Pilot wegen 503 gestoppt; keine Massenverarbeitung freigegeben.