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,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."""