- SearchSourceBuilder: deterministischer Vault->Source-Snapshot (read-only), atomar (temp->validate->fsync->replace), Secret-Scan fail-closed - verify_integrity: exakte object_id/path Set-Equality (stale Source kann nie APPLIED), Canary implizit ueber erwartetes Objekt-Set - apply_commit: Source-Build+Verification vor Rebuild; VERIFYING_SEARCH-Resume (kein Doppel-Rebuild) — C5E-Replay-Crash-Fall abgedeckt - Fix: source_object_count ist keine 0-Fehlerbedingung (echter Defekt) - SearchSourceBuildError + RC_SEARCH_SOURCE_BUILD_FAILURE (Human Gate) - c4b: source_path env-konfigurierbar, indexed_object_ids/paths Read-Back - Testsuite: 17 neue Tests (Test-Plan A-O + Realistic C4-Integration)
7.9 KiB
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.jsonist statisch baked-in (head35446c03…, count=84) und wird von keinem C4/C5-Producer aktualisiert./api/search/rebuildkonsumiert ausschließlich diese stale Source (server.pySOURCE_JSONhartkodiert).- C5D und C5E-Replay führen keinen Source-Build aus.
verify_integrity()akzeptiertobject_count > 0→ kann einen semantisch stale Search-Stand als PASS bewerten.- Produktiver Rebuild (Rain): HTTP 200
status=ok indexed=84 blocked=0bei 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:
{
"head": "<commit_sha des verifizierten Commits>", // Provenance-Anker
"count": <int>,
"objects": [
{
"path": "<REQUIRED>", "title": str,
"id": "<object/UUID> | 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 zurq_c5b.content_hash).knowledge_schemawird 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== [] (fehlendespath, malformed)SECRET_BLOCKEDfail-closedEXPECTED_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_JSONwird env-konfigurierbar überTOLARIA_SEARCH_SOURCE(Default = bisheriger Pfad). Die baked-inindex_source.jsonbestimmt nicht mehr die produktive Wahrheit — C5D schreibt den frischen derived Snapshot an den konfigurierten Pfad.search_api.pyhealth(): liefert zusätzlichindexed_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_idsset(indexed_paths) == erwartete_indexierbare_pathsobject_count == len(erwartet)failed_objects == [],secret_blockedgemäß Contract- Canary-Beweis:
canary_object_id ∈ indexed_object_idsUNDcanary_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.