Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,14 @@
# Changelog

## [3.2.1] - 2026-08-30

### 🧹 Cleanup des ontologies S3 orphelines

- **`storage_check`** ne considère plus automatiquement tous les objets contenant `_ontology_` comme légitimes : seules les ontologies encore référencées par une mémoire existante sont protégées.
- **`storage_cleanup`** peut désormais détecter et supprimer les ontologies laissées sur S3 après la suppression d'une mémoire, y compris les copies obsolètes d'une ontologie.
- **Compatibilité legacy** : les mémoires anciennes sans `ontology_uri` restent protégées par une correspondance stricte sur leur identifiant et leur nom d'ontologie.
- Correctif de l'issue [#31](https://github.com/Cloud-Temple/graph-memory/issues/31), validé sur Docker local : 5 ontologies orphelines détectées puis supprimées, second contrôle à 0 orphelin.

## [3.2.0] - 2026-06-04

### 🧭 `source_path` exposé dans la recherche Graph-first
Expand Down
2 changes: 1 addition & 1 deletion Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ FROM python:3.11.12-slim
# Métadonnées
LABEL maintainer="Cloud Temple"
LABEL description="MCP Memory Service - Knowledge Graph Memory for AI Agents"
LABEL version="3.2.0"
LABEL version="3.2.1"

