WP24c - Agentic Edge Validation & Chunk-Aware Multigraph-System (v4.5.8) #22
|
|
@ -2,7 +2,7 @@
|
||||||
FILE: app/core/database/qdrant_points.py
|
FILE: app/core/database/qdrant_points.py
|
||||||
DESCRIPTION: Object-Mapper für Qdrant. Konvertiert JSON-Payloads (Notes, Chunks, Edges)
|
DESCRIPTION: Object-Mapper für Qdrant. Konvertiert JSON-Payloads (Notes, Chunks, Edges)
|
||||||
in PointStructs und generiert deterministische UUIDs.
|
in PointStructs und generiert deterministische UUIDs.
|
||||||
VERSION: 4.0.0 (WP-24c: Gold-Standard Identity - 4-Parameter-ID)
|
VERSION: 4.1.0 (WP-24c: Gold-Standard Identity v4.1.0 - target_section Support)
|
||||||
STATUS: Active
|
STATUS: Active
|
||||||
DEPENDENCIES: qdrant_client, uuid, os, app.core.graph.graph_utils
|
DEPENDENCIES: qdrant_client, uuid, os, app.core.graph.graph_utils
|
||||||
LAST_ANALYSIS: 2026-01-10
|
LAST_ANALYSIS: 2026-01-10
|
||||||
|
|
@ -95,11 +95,12 @@ def _normalize_edge_payload(pl: dict) -> dict:
|
||||||
def points_for_edges(prefix: str, edge_payloads: List[dict]) -> Tuple[str, List[rest.PointStruct]]:
|
def points_for_edges(prefix: str, edge_payloads: List[dict]) -> Tuple[str, List[rest.PointStruct]]:
|
||||||
"""
|
"""
|
||||||
Konvertiert Kanten-Payloads in PointStructs.
|
Konvertiert Kanten-Payloads in PointStructs.
|
||||||
WP-24c v4.0.0: Nutzt die zentrale _mk_edge_id Funktion aus graph_utils.
|
WP-24c v4.1.0: Nutzt die zentrale _mk_edge_id Funktion aus graph_utils.
|
||||||
Dies eliminiert den ID-Drift zwischen manuellen und virtuellen Kanten.
|
Dies eliminiert den ID-Drift zwischen manuellen und virtuellen Kanten.
|
||||||
|
|
||||||
GOLD-STANDARD v4.0.0: Die ID-Generierung verwendet STRICT nur die 4 Parameter
|
GOLD-STANDARD v4.1.0: Die ID-Generierung verwendet 4 Parameter + optional target_section
|
||||||
(kind, source_id, target_id, scope). rule_id und variant werden ignoriert.
|
(kind, source_id, target_id, scope, target_section).
|
||||||
|
rule_id und variant werden ignoriert, target_section fließt ein (Multigraph-Support).
|
||||||
"""
|
"""
|
||||||
_, _, edges_col = _names(prefix)
|
_, _, edges_col = _names(prefix)
|
||||||
points: List[rest.PointStruct] = []
|
points: List[rest.PointStruct] = []
|
||||||
|
|
@ -107,23 +108,26 @@ def points_for_edges(prefix: str, edge_payloads: List[dict]) -> Tuple[str, List[
|
||||||
for raw in edge_payloads:
|
for raw in edge_payloads:
|
||||||
pl = _normalize_edge_payload(raw)
|
pl = _normalize_edge_payload(raw)
|
||||||
|
|
||||||
# Extraktion der Identitäts-Parameter (GOLD-STANDARD v4.0.0: nur 4 Parameter)
|
# Extraktion der Identitäts-Parameter (GOLD-STANDARD v4.1.0)
|
||||||
kind = pl.get("kind", "edge")
|
kind = pl.get("kind", "edge")
|
||||||
s = pl.get("source_id", "unknown-src")
|
s = pl.get("source_id", "unknown-src")
|
||||||
t = pl.get("target_id", "unknown-tgt")
|
t = pl.get("target_id", "unknown-tgt")
|
||||||
scope = pl.get("scope", "note")
|
scope = pl.get("scope", "note")
|
||||||
|
target_section = pl.get("target_section") # WP-24c v4.1.0: target_section für Section-Links
|
||||||
|
|
||||||
# Hinweis: rule_id und variant werden im Payload gespeichert,
|
# Hinweis: rule_id und variant werden im Payload gespeichert,
|
||||||
# fließen aber NICHT in die ID-Generierung ein (v4.0.0 Standard)
|
# fließen aber NICHT in die ID-Generierung ein (v4.0.0 Standard)
|
||||||
|
# target_section fließt in die ID ein (v4.1.0: Multigraph-Support für Section-Links)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
# Aufruf der Single-Source-of-Truth für IDs
|
# Aufruf der Single-Source-of-Truth für IDs
|
||||||
# GOLD-STANDARD v4.0.0: Nur 4 Parameter werden verwendet
|
# GOLD-STANDARD v4.1.0: 4 Parameter + optional target_section
|
||||||
point_id = _mk_edge_id(
|
point_id = _mk_edge_id(
|
||||||
kind=kind,
|
kind=kind,
|
||||||
s=s,
|
s=s,
|
||||||
t=t,
|
t=t,
|
||||||
scope=scope
|
scope=scope,
|
||||||
|
target_section=target_section
|
||||||
)
|
)
|
||||||
|
|
||||||
# Synchronisierung des Payloads mit der berechneten ID
|
# Synchronisierung des Payloads mit der berechneten ID
|
||||||
|
|
|
||||||
|
|
@ -4,12 +4,13 @@ DESCRIPTION: Der zentrale IngestionService (Orchestrator).
|
||||||
WP-25a: Integration der Mixture of Experts (MoE) Architektur.
|
WP-25a: Integration der Mixture of Experts (MoE) Architektur.
|
||||||
WP-15b: Two-Pass Workflow mit globalem Kontext-Cache.
|
WP-15b: Two-Pass Workflow mit globalem Kontext-Cache.
|
||||||
WP-20/22: Cloud-Resilienz und Content-Lifecycle integriert.
|
WP-20/22: Cloud-Resilienz und Content-Lifecycle integriert.
|
||||||
AUDIT v4.0.0:
|
AUDIT v4.1.0:
|
||||||
- GOLD-STANDARD v4.0.0: Phase 2 verwendet exakt dieselbe 4-Parameter-ID wie Phase 1.
|
- GOLD-STANDARD v4.1.0: Symmetrie-Integrität korrigiert (note_id, source_id, kind, target_section).
|
||||||
|
- Phase 2 verwendet exakt dieselbe ID-Generierung wie Phase 1 (inkl. target_section).
|
||||||
- Authority-Check in Phase 2 prüft mit konsistenter ID-Generierung.
|
- Authority-Check in Phase 2 prüft mit konsistenter ID-Generierung.
|
||||||
- Eliminiert Duplikate durch inkonsistente ID-Generierung (Steinzeitaxt-Problem).
|
- Eliminiert Duplikate durch inkonsistente ID-Generierung (Steinzeitaxt-Problem).
|
||||||
- Beibehaltung der strikten 2-Phasen-Strategie (Authority-First).
|
- Beibehaltung der strikten 2-Phasen-Strategie (Authority-First).
|
||||||
VERSION: 4.0.0 (WP-24c: Gold-Standard Identity)
|
VERSION: 4.1.0 (WP-24c: Gold-Standard Identity v4.1.0)
|
||||||
STATUS: Active
|
STATUS: Active
|
||||||
"""
|
"""
|
||||||
import logging
|
import logging
|
||||||
|
|
@ -146,21 +147,31 @@ class IngestionService:
|
||||||
logger.info(f"🔄 PHASE 2: Validiere {len(self.symmetry_buffer)} Symmetrien gegen Live-DB...")
|
logger.info(f"🔄 PHASE 2: Validiere {len(self.symmetry_buffer)} Symmetrien gegen Live-DB...")
|
||||||
final_virtuals = []
|
final_virtuals = []
|
||||||
for v_edge in self.symmetry_buffer:
|
for v_edge in self.symmetry_buffer:
|
||||||
src, tgt, kind = v_edge.get("note_id"), v_edge.get("target_id"), v_edge.get("kind")
|
# WP-24c v4.1.0: Korrekte Extraktion der Identitäts-Parameter
|
||||||
if not src or not tgt: continue
|
src = v_edge.get("source_id") or v_edge.get("note_id") # source_id hat Priorität
|
||||||
|
tgt = v_edge.get("target_id")
|
||||||
|
kind = v_edge.get("kind")
|
||||||
|
scope = v_edge.get("scope", "note")
|
||||||
|
target_section = v_edge.get("target_section") # WP-24c v4.1.0: target_section berücksichtigen
|
||||||
|
|
||||||
|
if not all([src, tgt, kind]):
|
||||||
|
continue
|
||||||
|
|
||||||
# WP-24c v4.0.0: Nutzung der zentralisierten ID-Logik aus graph_utils
|
# WP-24c v4.1.0: Nutzung der zentralisierten ID-Logik aus graph_utils
|
||||||
# GOLD-STANDARD: Exakt 4 Parameter (kind, source, target, scope)
|
# GOLD-STANDARD v4.1.0: ID-Generierung muss absolut synchron zu Phase 1 sein
|
||||||
|
# - Wenn target_section vorhanden, muss es in die ID einfließen
|
||||||
|
# - Dies stellt sicher, dass der Authority-Check korrekt funktioniert
|
||||||
try:
|
try:
|
||||||
v_id = _mk_edge_id(kind, src, tgt, "note")
|
v_id = _mk_edge_id(kind, src, tgt, scope, target_section=target_section)
|
||||||
except ValueError:
|
except ValueError:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
# AUTHORITY-CHECK: Nur schreiben, wenn keine manuelle Kante existiert
|
# AUTHORITY-CHECK: Nur schreiben, wenn keine manuelle Kante existiert
|
||||||
# Prüft mit exakt derselben 4-Parameter-ID, die in Phase 1 verwendet wurde
|
# Prüft mit exakt derselben ID, die in Phase 1 verwendet wurde (inkl. target_section)
|
||||||
if not is_explicit_edge_present(self.client, self.prefix, v_id):
|
if not is_explicit_edge_present(self.client, self.prefix, v_id):
|
||||||
final_virtuals.append(v_edge)
|
final_virtuals.append(v_edge)
|
||||||
logger.info(f" 🔄 [SYMMETRY] Add inverse: {src} --({kind})--> {tgt}")
|
section_info = f" (section: {target_section})" if target_section else ""
|
||||||
|
logger.info(f" 🔄 [SYMMETRY] Add inverse: {src} --({kind})--> {tgt}{section_info}")
|
||||||
else:
|
else:
|
||||||
logger.info(f" 🛡️ [PROTECTED] Manuelle Kante gefunden. Symmetrie für {kind} unterdrückt.")
|
logger.info(f" 🛡️ [PROTECTED] Manuelle Kante gefunden. Symmetrie für {kind} unterdrückt.")
|
||||||
|
|
||||||
|
|
@ -245,14 +256,41 @@ class IngestionService:
|
||||||
if not self._is_valid_id(t_id): continue
|
if not self._is_valid_id(t_id): continue
|
||||||
|
|
||||||
resolved_kind = edge_registry.resolve(e.get("kind", "related_to"), provenance="explicit")
|
resolved_kind = edge_registry.resolve(e.get("kind", "related_to"), provenance="explicit")
|
||||||
e.update({"kind": resolved_kind, "target_id": t_id, "origin_note_id": note_id, "virtual": False})
|
# WP-24c v4.1.0: target_section aus dem Edge-Payload extrahieren und beibehalten
|
||||||
|
target_section = e.get("target_section")
|
||||||
|
e.update({
|
||||||
|
"kind": resolved_kind,
|
||||||
|
"relation": resolved_kind, # Konsistenz: kind und relation identisch
|
||||||
|
"target_id": t_id,
|
||||||
|
"source_id": e.get("source_id") or note_id, # Sicherstellen, dass source_id gesetzt ist
|
||||||
|
"origin_note_id": note_id,
|
||||||
|
"virtual": False
|
||||||
|
})
|
||||||
explicit_edges.append(e)
|
explicit_edges.append(e)
|
||||||
|
|
||||||
# Symmetrie puffern
|
# Symmetrie puffern (WP-24c v4.1.0: Korrekte Symmetrie-Integrität)
|
||||||
inv_kind = edge_registry.get_inverse(resolved_kind)
|
inv_kind = edge_registry.get_inverse(resolved_kind)
|
||||||
if inv_kind and t_id != note_id:
|
if inv_kind and t_id != note_id:
|
||||||
v_edge = e.copy()
|
# GOLD-STANDARD v4.1.0: Symmetrie-Integrität
|
||||||
v_edge.update({"note_id": t_id, "target_id": note_id, "kind": inv_kind, "virtual": True, "origin_note_id": note_id})
|
# - note_id: Besitzer-Wechsel zum Link-Ziel
|
||||||
|
# - source_id: Neue Quelle (Note-ID des Link-Ziels)
|
||||||
|
# - target_id: Ursprüngliche Quelle (note_id)
|
||||||
|
# - kind/relation: Invers setzen
|
||||||
|
# - target_section: Beibehalten (falls vorhanden)
|
||||||
|
# - scope: Immer "note" für Symmetrien (Note-Level Backbone)
|
||||||
|
v_edge = {
|
||||||
|
"note_id": t_id, # Besitzer-Wechsel: Symmetrie gehört zum Link-Ziel
|
||||||
|
"source_id": t_id, # Neue Quelle ist das Link-Ziel
|
||||||
|
"target_id": note_id, # Ziel ist die ursprüngliche Quelle
|
||||||
|
"kind": inv_kind, # Inverser Kanten-Typ
|
||||||
|
"relation": inv_kind, # Konsistenz: kind und relation identisch
|
||||||
|
"scope": "note", # Symmetrien sind immer Note-Level
|
||||||
|
"virtual": True,
|
||||||
|
"origin_note_id": note_id, # Tracking: Woher kommt die Symmetrie
|
||||||
|
}
|
||||||
|
# target_section beibehalten, falls vorhanden (für Section-Links)
|
||||||
|
if target_section:
|
||||||
|
v_edge["target_section"] = target_section
|
||||||
self.symmetry_buffer.append(v_edge)
|
self.symmetry_buffer.append(v_edge)
|
||||||
|
|
||||||
# DB Upsert
|
# DB Upsert
|
||||||
|
|
|
||||||
|
|
@ -2,9 +2,9 @@
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
"""
|
"""
|
||||||
FILE: scripts/import_markdown.py
|
FILE: scripts/import_markdown.py
|
||||||
VERSION: 2.6.1 (2026-01-10)
|
VERSION: 2.6.2 (WP-24c: Gold-Standard v4.1.0)
|
||||||
STATUS: Active (Core)
|
STATUS: Active (Core)
|
||||||
COMPATIBILITY: IngestionProcessor v3.4.2+, graph_utils v1.6.2+
|
COMPATIBILITY: IngestionProcessor v4.0.0+, graph_utils v4.1.0+
|
||||||
|
|
||||||
Zweck:
|
Zweck:
|
||||||
-------
|
-------
|
||||||
|
|
@ -108,7 +108,19 @@ async def main_async(args):
|
||||||
# Diese Liste stellt sicher, dass keine System-Leichen oder temporäre Dateien
|
# Diese Liste stellt sicher, dass keine System-Leichen oder temporäre Dateien
|
||||||
# den Graphen korrumpieren oder zu ID-Kollisionen führen.
|
# den Graphen korrumpieren oder zu ID-Kollisionen führen.
|
||||||
files = []
|
files = []
|
||||||
ignore_list = [".trash", ".obsidian", ".sync", "templates", "_system", ".git"]
|
|
||||||
|
# WP-24c v4.1.0: MINDNET_IGNORE_FOLDERS aus Umgebungsvariable
|
||||||
|
# Format: Komma-separierte Liste von Ordnernamen (z.B. "trash,temp,archive")
|
||||||
|
env_ignore = os.getenv("MINDNET_IGNORE_FOLDERS", "")
|
||||||
|
env_ignore_list = [f.strip() for f in env_ignore.split(",") if f.strip()] if env_ignore else []
|
||||||
|
|
||||||
|
# Standard-Ignore-Liste (System-Ordner)
|
||||||
|
default_ignore_list = [".trash", ".obsidian", ".sync", "templates", "_system", ".git"]
|
||||||
|
|
||||||
|
# Kombinierte Ignore-Liste (Umgebungsvariable hat Priorität, wird mit Defaults kombiniert)
|
||||||
|
ignore_list = list(set(default_ignore_list + env_ignore_list))
|
||||||
|
|
||||||
|
logger.info(f"📁 Ignore-Liste: {ignore_list}")
|
||||||
|
|
||||||
for f in all_files_raw:
|
for f in all_files_raw:
|
||||||
f_str = str(f)
|
f_str = str(f)
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user