memuron 0.1.1__py3-none-any.whl

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.
Files changed (74) hide show
  1. memuron/__init__.py +3 -0
  2. memuron/actions/__init__.py +12 -0
  3. memuron/actions/context.py +63 -0
  4. memuron/actions/helpers.py +88 -0
  5. memuron/actions/memory.py +340 -0
  6. memuron/actions/memory_write.py +290 -0
  7. memuron/actions/nodes.py +340 -0
  8. memuron/actions/registry.py +5 -0
  9. memuron/actions/runtime.py +37 -0
  10. memuron/actions/spaces_documents.py +720 -0
  11. memuron/actions/sync.py +155 -0
  12. memuron/application/__init__.py +1 -0
  13. memuron/application/api.py +206 -0
  14. memuron/application/app.py +103 -0
  15. memuron/application/capabilities.py +82 -0
  16. memuron/application/cli.py +35 -0
  17. memuron/application/config.py +176 -0
  18. memuron/application/mcp.py +44 -0
  19. memuron/application/mcp_oauth.py +290 -0
  20. memuron/application/registry.py +52 -0
  21. memuron/context.py +532 -0
  22. memuron/documents/__init__.py +1 -0
  23. memuron/documents/link_guardian.py +192 -0
  24. memuron/documents/linking.py +292 -0
  25. memuron/documents/parser.py +1152 -0
  26. memuron/documents/storage.py +151 -0
  27. memuron/documents/url_ingest.py +375 -0
  28. memuron/domain/__init__.py +1 -0
  29. memuron/domain/decoders.py +1 -0
  30. memuron/domain/encoders.py +185 -0
  31. memuron/domain/lifecycles.py +8 -0
  32. memuron/domain/limits.py +6 -0
  33. memuron/domain/representations.py +56 -0
  34. memuron/domain/schemas.py +581 -0
  35. memuron/domain/scope_filter.py +104 -0
  36. memuron/graphfs/__init__.py +1 -0
  37. memuron/graphfs/manual.py +635 -0
  38. memuron/graphfs/projection.py +578 -0
  39. memuron/graphfs/query.py +1782 -0
  40. memuron/graphfs/read_model.py +574 -0
  41. memuron/ingest/__init__.py +1 -0
  42. memuron/ingest/guardian.py +213 -0
  43. memuron/ingest/jobs.py +424 -0
  44. memuron/ingest/prompts.py +147 -0
  45. memuron/memory/__init__.py +1 -0
  46. memuron/memory/engine.py +35 -0
  47. memuron/memory/projections.py +452 -0
  48. memuron/memory/recipes.py +3247 -0
  49. memuron/persistence/__init__.py +1 -0
  50. memuron/persistence/db_pool.py +57 -0
  51. memuron/persistence/identity_store.py +918 -0
  52. memuron/persistence/store_helpers.py +16 -0
  53. memuron/search/__init__.py +1 -0
  54. memuron/search/fulltext.py +110 -0
  55. memuron/search/hybrid.py +284 -0
  56. memuron/search/pgvector.py +252 -0
  57. memuron/security/__init__.py +1 -0
  58. memuron/security/auth.py +143 -0
  59. memuron/security/auth_provider.py +119 -0
  60. memuron/security/authorization.py +53 -0
  61. memuron/security/clerk_scopes.py +94 -0
  62. memuron/security/clerk_webhooks.py +61 -0
  63. memuron/security/jwt_tokens.py +53 -0
  64. memuron/security/passwords.py +38 -0
  65. memuron/security/tenant.py +58 -0
  66. memuron/spaces/__init__.py +1 -0
  67. memuron/spaces/model.py +35 -0
  68. memuron/spaces/service.py +155 -0
  69. memuron/sync/__init__.py +25 -0
  70. memuron/sync/folder.py +828 -0
  71. memuron-0.1.1.dist-info/METADATA +242 -0
  72. memuron-0.1.1.dist-info/RECORD +74 -0
  73. memuron-0.1.1.dist-info/WHEEL +4 -0
  74. memuron-0.1.1.dist-info/entry_points.txt +4 -0
