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.
- memuron/__init__.py +3 -0
- memuron/actions/__init__.py +12 -0
- memuron/actions/context.py +63 -0
- memuron/actions/helpers.py +88 -0
- memuron/actions/memory.py +340 -0
- memuron/actions/memory_write.py +290 -0
- memuron/actions/nodes.py +340 -0
- memuron/actions/registry.py +5 -0
- memuron/actions/runtime.py +37 -0
- memuron/actions/spaces_documents.py +720 -0
- memuron/actions/sync.py +155 -0
- memuron/application/__init__.py +1 -0
- memuron/application/api.py +206 -0
- memuron/application/app.py +103 -0
- memuron/application/capabilities.py +82 -0
- memuron/application/cli.py +35 -0
- memuron/application/config.py +176 -0
- memuron/application/mcp.py +44 -0
- memuron/application/mcp_oauth.py +290 -0
- memuron/application/registry.py +52 -0
- memuron/context.py +532 -0
- memuron/documents/__init__.py +1 -0
- memuron/documents/link_guardian.py +192 -0
- memuron/documents/linking.py +292 -0
- memuron/documents/parser.py +1152 -0
- memuron/documents/storage.py +151 -0
- memuron/documents/url_ingest.py +375 -0
- memuron/domain/__init__.py +1 -0
- memuron/domain/decoders.py +1 -0
- memuron/domain/encoders.py +185 -0
- memuron/domain/lifecycles.py +8 -0
- memuron/domain/limits.py +6 -0
- memuron/domain/representations.py +56 -0
- memuron/domain/schemas.py +581 -0
- memuron/domain/scope_filter.py +104 -0
- memuron/graphfs/__init__.py +1 -0
- memuron/graphfs/manual.py +635 -0
- memuron/graphfs/projection.py +578 -0
- memuron/graphfs/query.py +1782 -0
- memuron/graphfs/read_model.py +574 -0
- memuron/ingest/__init__.py +1 -0
- memuron/ingest/guardian.py +213 -0
- memuron/ingest/jobs.py +424 -0
- memuron/ingest/prompts.py +147 -0
- memuron/memory/__init__.py +1 -0
- memuron/memory/engine.py +35 -0
- memuron/memory/projections.py +452 -0
- memuron/memory/recipes.py +3247 -0
- memuron/persistence/__init__.py +1 -0
- memuron/persistence/db_pool.py +57 -0
- memuron/persistence/identity_store.py +918 -0
- memuron/persistence/store_helpers.py +16 -0
- memuron/search/__init__.py +1 -0
- memuron/search/fulltext.py +110 -0
- memuron/search/hybrid.py +284 -0
- memuron/search/pgvector.py +252 -0
- memuron/security/__init__.py +1 -0
- memuron/security/auth.py +143 -0
- memuron/security/auth_provider.py +119 -0
- memuron/security/authorization.py +53 -0
- memuron/security/clerk_scopes.py +94 -0
- memuron/security/clerk_webhooks.py +61 -0
- memuron/security/jwt_tokens.py +53 -0
- memuron/security/passwords.py +38 -0
- memuron/security/tenant.py +58 -0
- memuron/spaces/__init__.py +1 -0
- memuron/spaces/model.py +35 -0
- memuron/spaces/service.py +155 -0
- memuron/sync/__init__.py +25 -0
- memuron/sync/folder.py +828 -0
- memuron-0.1.1.dist-info/METADATA +242 -0
- memuron-0.1.1.dist-info/RECORD +74 -0
- memuron-0.1.1.dist-info/WHEEL +4 -0
- 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)
|