feat(reparenting): supersede di un record ri-parenta automaticamente i figli attivi

- gateway: nel blocco supersede, i figli attivi (parent_id == vecchio UUID,
  superseded_by vuoto) vengono ri-parentati al nuovo UUID; audit action
  'reparent'; risposta con campo 'reparented'. Un solo livello: i nipoti
  puntano agli UUID dei figli, invariati. I figli superseduti restano
  storici ancorati alla vecchia lineage.
- estensione: qmem_tree con fallback lineage (cerca figli anche per
  supersedes_id del root) per gli orfani pre-fix; qmem_correct riporta
  il numero di figli ri-parentati.
- test: 2 nuovi (ri-parenta figli attivi; ri-parenta solo attivi con
  figlio già superseduto). 34 pass.
- deploy su brain (10.8.0.3): main.py aggiornato, container ricreato,
  smoke test end-to-end OK (reparented=1).
This commit is contained in:
Matteo Benedetto
2026-08-24 15:43:33 +02:00
parent 0283d3964e
commit e68fca3388
4 changed files with 160 additions and 14 deletions
+18 -2
View File
@@ -70,8 +70,24 @@ class FakeQdrant:
for pid in points_selector:
self.points.pop(pid, None)
def scroll(self, collection_name, **kw):
return list(self.points.values()), None
def _matches(self, pl, query_filter):
"""Applica i filtri metadata (FieldCondition match / IsEmptyCondition)."""
if not query_filter or not query_filter.must:
return True
for cond in query_filter.must:
if hasattr(cond, "key") and hasattr(cond, "match"):
if pl.get(cond.key) != cond.match.value:
return False
elif hasattr(cond, "is_empty"):
if pl.get(cond.is_empty.key):
return False
return True
def scroll(self, collection_name, scroll_filter=None, limit=None, with_payload=True, **kw):
points = [p for p in self.points.values() if self._matches(p.payload, scroll_filter)]
if limit:
points = points[:limit]
return points, None
def get_collection(self, collection_name):
class _Info: