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,155 @@
1
+ """Space resolution helpers."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from typing import Any
6
+
7
+ from fastapi import HTTPException
8
+ from artha_engine.runtime.auth import AuthContext
9
+
10
+ from memuron.persistence.identity_store import IdentityStore
11
+
12
+
13
+ def resolve_space_reference(
14
+ identity: IdentityStore,
15
+ *,
16
+ org_id: str,
17
+ space_ref: str,
18
+ ) -> dict[str, Any] | None:
19
+ """Resolve a space UUID, slug, token, or filesystem path."""
20
+
21
+ value = space_ref.strip().rstrip("/")
22
+ if value.startswith("/spaces/"):
23
+ value = value.removeprefix("/spaces/")
24
+ spaces = identity.ensure_default_spaces(org_id)
25
+ for space in spaces:
26
+ if value in {
27
+ str(space["id"]),
28
+ str(space["slug"]),
29
+ str(space["token"]),
30
+ }:
31
+ return space
32
+ return None
33
+
34
+
35
+ def resolve_ingest_hint_space(
36
+ identity: IdentityStore,
37
+ *,
38
+ user_id: str,
39
+ org_id: str,
40
+ space_id: str | None,
41
+ ) -> dict[str, Any]:
42
+ enabled = identity.get_enabled_org_spaces(user_id, org_id)
43
+ if not enabled:
44
+ raise HTTPException(
45
+ status_code=400,
46
+ detail="Enable at least one space before ingesting memories",
47
+ )
48
+ enabled_by_id = {space["id"]: space for space in enabled}
49
+ if space_id:
50
+ space = resolve_space_reference(
51
+ identity,
52
+ org_id=org_id,
53
+ space_ref=space_id,
54
+ )
55
+ if space is None:
56
+ raise HTTPException(status_code=404, detail="Space not found")
57
+ if space["id"] not in enabled_by_id:
58
+ raise HTTPException(status_code=400, detail="That space is disabled — enable it first")
59
+ return enabled_by_id[space["id"]]
60
+ for space in enabled:
61
+ if space.get("is_default"):
62
+ return space
63
+ return enabled[0]
64
+
65
+
66
+ def _default_hint_space(enabled: list[dict[str, Any]]) -> dict[str, Any]:
67
+ for space in enabled:
68
+ if space.get("is_default"):
69
+ return space
70
+ return enabled[0]
71
+
72
+
73
+ def guardian_space_context(
74
+ identity: IdentityStore,
75
+ *,
76
+ user_id: str,
77
+ org_id: str,
78
+ hint_space: dict[str, Any] | None = None,
79
+ space_mode: str = "assist",
80
+ ) -> dict[str, str]:
81
+ enabled = identity.get_enabled_org_spaces(user_id, org_id)
82
+ if not enabled:
83
+ raise HTTPException(
84
+ status_code=400,
85
+ detail="Enable at least one space before ingesting memories",
86
+ )
87
+ enabled_ids = {space["id"] for space in enabled}
88
+ if hint_space is None:
89
+ hint_space = _default_hint_space(enabled)
90
+ elif hint_space["id"] not in enabled_ids:
91
+ hint_space = _default_hint_space(enabled)
92
+
93
+ detail_lines: list[str] = []
94
+ registered_tokens: list[str] = []
95
+ for space in enabled:
96
+ registered_tokens.append(space["token"])
97
+ summary = space["description"] or space["name"]
98
+ prompt = space["guardian_prompt"] or summary
99
+ detail_lines.append(f"- {space['name']} ({space['token']}): {prompt}")
100
+
101
+ other_lines = [
102
+ f"- {space['name']} ({space['token']}): {space['description'] or space['name']}"
103
+ for space in enabled
104
+ if space["id"] != hint_space["id"]
105
+ ]
106
+ return {
107
+ "active_space_name": hint_space["name"],
108
+ "active_space_token": hint_space["token"],
109
+ "active_space_prompt": hint_space["guardian_prompt"],
110
+ "other_spaces_summary": "\n".join(other_lines) if other_lines else "(none)",
111
+ "all_spaces_detail": "\n".join(detail_lines) if detail_lines else "(none)",
112
+ "registered_tokens": ", ".join(registered_tokens),
113
+ "space_mode": space_mode,
114
+ }
115
+
116
+
117
+ def apply_guardian_space_scope(
118
+ scope: list[str],
119
+ *,
120
+ space_context: dict[str, str],
121
+ ) -> list[str]:
122
+ registered = [
123
+ token.strip()
124
+ for token in space_context.get("registered_tokens", "").split(",")
125
+ if token.strip()
126
+ ]
127
+ registered_set = set(registered)
128
+ active = space_context.get("active_space_token")
129
+ mode = space_context.get("space_mode", "assist")
130
+
131
+ space_tokens = [token for token in scope if token in registered_set]
132
+ other_tokens = [token for token in scope if token not in registered_set]
133
+
134
+ if mode == "strict" and active:
135
+ if active not in space_tokens:
136
+ space_tokens.insert(0, active)
137
+ elif not space_tokens and active:
138
+ space_tokens = [active]
139
+
140
+ if not space_tokens and registered:
141
+ raise ValueError("Guardian must assign at least one enabled space token")
142
+
143
+ if len(space_tokens) > 1:
144
+ if mode == "strict" and active and active in space_tokens:
145
+ space_tokens = [active]
146
+ else:
147
+ space_tokens = [space_tokens[0]]
148
+
149
+ merged: list[str] = []
150
+ seen: set[str] = set()
151
+ for token in [*space_tokens, *other_tokens]:
152
+ if token not in seen:
153
+ seen.add(token)
154
+ merged.append(token)
155
+ return merged
@@ -0,0 +1,25 @@
1
+ """Folder sync helpers."""
2
+
3
+ from memuron.sync.folder import (
4
+ DEFAULT_MANIFEST_RELATIVE_PATH,
5
+ SyncManifest,
6
+ SyncPlan,
7
+ init_manifest,
8
+ load_manifest,
9
+ manifest_path_for_root,
10
+ plan_folder_sync,
11
+ run_folder_sync,
12
+ scan_folder,
13
+ )
14
+
15
+ __all__ = [
16
+ "DEFAULT_MANIFEST_RELATIVE_PATH",
17
+ "SyncManifest",
18
+ "SyncPlan",
19
+ "init_manifest",
20
+ "load_manifest",
21
+ "manifest_path_for_root",
22
+ "plan_folder_sync",
23
+ "run_folder_sync",
24
+ "scan_folder",
25
+ ]