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,574 @@
|
|
|
1
|
+
"""Indexed reads over the Memuron filesystem projection."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import json
|
|
6
|
+
import re
|
|
7
|
+
from typing import Any
|
|
8
|
+
|
|
9
|
+
from artha_engine.store.projection_sql import sql_store_fetchall, sql_store_has_tables
|
|
10
|
+
|
|
11
|
+
MAX_READ_ROWS = 1000
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def _parse(value: Any, default: Any) -> Any:
|
|
15
|
+
if value is None:
|
|
16
|
+
return default
|
|
17
|
+
if isinstance(value, (dict, list)):
|
|
18
|
+
return value
|
|
19
|
+
try:
|
|
20
|
+
return json.loads(str(value))
|
|
21
|
+
except json.JSONDecodeError:
|
|
22
|
+
return default
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def _node(row: dict[str, Any]) -> dict[str, Any]:
|
|
26
|
+
placement = None
|
|
27
|
+
if row.get("placement_id"):
|
|
28
|
+
placement = {
|
|
29
|
+
"id": str(row["placement_id"]),
|
|
30
|
+
"parent_id": str(row["parent_id"]),
|
|
31
|
+
"child_id": str(row["node_id"]),
|
|
32
|
+
"name": str(row["placement_name"]),
|
|
33
|
+
"metadata": _parse(row.get("placement_metadata_json"), {}),
|
|
34
|
+
"inherit_parent_scope": bool(row.get("inherit_parent_scope", True)),
|
|
35
|
+
}
|
|
36
|
+
content = str(row.get("content") or "")
|
|
37
|
+
node_type = str(row.get("node_type") or "text")
|
|
38
|
+
display = str(row.get("placement_name") or row.get("display") or "")
|
|
39
|
+
preview = " ".join(content.split())
|
|
40
|
+
return {
|
|
41
|
+
"kind": "node",
|
|
42
|
+
"id": str(row["node_id"]),
|
|
43
|
+
"type": node_type,
|
|
44
|
+
"node_type": node_type,
|
|
45
|
+
"display": display,
|
|
46
|
+
"preview": preview[:240] + ("..." if len(preview) > 240 else ""),
|
|
47
|
+
"content": content,
|
|
48
|
+
"content_length": len(content),
|
|
49
|
+
"truncated": len(preview) > len(display),
|
|
50
|
+
"encoding": str(row.get("encoding") or "memory"),
|
|
51
|
+
"scope": _parse(row.get("scope_json"), []),
|
|
52
|
+
"payload": _parse(row.get("payload_json"), {}),
|
|
53
|
+
"metadata": _parse(row.get("metadata_json"), {}),
|
|
54
|
+
"degree": int(row.get("degree") or 0),
|
|
55
|
+
"placement": placement,
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def _degree_sql(alias: str = "n") -> str:
|
|
60
|
+
return f"""
|
|
61
|
+
(
|
|
62
|
+
SELECT COUNT(*) FROM memuron_fs_edges es
|
|
63
|
+
WHERE es.org_token = {alias}.org_token
|
|
64
|
+
AND es.space_token = {alias}.space_token
|
|
65
|
+
AND es.source_id = {alias}.node_id
|
|
66
|
+
AND es.target_id != {alias}.node_id
|
|
67
|
+
) + (
|
|
68
|
+
SELECT COUNT(*) FROM memuron_fs_edges et
|
|
69
|
+
WHERE et.org_token = {alias}.org_token
|
|
70
|
+
AND et.space_token = {alias}.space_token
|
|
71
|
+
AND et.target_id = {alias}.node_id
|
|
72
|
+
AND et.source_id != {alias}.node_id
|
|
73
|
+
)
|
|
74
|
+
"""
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
def list_entries(
|
|
78
|
+
store: object,
|
|
79
|
+
*,
|
|
80
|
+
org_token: str,
|
|
81
|
+
space_token: str,
|
|
82
|
+
collection_id: str | None = None,
|
|
83
|
+
node_type: str | None = None,
|
|
84
|
+
encoding: str | None = None,
|
|
85
|
+
node_id: str | None = None,
|
|
86
|
+
name: str | None = None,
|
|
87
|
+
floating: bool = False,
|
|
88
|
+
collections_only: bool = False,
|
|
89
|
+
limit: int = MAX_READ_ROWS,
|
|
90
|
+
) -> list[dict[str, Any]]:
|
|
91
|
+
if not sql_store_has_tables(store):
|
|
92
|
+
return _memory_list_entries(
|
|
93
|
+
store,
|
|
94
|
+
org_token=org_token,
|
|
95
|
+
space_token=space_token,
|
|
96
|
+
collection_id=collection_id,
|
|
97
|
+
node_type=node_type,
|
|
98
|
+
encoding=encoding,
|
|
99
|
+
node_id=node_id,
|
|
100
|
+
name=name,
|
|
101
|
+
floating=floating,
|
|
102
|
+
collections_only=collections_only,
|
|
103
|
+
limit=limit,
|
|
104
|
+
)
|
|
105
|
+
params: list[Any] = [org_token, space_token]
|
|
106
|
+
filters = ["n.org_token = ?", "n.space_token = ?"]
|
|
107
|
+
if node_type:
|
|
108
|
+
filters.append("n.node_type = ?")
|
|
109
|
+
params.append(node_type)
|
|
110
|
+
if encoding:
|
|
111
|
+
filters.append("n.encoding = ?")
|
|
112
|
+
params.append(encoding)
|
|
113
|
+
if node_id:
|
|
114
|
+
filters.append("n.node_id = ?")
|
|
115
|
+
params.append(node_id)
|
|
116
|
+
if name:
|
|
117
|
+
filters.append("lower(n.display) LIKE lower(?)")
|
|
118
|
+
params.append(f"%{name.replace('*', '')}%")
|
|
119
|
+
if collections_only:
|
|
120
|
+
filters.append("n.node_type = 'collection'")
|
|
121
|
+
if floating:
|
|
122
|
+
filters.extend(
|
|
123
|
+
[
|
|
124
|
+
"n.node_type != 'collection'",
|
|
125
|
+
"""
|
|
126
|
+
NOT EXISTS (
|
|
127
|
+
SELECT 1 FROM memuron_fs_entries fp
|
|
128
|
+
WHERE fp.org_token = n.org_token
|
|
129
|
+
AND fp.space_token = n.space_token
|
|
130
|
+
AND fp.child_id = n.node_id
|
|
131
|
+
)
|
|
132
|
+
""",
|
|
133
|
+
]
|
|
134
|
+
)
|
|
135
|
+
|
|
136
|
+
if collection_id:
|
|
137
|
+
filters.append("p.parent_id = ?")
|
|
138
|
+
params.append(collection_id)
|
|
139
|
+
rows = sql_store_fetchall(
|
|
140
|
+
store,
|
|
141
|
+
f"""
|
|
142
|
+
SELECT n.*, p.placement_id, p.parent_id, p.name AS placement_name,
|
|
143
|
+
p.metadata_json AS placement_metadata_json,
|
|
144
|
+
p.inherit_parent_scope,
|
|
145
|
+
{_degree_sql()} AS degree
|
|
146
|
+
FROM memuron_fs_entries p
|
|
147
|
+
JOIN memuron_fs_nodes n ON n.node_id = p.child_id
|
|
148
|
+
WHERE {' AND '.join(filters)}
|
|
149
|
+
ORDER BY p.sequence DESC
|
|
150
|
+
LIMIT ?
|
|
151
|
+
""",
|
|
152
|
+
(*params, limit),
|
|
153
|
+
)
|
|
154
|
+
else:
|
|
155
|
+
rows = sql_store_fetchall(
|
|
156
|
+
store,
|
|
157
|
+
f"""
|
|
158
|
+
SELECT n.*, NULL AS placement_id, NULL AS parent_id,
|
|
159
|
+
NULL AS placement_name, NULL AS placement_metadata_json,
|
|
160
|
+
NULL AS inherit_parent_scope,
|
|
161
|
+
{_degree_sql()} AS degree
|
|
162
|
+
FROM memuron_fs_nodes n
|
|
163
|
+
WHERE {' AND '.join(filters)}
|
|
164
|
+
ORDER BY n.sequence DESC
|
|
165
|
+
LIMIT ?
|
|
166
|
+
""",
|
|
167
|
+
(*params, limit),
|
|
168
|
+
)
|
|
169
|
+
return [_node(row) for row in rows]
|
|
170
|
+
|
|
171
|
+
|
|
172
|
+
def list_root(
|
|
173
|
+
store: object,
|
|
174
|
+
*,
|
|
175
|
+
org_token: str,
|
|
176
|
+
space_token: str,
|
|
177
|
+
collections_only: bool = False,
|
|
178
|
+
floating_only: bool = False,
|
|
179
|
+
limit: int = MAX_READ_ROWS,
|
|
180
|
+
) -> list[dict[str, Any]]:
|
|
181
|
+
if collections_only:
|
|
182
|
+
return list_entries(
|
|
183
|
+
store,
|
|
184
|
+
org_token=org_token,
|
|
185
|
+
space_token=space_token,
|
|
186
|
+
collections_only=True,
|
|
187
|
+
limit=limit,
|
|
188
|
+
)
|
|
189
|
+
if floating_only:
|
|
190
|
+
return list_entries(
|
|
191
|
+
store,
|
|
192
|
+
org_token=org_token,
|
|
193
|
+
space_token=space_token,
|
|
194
|
+
floating=True,
|
|
195
|
+
limit=limit,
|
|
196
|
+
)
|
|
197
|
+
collections = list_entries(
|
|
198
|
+
store,
|
|
199
|
+
org_token=org_token,
|
|
200
|
+
space_token=space_token,
|
|
201
|
+
collections_only=True,
|
|
202
|
+
limit=limit,
|
|
203
|
+
)
|
|
204
|
+
floating = list_entries(
|
|
205
|
+
store,
|
|
206
|
+
org_token=org_token,
|
|
207
|
+
space_token=space_token,
|
|
208
|
+
floating=True,
|
|
209
|
+
limit=max(0, limit - len(collections)),
|
|
210
|
+
)
|
|
211
|
+
return [*collections, *floating]
|
|
212
|
+
|
|
213
|
+
|
|
214
|
+
def get_nodes(
|
|
215
|
+
store: object,
|
|
216
|
+
node_ids: list[str],
|
|
217
|
+
*,
|
|
218
|
+
org_token: str | None = None,
|
|
219
|
+
space_token: str | None = None,
|
|
220
|
+
) -> list[dict[str, Any]]:
|
|
221
|
+
unique = list(dict.fromkeys(node_ids))
|
|
222
|
+
if not unique:
|
|
223
|
+
return []
|
|
224
|
+
if not sql_store_has_tables(store):
|
|
225
|
+
rows = getattr(store, "memuron_fs_nodes", {})
|
|
226
|
+
output = []
|
|
227
|
+
for node_id in unique:
|
|
228
|
+
row = rows.get(node_id)
|
|
229
|
+
if not row:
|
|
230
|
+
continue
|
|
231
|
+
if org_token is not None and row.get("org_token") != org_token:
|
|
232
|
+
continue
|
|
233
|
+
if space_token is not None and row.get("space_token") != space_token:
|
|
234
|
+
continue
|
|
235
|
+
output.append(_memory_node(row, store))
|
|
236
|
+
return output
|
|
237
|
+
placeholders = ", ".join("?" for _ in unique)
|
|
238
|
+
filters = [f"n.node_id IN ({placeholders})"]
|
|
239
|
+
params: list[Any] = list(unique)
|
|
240
|
+
if org_token is not None:
|
|
241
|
+
filters.append("n.org_token = ?")
|
|
242
|
+
params.append(org_token)
|
|
243
|
+
if space_token is not None:
|
|
244
|
+
filters.append("n.space_token = ?")
|
|
245
|
+
params.append(space_token)
|
|
246
|
+
rows = sql_store_fetchall(
|
|
247
|
+
store,
|
|
248
|
+
f"""
|
|
249
|
+
SELECT n.*, NULL AS placement_id, NULL AS parent_id,
|
|
250
|
+
NULL AS placement_name, NULL AS placement_metadata_json,
|
|
251
|
+
NULL AS inherit_parent_scope,
|
|
252
|
+
{_degree_sql()} AS degree
|
|
253
|
+
FROM memuron_fs_nodes n
|
|
254
|
+
WHERE {' AND '.join(filters)}
|
|
255
|
+
""",
|
|
256
|
+
tuple(params),
|
|
257
|
+
)
|
|
258
|
+
by_id = {str(row["node_id"]): _node(row) for row in rows}
|
|
259
|
+
return [by_id[node_id] for node_id in unique if node_id in by_id]
|
|
260
|
+
|
|
261
|
+
|
|
262
|
+
def grep_entries(
|
|
263
|
+
store: object,
|
|
264
|
+
query: str,
|
|
265
|
+
*,
|
|
266
|
+
org_token: str,
|
|
267
|
+
space_token: str,
|
|
268
|
+
allowed_ids: set[str] | None = None,
|
|
269
|
+
limit: int = 100,
|
|
270
|
+
literal: bool = False,
|
|
271
|
+
case_sensitive: bool = False,
|
|
272
|
+
invert: bool = False,
|
|
273
|
+
) -> list[dict[str, Any]]:
|
|
274
|
+
if not query.strip():
|
|
275
|
+
return []
|
|
276
|
+
if allowed_ids is not None and not allowed_ids:
|
|
277
|
+
return []
|
|
278
|
+
|
|
279
|
+
candidates = list_entries(
|
|
280
|
+
store,
|
|
281
|
+
org_token=org_token,
|
|
282
|
+
space_token=space_token,
|
|
283
|
+
limit=MAX_READ_ROWS,
|
|
284
|
+
)
|
|
285
|
+
if literal:
|
|
286
|
+
needle = query if case_sensitive else query.casefold()
|
|
287
|
+
|
|
288
|
+
def matches(content: str) -> bool:
|
|
289
|
+
haystack = content if case_sensitive else content.casefold()
|
|
290
|
+
return needle in haystack
|
|
291
|
+
|
|
292
|
+
else:
|
|
293
|
+
pattern = re.compile(query, 0 if case_sensitive else re.IGNORECASE)
|
|
294
|
+
|
|
295
|
+
def matches(content: str) -> bool:
|
|
296
|
+
return pattern.search(content) is not None
|
|
297
|
+
|
|
298
|
+
output = []
|
|
299
|
+
for item in candidates:
|
|
300
|
+
if allowed_ids is not None and item["id"] not in allowed_ids:
|
|
301
|
+
continue
|
|
302
|
+
if matches(item["content"]) == invert:
|
|
303
|
+
continue
|
|
304
|
+
item["score"] = 1.0
|
|
305
|
+
output.append(item)
|
|
306
|
+
if len(output) >= limit:
|
|
307
|
+
break
|
|
308
|
+
return output
|
|
309
|
+
|
|
310
|
+
|
|
311
|
+
def expand_neighbors(
|
|
312
|
+
store: object,
|
|
313
|
+
seed_ids: set[str],
|
|
314
|
+
*,
|
|
315
|
+
org_token: str,
|
|
316
|
+
space_token: str,
|
|
317
|
+
depth: int,
|
|
318
|
+
direction: str,
|
|
319
|
+
include_placements: bool,
|
|
320
|
+
include_self: bool,
|
|
321
|
+
) -> list[dict[str, Any]]:
|
|
322
|
+
visited = set(seed_ids)
|
|
323
|
+
frontier = set(seed_ids)
|
|
324
|
+
for _hop in range(depth):
|
|
325
|
+
if not frontier:
|
|
326
|
+
break
|
|
327
|
+
edge_rows = _edges_for_frontier(
|
|
328
|
+
store,
|
|
329
|
+
frontier,
|
|
330
|
+
org_token=org_token,
|
|
331
|
+
space_token=space_token,
|
|
332
|
+
direction=direction,
|
|
333
|
+
include_placements=include_placements,
|
|
334
|
+
)
|
|
335
|
+
next_ids: set[str] = set()
|
|
336
|
+
for edge in edge_rows:
|
|
337
|
+
source = str(edge["source_id"])
|
|
338
|
+
target = str(edge["target_id"])
|
|
339
|
+
if source == target:
|
|
340
|
+
continue
|
|
341
|
+
if direction in {"both", "outbound"} and source in frontier:
|
|
342
|
+
next_ids.add(target)
|
|
343
|
+
if direction in {"both", "inbound"} and target in frontier:
|
|
344
|
+
next_ids.add(source)
|
|
345
|
+
next_ids -= visited
|
|
346
|
+
visited |= next_ids
|
|
347
|
+
frontier = next_ids
|
|
348
|
+
if not include_self:
|
|
349
|
+
visited -= seed_ids
|
|
350
|
+
return get_nodes(
|
|
351
|
+
store,
|
|
352
|
+
sorted(visited),
|
|
353
|
+
org_token=org_token,
|
|
354
|
+
space_token=space_token,
|
|
355
|
+
)
|
|
356
|
+
|
|
357
|
+
|
|
358
|
+
def node_edges(
|
|
359
|
+
store: object,
|
|
360
|
+
node_ids: set[str],
|
|
361
|
+
*,
|
|
362
|
+
org_token: str,
|
|
363
|
+
space_token: str,
|
|
364
|
+
) -> list[dict[str, Any]]:
|
|
365
|
+
return _edges_for_frontier(
|
|
366
|
+
store,
|
|
367
|
+
node_ids,
|
|
368
|
+
org_token=org_token,
|
|
369
|
+
space_token=space_token,
|
|
370
|
+
direction="both",
|
|
371
|
+
include_placements=True,
|
|
372
|
+
)
|
|
373
|
+
|
|
374
|
+
|
|
375
|
+
def space_edges(
|
|
376
|
+
store: object,
|
|
377
|
+
*,
|
|
378
|
+
org_token: str,
|
|
379
|
+
space_token: str,
|
|
380
|
+
include_placements: bool = True,
|
|
381
|
+
) -> list[dict[str, Any]]:
|
|
382
|
+
if not sql_store_has_tables(store):
|
|
383
|
+
return [
|
|
384
|
+
dict(row)
|
|
385
|
+
for row in getattr(store, "memuron_fs_edges", {}).values()
|
|
386
|
+
if row["org_token"] == org_token
|
|
387
|
+
and row["space_token"] == space_token
|
|
388
|
+
and (include_placements or row["edge_type"] == "semantic_link")
|
|
389
|
+
]
|
|
390
|
+
edge_filter = "" if include_placements else " AND edge_type = 'semantic_link'"
|
|
391
|
+
return sql_store_fetchall(
|
|
392
|
+
store,
|
|
393
|
+
f"""
|
|
394
|
+
SELECT edge_id, source_id, target_id, edge_type, description
|
|
395
|
+
FROM memuron_fs_edges
|
|
396
|
+
WHERE org_token = ? AND space_token = ?{edge_filter}
|
|
397
|
+
ORDER BY sequence DESC
|
|
398
|
+
""",
|
|
399
|
+
(org_token, space_token),
|
|
400
|
+
)
|
|
401
|
+
|
|
402
|
+
|
|
403
|
+
def containing_collection(
|
|
404
|
+
store: object,
|
|
405
|
+
node_id: str,
|
|
406
|
+
*,
|
|
407
|
+
org_token: str,
|
|
408
|
+
space_token: str,
|
|
409
|
+
) -> str | None:
|
|
410
|
+
if not sql_store_has_tables(store):
|
|
411
|
+
matches = [
|
|
412
|
+
row
|
|
413
|
+
for row in getattr(store, "memuron_fs_entries", {}).values()
|
|
414
|
+
if row["child_id"] == node_id
|
|
415
|
+
and row["org_token"] == org_token
|
|
416
|
+
and row["space_token"] == space_token
|
|
417
|
+
]
|
|
418
|
+
matches.sort(key=lambda row: -int(row["sequence"]))
|
|
419
|
+
return str(matches[0]["parent_id"]) if matches else None
|
|
420
|
+
rows = sql_store_fetchall(
|
|
421
|
+
store,
|
|
422
|
+
"""
|
|
423
|
+
SELECT parent_id FROM memuron_fs_entries
|
|
424
|
+
WHERE child_id = ? AND org_token = ? AND space_token = ?
|
|
425
|
+
ORDER BY sequence DESC
|
|
426
|
+
LIMIT 1
|
|
427
|
+
""",
|
|
428
|
+
(node_id, org_token, space_token),
|
|
429
|
+
)
|
|
430
|
+
return str(rows[0]["parent_id"]) if rows else None
|
|
431
|
+
|
|
432
|
+
|
|
433
|
+
def _edges_for_frontier(
|
|
434
|
+
store: object,
|
|
435
|
+
frontier: set[str],
|
|
436
|
+
*,
|
|
437
|
+
org_token: str,
|
|
438
|
+
space_token: str,
|
|
439
|
+
direction: str,
|
|
440
|
+
include_placements: bool,
|
|
441
|
+
) -> list[dict[str, Any]]:
|
|
442
|
+
if not frontier:
|
|
443
|
+
return []
|
|
444
|
+
if not sql_store_has_tables(store):
|
|
445
|
+
output = []
|
|
446
|
+
for row in getattr(store, "memuron_fs_edges", {}).values():
|
|
447
|
+
if row["org_token"] != org_token or row["space_token"] != space_token:
|
|
448
|
+
continue
|
|
449
|
+
if not include_placements and row["edge_type"] != "semantic_link":
|
|
450
|
+
continue
|
|
451
|
+
source_match = direction in {"both", "outbound"} and row["source_id"] in frontier
|
|
452
|
+
target_match = direction in {"both", "inbound"} and row["target_id"] in frontier
|
|
453
|
+
if source_match or target_match:
|
|
454
|
+
output.append(dict(row))
|
|
455
|
+
return output
|
|
456
|
+
placeholders = ", ".join("?" for _ in frontier)
|
|
457
|
+
directions: list[str] = []
|
|
458
|
+
params: list[Any] = [org_token, space_token]
|
|
459
|
+
sorted_ids = sorted(frontier)
|
|
460
|
+
if direction in {"both", "outbound"}:
|
|
461
|
+
directions.append(f"source_id IN ({placeholders})")
|
|
462
|
+
params.extend(sorted_ids)
|
|
463
|
+
if direction in {"both", "inbound"}:
|
|
464
|
+
directions.append(f"target_id IN ({placeholders})")
|
|
465
|
+
params.extend(sorted_ids)
|
|
466
|
+
edge_filter = "" if include_placements else " AND edge_type = 'semantic_link'"
|
|
467
|
+
return sql_store_fetchall(
|
|
468
|
+
store,
|
|
469
|
+
f"""
|
|
470
|
+
SELECT edge_id, source_id, target_id, edge_type, description
|
|
471
|
+
FROM memuron_fs_edges
|
|
472
|
+
WHERE org_token = ? AND space_token = ?
|
|
473
|
+
AND ({' OR '.join(directions)}){edge_filter}
|
|
474
|
+
""",
|
|
475
|
+
tuple(params),
|
|
476
|
+
)
|
|
477
|
+
|
|
478
|
+
|
|
479
|
+
def _memory_degree(store: object, node_id: str, org_token: str, space_token: str) -> int:
|
|
480
|
+
return sum(
|
|
481
|
+
1
|
|
482
|
+
for row in getattr(store, "memuron_fs_edges", {}).values()
|
|
483
|
+
if row["org_token"] == org_token
|
|
484
|
+
and row["space_token"] == space_token
|
|
485
|
+
and node_id in {row["source_id"], row["target_id"]}
|
|
486
|
+
and row["source_id"] != row["target_id"]
|
|
487
|
+
)
|
|
488
|
+
|
|
489
|
+
|
|
490
|
+
def _memory_node(
|
|
491
|
+
row: dict[str, Any],
|
|
492
|
+
store: object,
|
|
493
|
+
placement: dict[str, Any] | None = None,
|
|
494
|
+
) -> dict[str, Any]:
|
|
495
|
+
return _node(
|
|
496
|
+
{
|
|
497
|
+
"node_id": row["node_id"],
|
|
498
|
+
"node_type": row["node_type"],
|
|
499
|
+
"display": row["display"],
|
|
500
|
+
"content": row["content"],
|
|
501
|
+
"encoding": row["encoding"],
|
|
502
|
+
"payload_json": row.get("payload", {}),
|
|
503
|
+
"metadata_json": row.get("metadata", {}),
|
|
504
|
+
"scope_json": row.get("scope", []),
|
|
505
|
+
"degree": _memory_degree(
|
|
506
|
+
store,
|
|
507
|
+
str(row["node_id"]),
|
|
508
|
+
str(row["org_token"]),
|
|
509
|
+
str(row["space_token"]),
|
|
510
|
+
),
|
|
511
|
+
"placement_id": placement.get("placement_id") if placement else None,
|
|
512
|
+
"parent_id": placement.get("parent_id") if placement else None,
|
|
513
|
+
"placement_name": placement.get("name") if placement else None,
|
|
514
|
+
"placement_metadata_json": placement.get("metadata") if placement else None,
|
|
515
|
+
"inherit_parent_scope": placement.get("inherit_parent_scope") if placement else None,
|
|
516
|
+
}
|
|
517
|
+
)
|
|
518
|
+
|
|
519
|
+
|
|
520
|
+
def _memory_list_entries(
|
|
521
|
+
store: object,
|
|
522
|
+
*,
|
|
523
|
+
org_token: str,
|
|
524
|
+
space_token: str,
|
|
525
|
+
collection_id: str | None,
|
|
526
|
+
node_type: str | None,
|
|
527
|
+
encoding: str | None,
|
|
528
|
+
node_id: str | None,
|
|
529
|
+
name: str | None,
|
|
530
|
+
floating: bool,
|
|
531
|
+
collections_only: bool,
|
|
532
|
+
limit: int,
|
|
533
|
+
) -> list[dict[str, Any]]:
|
|
534
|
+
nodes = getattr(store, "memuron_fs_nodes", {})
|
|
535
|
+
placements = getattr(store, "memuron_fs_entries", {})
|
|
536
|
+
placed_ids = {
|
|
537
|
+
row["child_id"]
|
|
538
|
+
for row in placements.values()
|
|
539
|
+
if row["org_token"] == org_token and row["space_token"] == space_token
|
|
540
|
+
}
|
|
541
|
+
output: list[dict[str, Any]] = []
|
|
542
|
+
if collection_id:
|
|
543
|
+
rows = sorted(
|
|
544
|
+
(
|
|
545
|
+
row
|
|
546
|
+
for row in placements.values()
|
|
547
|
+
if row["parent_id"] == collection_id
|
|
548
|
+
and row["org_token"] == org_token
|
|
549
|
+
and row["space_token"] == space_token
|
|
550
|
+
),
|
|
551
|
+
key=lambda row: -int(row["sequence"]),
|
|
552
|
+
)
|
|
553
|
+
candidates = [(nodes.get(row["child_id"]), row) for row in rows]
|
|
554
|
+
else:
|
|
555
|
+
candidates = [(row, None) for row in nodes.values()]
|
|
556
|
+
for row, placement in candidates:
|
|
557
|
+
if not row or row["org_token"] != org_token or row["space_token"] != space_token:
|
|
558
|
+
continue
|
|
559
|
+
if node_type and row["node_type"] != node_type:
|
|
560
|
+
continue
|
|
561
|
+
if encoding and row["encoding"] != encoding:
|
|
562
|
+
continue
|
|
563
|
+
if node_id and row["node_id"] != node_id:
|
|
564
|
+
continue
|
|
565
|
+
if collections_only and row["node_type"] != "collection":
|
|
566
|
+
continue
|
|
567
|
+
if floating and (row["node_type"] == "collection" or row["node_id"] in placed_ids):
|
|
568
|
+
continue
|
|
569
|
+
if name and name.replace("*", "").lower() not in row["display"].lower():
|
|
570
|
+
continue
|
|
571
|
+
output.append(_memory_node(row, store, placement))
|
|
572
|
+
if len(output) >= limit:
|
|
573
|
+
break
|
|
574
|
+
return output
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""Guardian ingest planning, prompts, and asynchronous job workers."""
|