# C5D — SEARCH SOURCE PIPELINE REPAIR: ARCHITECTURE_DECISION **Datum:** 2026-08-26 · **Autor:** Red Queen (MAKER) · **Status:** FREIGEGEBEN (Christian) **Mission:** fehlende deterministische Verbindung `TOLARIA VERIFIED STATE → SEARCH SOURCE BUILD/REFRESH → SEARCH REBUILD → SEARCH VERIFICATION` implementieren. --- ## 1. Root Cause (aus C5F-Evidence, übernommen & bestätigt) - `/app/index_source.json` ist **statisch baked-in** (head `35446c03…`, count=84) und wird von **keinem C4/C5-Producer** aktualisiert. - `/api/search/rebuild` konsumiert **ausschließlich** diese stale Source (`server.py` `SOURCE_JSON` hartkodiert). - C5D und C5E-Replay führen **keinen Source-Build** aus. - `verify_integrity()` akzeptiert `object_count > 0` → kann einen **semantisch stale** Search-Stand als PASS bewerten. - Produktiver Rebuild (Rain): HTTP 200 `status=ok indexed=84 blocked=0` bei Tolaria=85 / index_source=84 / Search=84 → **Canary Discovery=FAIL**. HTTP-200 allein darf NIE APPLIED ermöglichen. --- ## 2. ARCHITECTURE_DECISION ### 2.1 SOURCE_BUILDER_OWNER = **C5DEngine** Der Source-Build gehört in **C5D** (nicht in C5C, nicht in den Search-Service selbst). **Begründung (gegen die Alternativen):** | Kriterium | C5C | Search-Service (Weg A) | **C5D (GEWÄHLT)** | |---|---|---|---| | Eindeutige Ownership | ✗ C5C ist Forgejo→Tolaria; Search internes Detail | ✗ koppelt Retrieval-Layer an Tolaria | ✓ C5D orchestriert bereits den kompletten Search-Schritt | | Deterministischer Input | ✗ | ✗ | ✓ C5D läuft NUR nach Tolaria-Verifikation (DRIFT=0) | | Atomare Veröffentlichung | ✗ | ✗ | ✓ temp→validate→atomic rename→rebuild | | Recovery | ✗ | ✗ | ✓ C5D-State-Machine (idempotent) | | Split-Brain-Gefahr | hoch (2 Writer) | hoch | **minimal** (1 Owner) | | Security Boundary | ✗ | ✗ | ✓ Search-Token bleibt bei C5D | | Testbarkeit | mittel | schlecht | **hoch** (bestehendes Fake-Injektionsmuster) | | Keine neue Source of Truth | ✓ | ✓ | ✓ (ephemeral build artifact) | **Gegen C5C:** C5C-Docstring verbietet jeden Search-Bezug („Kein Search-Rebuild-Aufruf“); Source-Build würde C5C an Search-Internal koppeln und die Rollentrennung verletzen. **Gegen Search-Service selbst:** würde dem abgeleiteten Retrieval-Layer eine Tolaria-Leseabhängigkeit aufzwingen und die Ownership aufteilen. Der Service bleibt ein **dummer Konsument** einer Source-Datei (nur Pfad env-konfigurierbar). **Konsequenz:** C5D liest Tolaria **read-only** (Source-Build). Die bestehende `NO-TOLARIA-WRITE-GUARANTEE` bleibt unverändert (verbietet nur `/api/vault/save`). Statische Guards werden entsprechend erweitert. ### 2.2 SOURCE_BUILD_CONTRACT (exakt, was `rebuild_from_source` liest) Der Builder erzeugt deterministisch: ```json { "head": "", // Provenance-Anker "count": , "objects": [ { "path": "", "title": str, "id": " | null", "type": str|null, "role": str|null, "representation": str|null, "state": str|null, "knowledge_schema": str|null, "content_hash": sha256(body), "body": str, "aliases": [], "tags": [], "derived_from": str|null }, ... ] } ``` - `content_hash` = `hashlib.sha256(body)` (identisch zu `rq_c5b.content_hash`). - `knowledge_schema` wird von der Engine nicht konsumiert, ist aber Teil des bestehenden Source-Formats → wird aus Tolaria-Metadaten übernommen (kein erfundener Wert). - **Kein einziges erfundenes Feld.** Jedes Feld stammt aus dem gelesenen Tolaria-Objekt. ### 2.3 SOURCE_VERIFICATION_CONTRACT (Completeness, §5) Vor dem Rebuild wird der erzeugte Snapshot **vollständig** verifiziert (nicht nur `object_count`): - `SOURCE_OBJECT_COUNT` == `len(erwartete_indexierbare_object_ids)` - `UNIQUE_OBJECT_IDS` (keine Duplikate) - `UNIQUE_PATHS` (keine Duplikate) - `INVALID_OBJECTS` == [] (fehlendes `path`, malformed) - `SECRET_BLOCKED` fail-closed - `EXPECTED_SOURCE_HEAD` == commit_sha - **Objekt-Set-Gleichheit:** `set(object_ids) ∪ legacy_paths` == `erwartete_indexierbare_menge` **Erwartete indexierbare Menge (deterministisch, definiert):** = Menge der Tolaria-Vault-Objekte, die **IN_SCOPE** sind **UND** eine gültige `object_id` haben (via C5B `KnowledgeScope`/`extract_object_id`), **PLUS** die LEGACY_SPECIALs (`README.md` x2, `vps.md`). Objekte ohne gültige object_id (z.B. das `start.md`-Orphan, HUMAN_REVIEW) sind **nicht** indexierbar → werden ausgeschlossen. Das reproduziert deterministisch 84→85-Übergang (Canary hat gültige object_id). Bei Abweichung: **FAIL CLOSED**, kein Rebuild. ### 2.4 ATOMICITY_MODEL (§6) `build temp → validate temp → fsync → os.replace (atomic rename) → erst dann rebuild`. Ein Crash hinterlässt nie eine halb geschriebene Source: entweder alte komplette oder neue komplette Datei. Temp-Datei wird bei Restart verworfen (idempotent neu gebaut). ### 2.5 STATE_MACHINE_CHANGES (§8) — KEINE neuen States **Entscheidung:** Source-Build + -Verification laufen **atomar innerhalb des bestehenden `UPDATING_SEARCH`-Schritts**, unmittelbar vor dem Rebuild. Kein neuer `BUILDING_SEARCH_SOURCE`/`VERIFYING_SEARCH_SOURCE`-State. **Begründung:** (1) Build ist idempotent (kein Tolaria-Write → kein Doppel-Write-Risiko); (2) Restart re-entert aus `UPDATING_SEARCH`/`RETRY_PENDING` → Build wird deterministisch wiederholt; (3) Fehler werden über den bestehenden Retry/Human-Gate-Mechanismus klassifiziert. Die Invariante („kein Rebuild vor verifizierter Source“) ist gewahrt, ohne die State-Zahl kosmetisch aufzublähen. **Eine notwendige Erweiterung:** `C5DEngine.apply_commit` akzeptiert zusätzlich `ST_VERIFYING_SEARCH` als Re-Entry (Crash nach Rebuild vor Health, §9-Fall D). Verhalten: idempotenter Re-Entry → vollständige Sequenz (Build→Rebuild→Health→Verify) wird sicher wiederholt. ### 2.6 SEARCH-SERVICE-ÄNDERUNGEN (Deployment, §10) - `server.py`: `SOURCE_JSON` wird env-konfigurierbar über `TOLARIA_SEARCH_SOURCE` (Default = bisheriger Pfad). Die baked-in `index_source.json` bestimmt **nicht mehr** die produktive Wahrheit — C5D schreibt den frischen derived Snapshot an den konfigurierten Pfad. - `search_api.py` `health()`: liefert zusätzlich `indexed_object_ids` + `indexed_paths` (Read-Back für deterministische Objekt-Set-Verifikation, §7). Persistierter Index enthält ebenfalls diese Mengen. - **Keine** Netzwerköffnung, **kein** öffentliches Port-Mapping, Secrets bleiben beim Search-/C5D-Executor-Kontext. ### 2.7 C5D_VERIFICATION_HARDENING (§7) `verify_integrity` ersetzt die `object_count > 0`-Regel durch **exakte Objekt-Set-Verifikation**: - `set(indexed_object_ids) == erwartete_indexierbare_ids` - `set(indexed_paths) == erwartete_indexierbare_paths` - `object_count == len(erwartet)` - `failed_objects == []`, `secret_blocked` gemäß Contract - **Canary-Beweis:** `canary_object_id ∈ indexed_object_ids` UND `canary_path ∈ indexed_paths` (statt „HTTP 200 + count>0“). Ein HTTP-200-Rebuild mit stale Source kann damit NIE APPLIED ermöglichen. --- ## 3. Betroffene Dateien | Datei | Änderung | |---|---| | `tolaria/c5-sync-service/rq_c5d.py` | + `SearchSourceBuilder`, `verify_integrity`-Härtung, `apply_commit` mit Build+Verify+VERIFYING_SEARCH-ReEntry, Guards | | `tolaria/c5-sync-service/rq_c5e.py` | Recovery berücksichtigt Source-Build-Schritt + `VERIFYING_SEARCH`-Resume | | `tolaria/c4b-search-service/server.py` | `TOLARIA_SEARCH_SOURCE` env-konfigurierbar | | `tolaria/c4b-search-service/search_api.py` | `health()` + persistierter Index liefert `indexed_object_ids`/`indexed_paths` | | `tolaria/c5-sync-service/test_c5d.py` | erweiterte Tests (A–O) | | `tolaria/c5-sync-service/test_c5e.py` | Crash-Fälle d5 | | neu: `tolaria/c5-sync-service/test_c5d_source.py` | Realistic Integration Test (echte C4-Engine, kein Fake) | **Nicht angefasst:** Produktions-DB, Canary-DB, `index_source.json` (bleibt als historische Evidence erhalten), Vault-Git-Mods, Forgejo-Master-Write im Repair.