# Variables d'environnement Python
ENV PYTHONDONTWRITEBYTECODE=1
Expand Down
4 changes: 2 additions & 2 deletions README.en.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,9 @@ Built by **[Cloud Temple](https://www.cloud-temple.com)**.

## 📋 Changelog

See **[CHANGELOG.md](CHANGELOG.md)** for the full version history (v0.5.0 → v3.2.0).
See **[CHANGELOG.md](CHANGELOG.md)** for the full version history (v0.5.0 → v3.2.1).

**Latest**: v3.2.0 (June 4, 2026) — `source_path` exposed in Graph-first search: `memory_search` and `memory_query` now return the canonical source path (`source_path`) and a derived `repo_path` for every document/chunk, letting an agent open the Git file immediately without a full `document_list`. Enriched via a retroactive graph join (no re-ingestion). `document_get`/`document_list` tools aligned. Previously: v3.1.1 (`/admin` "⚡ Ingest Jobs" console).
**Latest**: v3.2.1 (August 30, 2026) — `storage_check` and `storage_cleanup` now detect S3 ontology objects orphaned after memory deletion, while preserving ontologies still referenced by existing memories and legacy memories without an `ontology_uri`. Fixes [#31](https://github.com/Cloud-Temple/graph-memory/issues/31). Previously: v3.2.0 (`source_path` exposed in Graph-first search).

---

Expand Down
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -66,9 +66,9 @@ Développé par **[Cloud Temple](https://www.cloud-temple.com)**.

## 📋 Changelog

Voir **[CHANGELOG.md](CHANGELOG.md)** pour l'historique complet des versions (v0.5.0 → v3.2.0).
Voir **[CHANGELOG.md](CHANGELOG.md)** pour l'historique complet des versions (v0.5.0 → v3.2.1).

**Dernière version** : v3.2.0 (4 juin 2026) — `source_path` exposé dans la recherche Graph-first : `memory_search` et `memory_query` renvoient désormais le chemin source canonique (`source_path`) et un `repo_path` dérivé pour chaque document/chunk, permettant à un agent d'ouvrir immédiatement le fichier Git sans `document_list` complet. Enrichissement par jointure graphe rétroactive (aucune ré-ingestion). Outils `document_get`/`document_list` alignés. Précédemment : v3.1.1 (console `/admin` « ⚡ Ingest Jobs »).
**Dernière version** : v3.2.1 (30 août 2026) — `storage_check` et `storage_cleanup` détectent désormais les ontologies S3 devenues orphelines après la suppression d'une mémoire, tout en protégeant les ontologies encore référencées et les mémoires legacy sans `ontology_uri`. Correctif de l'issue [#31](https://github.com/Cloud-Temple/graph-memory/issues/31). Précédemment : v3.2.0 (`source_path` exposé dans la recherche Graph-first).

---

Expand Down Expand Up @@ -932,4 +932,4 @@ Développé par **[Cloud Temple](https://www.cloud-temple.com)**.

---

*Graph Memory v3.2.0Juin 2026*
*Graph Memory v3.2.1Août 2026*
2 changes: 1 addition & 1 deletion VERSION
Original file line number Diff line number Diff line change
@@ -1 +1 @@
3.2.0
3.2.1
2 changes: 1 addition & 1 deletion src/mcp_memory/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,5 +15,5 @@
python -m src.mcp_memory.server --port 8002
"""

__version__ = "3.2.0"
__version__ = "3.2.1"
__author__ = "Cloud Temple"
22 changes: 16 additions & 6 deletions src/mcp_memory/server.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
from .auth.middleware import AuthMiddleware, LoggingMiddleware, StaticFilesMiddleware
from .auth.context import check_memory_access, check_write_permission, check_admin_permission, get_allowed_memory_ids, current_auth
from .core.validators import validate_memory_id, validate_filename, validate_document_size, validate_entity_name, validate_backup_id as validate_backup_id_format, check_bootstrap_key_safety
from .storage_consistency import collect_referenced_ontology_keys, is_referenced_ontology_key


# =============================================================================
Expand Down Expand Up @@ -2226,13 +2227,21 @@ async def storage_check(
return admin_err

# 1. Récupérer les mémoires à vérifier
all_memories = await get_graph().list_memories()
if memory_id:
memory = await get_graph().get_memory(memory_id)
memory = next((mem for mem in all_memories if mem.id == memory_id), None)
if not memory:
return {"status": "error", "message": f"Mémoire '{memory_id}' non trouvée"}
memories = [memory]
else:
memories = await get_graph().list_memories()
memories = all_memories

# Les ontologies ne sont légitimes que si une mémoire existante les
# référence. Les mémoires legacy sans ontology_uri sont protégées par
# un fallback strict sur leur préfixe et leur nom d'ontologie.
referenced_ontology_keys, legacy_ontology_patterns = collect_referenced_ontology_keys(
all_memories, get_storage()._parse_key
)

# 2. Collecter toutes les URIs des documents référencés dans le graphe
graph_uris = set() # URIs référencées dans Neo4j
Expand Down Expand Up @@ -2275,7 +2284,6 @@ async def storage_check(
all_graph_uris = set(graph_uris) # Commencer avec celles du scope
if memory_id:
# Charger les URIs des autres mémoires aussi
all_memories = await get_graph().list_memories()
for mem in all_memories:
if mem.id == memory_id:
continue # Déjà chargé
Expand Down Expand Up @@ -2310,9 +2318,11 @@ async def storage_check(
if key.startswith("_backups/"):
continue

# Ignorer les ontologies (fichiers légitimes)
# Le pattern est {hash[:8]}__ontology_{name}.yaml (double _ car hash + _ontology)
if "_ontology_" in key:
# Ignorer uniquement les ontologies encore référencées par une
# mémoire existante. Une ontologie de mémoire supprimée est orpheline.
if is_referenced_ontology_key(
key, referenced_ontology_keys, legacy_ontology_patterns
):
continue

# Si la clé n'est pas référencée dans le graphe → orphelin
Expand Down
6 changes: 3 additions & 3 deletions src/mcp_memory/static/admin.html
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Cloud Temple — Graph Memory Admin</title>
<link rel="stylesheet" href="/static/css/admin.css?v=3.2.0-admin">
<link rel="stylesheet" href="/static/css/admin.css?v=3.2.1-admin">
</head>
<body>
<div class="login-overlay" id="loginOverlay">
Expand Down Expand Up @@ -45,7 +45,7 @@ <h1>Graph Memory</h1>
</main>
</div>

<script src="/static/js/admin-api.js?v=3.2.0-admin"></script>
<script src="/static/js/admin-app.js?v=3.2.0-admin"></script>
<script src="/static/js/admin-api.js?v=3.2.1-admin"></script>
<script src="/static/js/admin-app.js?v=3.2.1-admin"></script>
</body>
</html>
41 changes: 41 additions & 0 deletions src/mcp_memory/storage_consistency.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
"""Règles pures de classification des objets S3 liés aux mémoires."""


def collect_referenced_ontology_keys(memories, parse_key):
"""Retourne les clés d'ontologie légitimes et les fallbacks legacy.

Les mémoires récentes référencent exactement leur objet via ``ontology_uri``.
Pour une mémoire legacy sans URI, on protège uniquement son propre préfixe
et le nom de son ontologie ; les autres objets ``_ontology_`` restent
éligibles au nettoyage.
"""
referenced_keys = set()
legacy_patterns = []

for memory in memories:
ontology_uri = getattr(memory, "ontology_uri", None)
if ontology_uri:
try:
referenced_keys.add(parse_key(ontology_uri))
continue
except ValueError:
pass

memory_id = getattr(memory, "id", "")
ontology = getattr(memory, "ontology", "")
if memory_id and ontology:
legacy_patterns.append((f"{memory_id}/", f"__ontology_{ontology}.yaml"))

return referenced_keys, legacy_patterns


def is_referenced_ontology_key(key, referenced_keys, legacy_patterns):
"""Indique si une clé d'ontologie est encore rattachée à une mémoire."""
if "_ontology_" not in key:
return False
if key in referenced_keys:
return True
return any(
key.startswith(prefix) and key.endswith(suffix)
for prefix, suffix in legacy_patterns
)
68 changes: 68 additions & 0 deletions tests/test_storage_consistency.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
from types import SimpleNamespace

from src.mcp_memory.storage_consistency import (
collect_referenced_ontology_keys,
is_referenced_ontology_key,
)


def _parse_key(value: str) -> str:
if value.startswith("s3://"):
parts = value[5:].split("/", 1)
if len(parts) != 2:
raise ValueError(value)
return parts[1]
return value


def test_referenced_ontology_is_protected_but_duplicate_is_orphan():
memory = SimpleNamespace(
id="active-memory",
ontology="software-development",
ontology_uri=(
"s3://bucket/active-memory/documents/aaaa1111_"
"_ontology_software-development.yaml"
),
)
referenced, legacy = collect_referenced_ontology_keys([memory], _parse_key)

assert is_referenced_ontology_key(
"active-memory/documents/aaaa1111__ontology_software-development.yaml",
referenced,
legacy,
)
assert not is_referenced_ontology_key(
"active-memory/documents/bbbb2222__ontology_software-development.yaml",
referenced,
legacy,
)


def test_deleted_memory_ontology_is_orphan():
referenced, legacy = collect_referenced_ontology_keys([], _parse_key)

assert not is_referenced_ontology_key(
"deleted-memory/documents/aaaa1111__ontology_general.yaml",
referenced,
legacy,
)


def test_legacy_memory_without_uri_protects_only_its_expected_ontology():
memory = SimpleNamespace(
id="legacy-memory",
ontology="legal",
ontology_uri=None,
)
referenced, legacy = collect_referenced_ontology_keys([memory], _parse_key)

assert is_referenced_ontology_key(
"legacy-memory/documents/aaaa1111__ontology_legal.yaml",
referenced,
legacy,
)
assert not is_referenced_ontology_key(
"deleted-memory/documents/aaaa1111__ontology_legal.yaml",
referenced,
legacy,
)
1 change: 1 addition & 0 deletions tools/graph-memory-ingest/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
/bin/
Loading