@contextableai/openclaw-memory-rebac 0.1.4 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -49,7 +49,7 @@ services:
49
49
  dockerfile: Dockerfile
50
50
  restart: unless-stopped
51
51
  ports:
52
- - "8000:8000"
52
+ - "127.0.0.1:8000:8000"
53
53
  environment:
54
54
  # -- Graph database (Neo4j) --
55
55
  NEO4J_URI: bolt://neo4j:7687
@@ -149,6 +149,25 @@ def patch():
149
149
  'created_at', 'labels',
150
150
  }
151
151
 
152
+ # Edge names that represent deduplication artifacts, not real semantic facts.
153
+ # Older graphiti-core versions created these during node resolution; the LLM
154
+ # may also extract them as relationships. Neither source is useful — upstream
155
+ # abandoned IS_DUPLICATE_OF edges and the information is redundant with node
156
+ # UUID reuse. Matched case-insensitively with space/underscore normalization.
157
+ # See: https://github.com/contextablemark/openclaw-memory-rebac/issues/12
158
+ RESERVED_EDGE_NAMES = {
159
+ 'IS_DUPLICATE_OF',
160
+ 'DUPLICATE_OF',
161
+ 'HAS_DUPLICATE',
162
+ 'DUPLICATES',
163
+ }
164
+
165
+ def _is_reserved_edge_name(name):
166
+ """Check if an edge name is a deduplication artifact."""
167
+ if not name:
168
+ return False
169
+ return name.strip().upper().replace(" ", "_") in RESERVED_EDGE_NAMES
170
+
152
171
  def _sanitize_attributes(attrs, reserved_keys):
153
172
  """Flatten non-primitive values and strip reserved keys to prevent clobber."""
154
173
  if not attrs:
@@ -194,6 +213,31 @@ def patch():
194
213
  list((edge.attributes or {}).keys()),
195
214
  edge.source_node_uuid, edge.target_node_uuid,
196
215
  )
216
+ # Filter deduplication-artifact edges (IS_DUPLICATE_OF and variants).
217
+ # These are not real facts — they are structural metadata that upstream
218
+ # graphiti-core has since abandoned.
219
+ original_edge_count = len(entity_edges)
220
+ entity_edges = [e for e in entity_edges if not _is_reserved_edge_name(e.name)]
221
+ dup_filtered = original_edge_count - len(entity_edges)
222
+ if dup_filtered:
223
+ logger.warning(
224
+ "DIAG duplicate_edge_filtered: removed %d IS_DUPLICATE_OF-family "
225
+ "edge(s) at bulk_add level", dup_filtered,
226
+ )
227
+
228
+ # Filter self-referential edges (entity relating to itself).
229
+ pre_self_count = len(entity_edges)
230
+ entity_edges = [
231
+ e for e in entity_edges
232
+ if e.source_node_uuid != e.target_node_uuid
233
+ ]
234
+ self_ref_count = pre_self_count - len(entity_edges)
235
+ if self_ref_count:
236
+ logger.warning(
237
+ "DIAG self_ref_edge_filtered: removed %d self-referential edge(s)",
238
+ self_ref_count,
239
+ )
240
+
197
241
  return await original_bulk_add(
198
242
  driver, episodic_nodes, episodic_edges,
199
243
  entity_nodes, entity_edges, embedder,
@@ -253,15 +297,26 @@ def patch():
253
297
  except Exception as _patch_err:
254
298
  logger.warning("Could not patch ExtractedEdges for None-index filtering: %s", _patch_err)
255
299
 
256
- # Fallback: catch any remaining TypeError from the whole function
300
+ # Fallback: catch any remaining TypeError from the whole function,
301
+ # and filter IS_DUPLICATE_OF edges early (before embedding computation).
257
302
  async def safe_extract_edges(*args, **kwargs):
258
303
  try:
259
- return await original_extract_edges(*args, **kwargs)
304
+ result = await original_extract_edges(*args, **kwargs)
260
305
  except TypeError as e:
261
306
  if "not supported between instances" in str(e):
262
307
  logger.warning("extract_edges skipped due to LLM output issue: %s", e)
263
308
  return []
264
309
  raise
310
+ if result:
311
+ original_len = len(result)
312
+ result = [e for e in result if not _is_reserved_edge_name(e.name)]
313
+ dropped = original_len - len(result)
314
+ if dropped:
315
+ logger.warning(
316
+ "DIAG duplicate_edge_filtered: removed %d IS_DUPLICATE_OF-family "
317
+ "edge(s) at extract_edges level", dropped,
318
+ )
319
+ return result
265
320
 
266
321
  edge_ops_mod.extract_edges = safe_extract_edges
267
322
  # Patch the local binding in graphiti.py
@@ -56,8 +56,8 @@ services:
56
56
  command: serve
57
57
  restart: unless-stopped
58
58
  ports:
59
- - "${SPICEDB_GRPC_PORT:-50051}:50051" # gRPC
60
- - "${SPICEDB_HTTP_PORT:-8080}:8080" # HTTP metrics / healthz
59
+ - "127.0.0.1:${SPICEDB_GRPC_PORT:-50051}:50051" # gRPC
60
+ - "127.0.0.1:${SPICEDB_HTTP_PORT:-8080}:8080" # HTTP metrics / healthz
61
61
  environment:
62
62
  SPICEDB_GRPC_PRESHARED_KEY: ${SPICEDB_PRESHARED_KEY:-dev_token}
63
63
  SPICEDB_DATASTORE_ENGINE: postgres
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@contextableai/openclaw-memory-rebac",
3
- "version": "0.1.4",
3
+ "version": "0.2.0",
4
4
  "description": "OpenClaw two-layer memory plugin: SpiceDB ReBAC authorization + Graphiti knowledge graph",
5
5
  "type": "module",
6
6
  "license": "MIT",