@@ -0,0 +1,578 @@
1
+ """Ledger-fed read model for fast graph filesystem queries."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import json
6
+ from typing import Any
7
+
8
+ from artha_engine.store.projection_sql import (
9
+ sql_store_execute,
10
+ sql_store_fetchall,
11
+ sql_store_has_tables,
12
+ )
13
+
14
+
15
+ def _json(value: object, default: object) -> object:
16
+ if value is None:
17
+ return default
18
+ if isinstance(value, (dict, list)):
19
+ return value
20
+ try:
21
+ return json.loads(str(value))
22
+ except json.JSONDecodeError:
23
+ return default
24
+
25
+
26
+ def _dump(value: object) -> str:
27
+ return json.dumps(value, ensure_ascii=True)
28
+
29
+
30
+ def _scope_parts(scope: object) -> tuple[str, str]:
31
+ values = _json(scope, [])
32
+ tokens = [str(item) for item in values] if isinstance(values, list) else []
33
+ org_token = next((token for token in tokens if token.startswith("org:")), "")
34
+ space_token = next((token for token in tokens if token.startswith("space.")), "")
35
+ return org_token, space_token
36
+
37
+
38
+ def _display(node_type: str, content: str, payload: dict[str, Any]) -> str:
39
+ if node_type == "collection" and payload.get("name"):
40
+ return str(payload["name"])
41
+ if node_type in {"document", "image"}:
42
+ for key in ("file_name", "filename"):
43
+ if payload.get(key):
44
+ return str(payload[key])
45
+ compact = " ".join(content.split())
46
+ return compact[:77] + ("..." if len(compact) > 77 else "")
47
+
48
+
49
+ class FilesystemProjection:
50
+ name = "memuron_fs"
51
+
52
+ def init(self, store: Any) -> None:
53
+ if not sql_store_has_tables(store):
54
+ store.memuron_fs_nodes = getattr(store, "memuron_fs_nodes", {})
55
+ store.memuron_fs_entries = getattr(store, "memuron_fs_entries", {})
56
+ store.memuron_fs_edges = getattr(store, "memuron_fs_edges", {})
57
+ return
58
+ sql_store_execute(
59
+ store,
60
+ """
61
+ CREATE TABLE IF NOT EXISTS memuron_fs_nodes (
62
+ node_id TEXT PRIMARY KEY,
63
+ org_token TEXT NOT NULL DEFAULT '',
64
+ space_token TEXT NOT NULL DEFAULT '',
65
+ node_type TEXT NOT NULL DEFAULT 'text',
66
+ display TEXT NOT NULL,
67
+ content TEXT NOT NULL,
68
+ encoding TEXT NOT NULL DEFAULT 'memory',
69
+ payload_json TEXT NOT NULL DEFAULT '{}',
70
+ metadata_json TEXT NOT NULL DEFAULT '{}',
71
+ scope_json TEXT NOT NULL DEFAULT '[]',
72
+ sequence BIGINT NOT NULL DEFAULT 0
73
+ )
74
+ """,
75
+ )
76
+ sql_store_execute(
77
+ store,
78
+ """
79
+ CREATE TABLE IF NOT EXISTS memuron_fs_entries (
80
+ placement_id TEXT PRIMARY KEY,
81
+ parent_id TEXT NOT NULL,
82
+ child_id TEXT NOT NULL,
83
+ name TEXT NOT NULL,
84
+ org_token TEXT NOT NULL DEFAULT '',
85
+ space_token TEXT NOT NULL DEFAULT '',
86
+ inherit_parent_scope BOOLEAN NOT NULL DEFAULT TRUE,
87
+ metadata_json TEXT NOT NULL DEFAULT '{}',
88
+ sequence BIGINT NOT NULL DEFAULT 0
89
+ )
90
+ """,
91
+ )
92
+ sql_store_execute(
93
+ store,
94
+ """
95
+ CREATE TABLE IF NOT EXISTS memuron_fs_edges (
96
+ edge_id TEXT PRIMARY KEY,
97
+ source_id TEXT NOT NULL,
98
+ target_id TEXT NOT NULL,
99
+ edge_type TEXT NOT NULL,
100
+ description TEXT NOT NULL DEFAULT '',
101
+ org_token TEXT NOT NULL DEFAULT '',
102
+ space_token TEXT NOT NULL DEFAULT '',
103
+ sequence BIGINT NOT NULL DEFAULT 0
104
+ )
105
+ """,
106
+ )
107
+ for statement in (
108
+ "CREATE INDEX IF NOT EXISTS idx_memuron_fs_nodes_scope ON memuron_fs_nodes(org_token, space_token)",
109
+ "CREATE INDEX IF NOT EXISTS idx_memuron_fs_nodes_scope_type ON memuron_fs_nodes(org_token, space_token, node_type)",
110
+ "CREATE INDEX IF NOT EXISTS idx_memuron_fs_nodes_encoding ON memuron_fs_nodes(org_token, space_token, encoding)",
111
+ "CREATE INDEX IF NOT EXISTS idx_memuron_fs_entries_parent ON memuron_fs_entries(parent_id)",
112
+ "CREATE INDEX IF NOT EXISTS idx_memuron_fs_entries_child ON memuron_fs_entries(child_id)",
113
+ "CREATE INDEX IF NOT EXISTS idx_memuron_fs_entries_scope ON memuron_fs_entries(org_token, space_token)",
114
+ "CREATE INDEX IF NOT EXISTS idx_memuron_fs_edges_source ON memuron_fs_edges(org_token, space_token, source_id)",
115
+ "CREATE INDEX IF NOT EXISTS idx_memuron_fs_edges_target ON memuron_fs_edges(org_token, space_token, target_id)",
116
+ ):
117
+ sql_store_execute(store, statement)
118
+ self._add_column_if_missing(
119
+ store,
120
+ "memuron_fs_entries",
121
+ "inherit_parent_scope BOOLEAN NOT NULL DEFAULT TRUE",
122
+ )
123
+ self._ensure_fulltext(store)
124
+ self._backfill_document_containers(store)
125
+
126
+ def _add_column_if_missing(self, store: Any, table: str, column_sql: str) -> None:
127
+ try:
128
+ sql_store_execute(store, f"ALTER TABLE {table} ADD COLUMN {column_sql}")
129
+ except Exception:
130
+ pass
131
+
132
+ def _ensure_fulltext(self, store: Any) -> None:
133
+ try:
134
+ sql_store_execute(
135
+ store,
136
+ "ALTER TABLE memuron_fs_nodes ADD COLUMN IF NOT EXISTS content_tsv tsvector",
137
+ )
138
+ sql_store_execute(
139
+ store,
140
+ """
141
+ UPDATE memuron_fs_nodes
142
+ SET content_tsv = to_tsvector('english', coalesce(content, ''))
143
+ WHERE content_tsv IS NULL
144
+ """,
145
+ )
146
+ sql_store_execute(
147
+ store,
148
+ """
149
+ CREATE INDEX IF NOT EXISTS idx_memuron_fs_nodes_content_tsv
150
+ ON memuron_fs_nodes USING GIN (content_tsv)
151
+ """,
152
+ )
153
+ setattr(store, "_memuron_fs_fulltext_ready", True)
154
+ except Exception:
155
+ setattr(store, "_memuron_fs_fulltext_ready", False)
156
+
157
+ def _backfill_document_containers(self, store: Any) -> None:
158
+ if not sql_store_has_tables(store):
159
+ return
160
+ rows = sql_store_fetchall(
161
+ store,
162
+ """
163
+ SELECT p.parent_id, p.child_id, p.metadata_json
164
+ FROM memuron_fs_entries p
165
+ JOIN memuron_fs_nodes n ON n.node_id = p.parent_id
166
+ WHERE n.encoding = 'document_collection'
167
+ """,
168
+ )
169
+ for row in rows:
170
+ metadata = _json(row.get("metadata_json"), {})
171
+ self._annotate_document_container(
172
+ store,
173
+ parent_id=str(row["parent_id"]),
174
+ child_id=str(row["child_id"]),
175
+ metadata=metadata if isinstance(metadata, dict) else {},
176
+ )
177
+
178
+ def apply(self, event: dict[str, object], store: Any) -> None:
179
+ event_type = str(event.get("event_type", ""))
180
+ if event_type in {"memory.created", "memory.updated", "collection.created"}:
181
+ self._upsert_node(event, store)
182
+ elif event_type in {"delete", "memory.deleted"} and event.get("subject_type") == "memory":
183
+ self._delete_node(str(event["subject_id"]), store)
184
+ elif event_type == "placement.created":
185
+ self._upsert_placement(event, store)
186
+ elif event_type == "placement.removed" or (
187
+ event_type == "delete" and event.get("subject_type") == "memory_placement"
188
+ ):
189
+ self._delete_placement(self._removed_id(event, "placement_id"), store)
190
+ elif event_type == "link.created":
191
+ self._upsert_link(event, store)
192
+ elif event_type == "link.removed" or (
193
+ event_type == "delete" and event.get("subject_type") == "memory_link"
194
+ ):
195
+ self._delete_edge(self._removed_id(event, "link_id"), store)
196
+
197
+ def _value(self, event: dict[str, object]) -> dict[str, Any] | None:
198
+ payload = event.get("payload")
199
+ if not isinstance(payload, dict):
200
+ return None
201
+ arthaanu = payload.get("arthaanu")
202
+ if not isinstance(arthaanu, dict):
203
+ return None
204
+ value = arthaanu.get("value")
205
+ return value if isinstance(value, dict) else None
206
+
207
+ def _upsert_node(self, event: dict[str, object], store: Any) -> None:
208
+ value = self._value(event)
209
+ if value is None or not isinstance(value.get("content"), str):
210
+ return
211
+ node_id = str(event["subject_id"])
212
+ content = str(value["content"])
213
+ node_type = str(value.get("node_type") or "text")
214
+ payload = _json(value.get("payload"), {})
215
+ metadata = _json(value.get("metadata"), {})
216
+ scope = _json(value.get("scope"), [])
217
+ payload_dict = payload if isinstance(payload, dict) else {}
218
+ metadata_dict = metadata if isinstance(metadata, dict) else {}
219
+ scope_list = scope if isinstance(scope, list) else []
220
+ org_token, space_token = _scope_parts(scope_list)
221
+ row = {
222
+ "node_id": node_id,
223
+ "org_token": org_token,
224
+ "space_token": space_token,
225
+ "node_type": node_type,
226
+ "display": _display(node_type, content, payload_dict),
227
+ "content": content,
228
+ "encoding": str(value.get("encoding") or "memory"),
229
+ "payload": payload_dict,
230
+ "metadata": metadata_dict,
231
+ "scope": scope_list,
232
+ "sequence": int(event["sequence"]),
233
+ }
234
+ if sql_store_has_tables(store):
235
+ sql_store_execute(
236
+ store,
237
+ """
238
+ INSERT INTO memuron_fs_nodes (
239
+ node_id, org_token, space_token, node_type, display, content,
240
+ encoding, payload_json, metadata_json, scope_json, sequence
241
+ ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
242
+ ON CONFLICT(node_id) DO UPDATE SET
243
+ org_token = excluded.org_token,
244
+ space_token = excluded.space_token,
245
+ node_type = excluded.node_type,
246
+ display = excluded.display,
247
+ content = excluded.content,
248
+ encoding = excluded.encoding,
249
+ payload_json = excluded.payload_json,
250
+ metadata_json = excluded.metadata_json,
251
+ scope_json = excluded.scope_json,
252
+ sequence = excluded.sequence
253
+ """,
254
+ (
255
+ node_id,
256
+ org_token,
257
+ space_token,
258
+ node_type,
259
+ row["display"],
260
+ content,
261
+ row["encoding"],
262
+ _dump(payload_dict),
263
+ _dump(metadata_dict),
264
+ _dump(scope_list),
265
+ row["sequence"],
266
+ ),
267
+ )
268
+ if getattr(store, "_memuron_fs_fulltext_ready", False):
269
+ sql_store_execute(
270
+ store,
271
+ """
272
+ UPDATE memuron_fs_nodes
273
+ SET content_tsv = to_tsvector('english', coalesce(?, ''))
274
+ WHERE node_id = ?
275
+ """,
276
+ (content, node_id),
277
+ )
278
+ else:
279
+ store.memuron_fs_nodes[node_id] = row
280
+
281
+ def _upsert_placement(self, event: dict[str, object], store: Any) -> None:
282
+ value = self._value(event)
283
+ if value is None:
284
+ return
285
+ parent_id = value.get("parent_id")
286
+ child_id = value.get("child_id")
287
+ name = value.get("name")
288
+ if not all(isinstance(item, str) for item in (parent_id, child_id, name)):
289
+ return
290
+ placement_id = str(event["subject_id"])
291
+ metadata = _json(value.get("metadata"), {})
292
+ inherit_parent_scope = bool(value.get("inherit_parent_scope", True))
293
+ org_token, space_token = _scope_parts(value.get("scope"))
294
+ if not space_token:
295
+ org_token, space_token = self._node_scope(store, str(child_id))
296
+ row = {
297
+ "placement_id": placement_id,
298
+ "parent_id": str(parent_id),
299
+ "child_id": str(child_id),
300
+ "name": str(name),
301
+ "org_token": org_token,
302
+ "space_token": space_token,
303
+ "inherit_parent_scope": inherit_parent_scope,
304
+ "metadata": metadata if isinstance(metadata, dict) else {},
305
+ "sequence": int(event["sequence"]),
306
+ }
307
+ if sql_store_has_tables(store):
308
+ sql_store_execute(
309
+ store,
310
+ """
311
+ INSERT INTO memuron_fs_entries (
312
+ placement_id, parent_id, child_id, name, org_token, space_token,
313
+ inherit_parent_scope, metadata_json, sequence
314
+ ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
315
+ ON CONFLICT(placement_id) DO UPDATE SET
316
+ parent_id = excluded.parent_id,
317
+ child_id = excluded.child_id,
318
+ name = excluded.name,
319
+ org_token = excluded.org_token,
320
+ space_token = excluded.space_token,
321
+ inherit_parent_scope = excluded.inherit_parent_scope,
322
+ metadata_json = excluded.metadata_json,
323
+ sequence = excluded.sequence
324
+ """,
325
+ (
326
+ placement_id,
327
+ parent_id,
328
+ child_id,
329
+ name,
330
+ org_token,
331
+ space_token,
332
+ inherit_parent_scope,
333
+ _dump(row["metadata"]),
334
+ row["sequence"],
335
+ ),
336
+ )
337
+ self._upsert_placement_edge(row, store)
338
+ self._annotate_document_container(
339
+ store,
340
+ parent_id=str(parent_id),
341
+ child_id=str(child_id),
342
+ metadata=row["metadata"],
343
+ )
344
+ else:
345
+ store.memuron_fs_entries[placement_id] = row
346
+ store.memuron_fs_edges[placement_id] = {
347
+ "edge_id": placement_id,
348
+ "source_id": str(parent_id),
349
+ "target_id": str(child_id),
350
+ "edge_type": "placement",
351
+ "description": str(name),
352
+ "org_token": org_token,
353
+ "space_token": space_token,
354
+ "inherit_parent_scope": inherit_parent_scope,
355
+ "sequence": row["sequence"],
356
+ }
357
+ self._annotate_document_container(
358
+ store,
359
+ parent_id=str(parent_id),
360
+ child_id=str(child_id),
361
+ metadata=row["metadata"],
362
+ )
363
+
364
+ def _annotate_document_container(
365
+ self,
366
+ store: Any,
367
+ *,
368
+ parent_id: str,
369
+ child_id: str,
370
+ metadata: dict[str, Any],
371
+ ) -> None:
372
+ if metadata.get("role") != "source":
373
+ return
374
+ if sql_store_has_tables(store):
375
+ rows = sql_store_fetchall(
376
+ store,
377
+ """
378
+ SELECT encoding, payload_json
379
+ FROM memuron_fs_nodes
380
+ WHERE node_id = ?
381
+ """,
382
+ (parent_id,),
383
+ )
384
+ if not rows or rows[0].get("encoding") != "document_collection":
385
+ return
386
+ payload = _json(rows[0].get("payload_json"), {})
387
+ payload_dict = payload if isinstance(payload, dict) else {}
388
+ descriptor = {
389
+ "id": child_id,
390
+ "file_name": payload_dict.get("name"),
391
+ "source_type": payload_dict.get("source_type"),
392
+ "chunk_count": payload_dict.get("chunk_count", 0),
393
+ "image_count": payload_dict.get("image_count", 0),
394
+ "graph_image_count": payload_dict.get("graph_image_count", 0),
395
+ }
396
+ updated = {
397
+ **payload_dict,
398
+ "collection_kind": "document",
399
+ "document_id": child_id,
400
+ "document": descriptor,
401
+ }
402
+ sql_store_execute(
403
+ store,
404
+ "UPDATE memuron_fs_nodes SET payload_json = ? WHERE node_id = ?",
405
+ (_dump(updated), parent_id),
406
+ )
407
+ return
408
+ row = getattr(store, "memuron_fs_nodes", {}).get(parent_id)
409
+ if not row or row.get("encoding") != "document_collection":
410
+ return
411
+ payload = dict(row.get("payload") or {})
412
+ row["payload"] = {
413
+ **payload,
414
+ "collection_kind": "document",
415
+ "document_id": child_id,
416
+ "document": {
417
+ "id": child_id,
418
+ "file_name": payload.get("name"),
419
+ "source_type": payload.get("source_type"),
420
+ "chunk_count": payload.get("chunk_count", 0),
421
+ "image_count": payload.get("image_count", 0),
422
+ "graph_image_count": payload.get("graph_image_count", 0),
423
+ },
424
+ }
425
+
426
+ def _upsert_placement_edge(self, row: dict[str, Any], store: Any) -> None:
427
+ sql_store_execute(
428
+ store,
429
+ """
430
+ INSERT INTO memuron_fs_edges (
431
+ edge_id, source_id, target_id, edge_type, description,
432
+ org_token, space_token, sequence
433
+ ) VALUES (?, ?, ?, 'placement', ?, ?, ?, ?)
434
+ ON CONFLICT(edge_id) DO UPDATE SET
435
+ source_id = excluded.source_id,
436
+ target_id = excluded.target_id,
437
+ edge_type = excluded.edge_type,
438
+ description = excluded.description,
439
+ org_token = excluded.org_token,
440
+ space_token = excluded.space_token,
441
+ sequence = excluded.sequence
442
+ """,
443
+ (
444
+ row["placement_id"],
445
+ row["parent_id"],
446
+ row["child_id"],
447
+ row["name"],
448
+ row["org_token"],
449
+ row["space_token"],
450
+ row["sequence"],
451
+ ),
452
+ )
453
+
454
+ def _upsert_link(self, event: dict[str, object], store: Any) -> None:
455
+ value = self._value(event)
456
+ if value is None:
457
+ return
458
+ source_id = value.get("source_id")
459
+ target_id = value.get("target_id")
460
+ description = value.get("description")
461
+ if not isinstance(source_id, str) or not isinstance(target_id, str):
462
+ return
463
+ edge_id = str(event["subject_id"])
464
+ org_token, space_token = self._node_scope(store, source_id)
465
+ row = {
466
+ "edge_id": edge_id,
467
+ "source_id": source_id,
468
+ "target_id": target_id,
469
+ "edge_type": "semantic_link",
470
+ "description": str(description or ""),
471
+ "org_token": org_token,
472
+ "space_token": space_token,
473
+ "sequence": int(event["sequence"]),
474
+ }
475
+ if sql_store_has_tables(store):
476
+ sql_store_execute(
477
+ store,
478
+ """
479
+ INSERT INTO memuron_fs_edges (
480
+ edge_id, source_id, target_id, edge_type, description,
481
+ org_token, space_token, sequence
482
+ ) VALUES (?, ?, ?, 'semantic_link', ?, ?, ?, ?)
483
+ ON CONFLICT(edge_id) DO UPDATE SET
484
+ source_id = excluded.source_id,
485
+ target_id = excluded.target_id,
486
+ edge_type = excluded.edge_type,
487
+ description = excluded.description,
488
+ org_token = excluded.org_token,
489
+ space_token = excluded.space_token,
490
+ sequence = excluded.sequence
491
+ """,
492
+ (
493
+ edge_id,
494
+ source_id,
495
+ target_id,
496
+ row["description"],
497
+ org_token,
498
+ space_token,
499
+ row["sequence"],
500
+ ),
501
+ )
502
+ else:
503
+ store.memuron_fs_edges[edge_id] = row
504
+
505
+ def _node_scope(self, store: Any, node_id: str) -> tuple[str, str]:
506
+ if sql_store_has_tables(store):
507
+ rows = sql_store_fetchall(
508
+ store,
509
+ "SELECT org_token, space_token FROM memuron_fs_nodes WHERE node_id = ?",
510
+ (node_id,),
511
+ )
512
+ if rows:
513
+ return str(rows[0].get("org_token") or ""), str(rows[0].get("space_token") or "")
514
+ return "", ""
515
+ row = getattr(store, "memuron_fs_nodes", {}).get(node_id, {})
516
+ return str(row.get("org_token") or ""), str(row.get("space_token") or "")
517
+
518
+ def _removed_id(self, event: dict[str, object], key: str) -> str:
519
+ payload = event.get("payload")
520
+ if isinstance(payload, dict) and payload.get(key):
521
+ return str(payload[key])
522
+ return str(event.get("subject_id") or "")
523
+
524
+ def reset(self, store: Any) -> None:
525
+ if sql_store_has_tables(store):
526
+ sql_store_execute(store, "DELETE FROM memuron_fs_edges")
527
+ sql_store_execute(store, "DELETE FROM memuron_fs_entries")
528
+ sql_store_execute(store, "DELETE FROM memuron_fs_nodes")
529
+ else:
530
+ store.memuron_fs_nodes = {}
531
+ store.memuron_fs_entries = {}
532
+ store.memuron_fs_edges = {}
533
+
534
+ def _delete_node(self, node_id: str, store: Any) -> None:
535
+ if sql_store_has_tables(store):
536
+ sql_store_execute(
537
+ store,
538
+ "DELETE FROM memuron_fs_edges WHERE source_id = ? OR target_id = ?",
539
+ (node_id, node_id),
540
+ )
541
+ sql_store_execute(
542
+ store,
543
+ "DELETE FROM memuron_fs_entries WHERE parent_id = ? OR child_id = ?",
544
+ (node_id, node_id),
545
+ )
546
+ sql_store_execute(store, "DELETE FROM memuron_fs_nodes WHERE node_id = ?", (node_id,))
547
+ else:
548
+ store.memuron_fs_nodes.pop(node_id, None)
549
+ store.memuron_fs_entries = {
550
+ key: row
551
+ for key, row in store.memuron_fs_entries.items()
552
+ if node_id not in {row["parent_id"], row["child_id"]}
553
+ }
554
+ store.memuron_fs_edges = {
555
+ key: row
556
+ for key, row in store.memuron_fs_edges.items()
557
+ if node_id not in {row["source_id"], row["target_id"]}
558
+ }
559
+
560
+ def _delete_placement(self, placement_id: str, store: Any) -> None:
561
+ if not placement_id:
562
+ return
563
+ if sql_store_has_tables(store):
564
+ sql_store_execute(
565
+ store, "DELETE FROM memuron_fs_entries WHERE placement_id = ?", (placement_id,)
566
+ )
567
+ sql_store_execute(store, "DELETE FROM memuron_fs_edges WHERE edge_id = ?", (placement_id,))
568
+ else:
569
+ store.memuron_fs_entries.pop(placement_id, None)
570
+ store.memuron_fs_edges.pop(placement_id, None)
571
+
572
+ def _delete_edge(self, edge_id: str, store: Any) -> None:
573
+ if not edge_id:
574
+ return
575
+ if sql_store_has_tables(store):
576
+ sql_store_execute(store, "DELETE FROM memuron_fs_edges WHERE edge_id = ?", (edge_id,))
577
+ else:
578
+ store.memuron_fs_edges.pop(edge_id, None)