mesa-memory 0.9.0__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 (157) hide show
  1. mesa_api/__init__.py +9 -0
  2. mesa_api/admission.py +23 -0
  3. mesa_api/identifier_validation.py +67 -0
  4. mesa_api/routers/control/router.py +276 -0
  5. mesa_api/schemas.py +16 -0
  6. mesa_api/v4_router.py +1624 -0
  7. mesa_client/__init__.py +27 -0
  8. mesa_client/client.py +1265 -0
  9. mesa_mcp/__init__.py +5 -0
  10. mesa_mcp/adapter.py +226 -0
  11. mesa_mcp/antigravity_bridge.py +466 -0
  12. mesa_mcp/antigravity_cli.py +334 -0
  13. mesa_mcp/bounded_cache.py +133 -0
  14. mesa_mcp/codex_cli.py +631 -0
  15. mesa_mcp/codex_hooks.py +161 -0
  16. mesa_mcp/configuration.py +100 -0
  17. mesa_mcp/errors.py +24 -0
  18. mesa_mcp/gateway/__init__.py +1 -0
  19. mesa_mcp/gateway/anomaly.py +30 -0
  20. mesa_mcp/gateway/app.py +270 -0
  21. mesa_mcp/gateway/approval.py +132 -0
  22. mesa_mcp/gateway/auth.py +32 -0
  23. mesa_mcp/gateway/codex_transport.py +148 -0
  24. mesa_mcp/gateway/middleware.py +182 -0
  25. mesa_mcp/gateway/operations.py +955 -0
  26. mesa_mcp/gateway/policy/__init__.py +1 -0
  27. mesa_mcp/gateway/policy/engine.py +80 -0
  28. mesa_mcp/gateway/policy/simulator.py +45 -0
  29. mesa_mcp/http_service.py +63 -0
  30. mesa_mcp/local_cli.py +374 -0
  31. mesa_mcp/security.py +68 -0
  32. mesa_mcp/server.py +317 -0
  33. mesa_mcp/service.py +25 -0
  34. mesa_mcp/v4_service.py +582 -0
  35. mesa_mcp/workspace.py +23 -0
  36. mesa_memory/adapter/base.py +49 -0
  37. mesa_memory/adapter/claude.py +220 -0
  38. mesa_memory/adapter/data/cl100k_base.tiktoken +100256 -0
  39. mesa_memory/adapter/endpoints.py +14 -0
  40. mesa_memory/adapter/factory.py +288 -0
  41. mesa_memory/adapter/live.py +274 -0
  42. mesa_memory/adapter/ollama.py +113 -0
  43. mesa_memory/adapter/tokenizer.py +98 -0
  44. mesa_memory/api/middleware.py +91 -0
  45. mesa_memory/api/server.py +926 -0
  46. mesa_memory/config.py +993 -0
  47. mesa_memory/consolidation/loop.py +1179 -0
  48. mesa_memory/consolidation/parser.py +271 -0
  49. mesa_memory/consolidation/policy.py +173 -0
  50. mesa_memory/consolidation/router.py +392 -0
  51. mesa_memory/consolidation/schemas.py +255 -0
  52. mesa_memory/consolidation/validator.py +286 -0
  53. mesa_memory/container_health.py +25 -0
  54. mesa_memory/context_builder.py +693 -0
  55. mesa_memory/document_chunking.py +99 -0
  56. mesa_memory/embedding/__init__.py +21 -0
  57. mesa_memory/embedding/service.py +752 -0
  58. mesa_memory/extraction/service.py +614 -0
  59. mesa_memory/graph/__init__.py +5 -0
  60. mesa_memory/graph/projector.py +43 -0
  61. mesa_memory/observability/http.py +122 -0
  62. mesa_memory/observability/logger.py +259 -0
  63. mesa_memory/observability/metrics.py +252 -0
  64. mesa_memory/rebuild_runner.py +505 -0
  65. mesa_memory/retrieval/adaptive.py +311 -0
  66. mesa_memory/retrieval/core.py +2 -0
  67. mesa_memory/retrieval/legal_resolver.py +15 -0
  68. mesa_memory/runtime_entrypoint.py +49 -0
  69. mesa_memory/security/admin_cli.py +204 -0
  70. mesa_memory/security/api_keys.py +267 -0
  71. mesa_memory/security/input_validation.py +50 -0
  72. mesa_memory/security/rbac.py +787 -0
  73. mesa_memory/security/rbac_constants.py +25 -0
  74. mesa_memory/security/untrusted_memory.py +45 -0
  75. mesa_memory/utils.py +36 -0
  76. mesa_memory-0.9.0.dist-info/METADATA +213 -0
  77. mesa_memory-0.9.0.dist-info/RECORD +157 -0
  78. mesa_memory-0.9.0.dist-info/WHEEL +5 -0
  79. mesa_memory-0.9.0.dist-info/entry_points.txt +8 -0
  80. mesa_memory-0.9.0.dist-info/licenses/LICENSE +21 -0
  81. mesa_memory-0.9.0.dist-info/top_level.txt +6 -0
  82. mesa_storage/__init__.py +42 -0
  83. mesa_storage/alembic/README +1 -0
  84. mesa_storage/alembic/env.py +133 -0
  85. mesa_storage/alembic/script.py.mako +28 -0
  86. mesa_storage/alembic/versions/076eef5d1b6c_add_daily_limits.py +34 -0
  87. mesa_storage/alembic/versions/087de6628c51_add_mcp_activity_approval.py +81 -0
  88. mesa_storage/alembic/versions/0a7b8c9d0e1f_scope_catalog_physical_ids.py +42 -0
  89. mesa_storage/alembic/versions/1e7a061f7f9e_add_mcp_clients_and_bindings.py +61 -0
  90. mesa_storage/alembic/versions/41402f316580_add_temporal_validity_to_nodes.py +30 -0
  91. mesa_storage/alembic/versions/4933fb5fd0ea_initial_schema.py +143 -0
  92. mesa_storage/alembic/versions/9a1b2c3d4e5f_add_v4_catalog_provenance.py +367 -0
  93. mesa_storage/alembic/versions/9c6c7ae69ed7_add_mcp_policy_rules.py +41 -0
  94. mesa_storage/alembic/versions/a1d2e3f4b5c6_add_wal_projection_states.py +53 -0
  95. mesa_storage/alembic/versions/a2b3c4d5e6f7_add_declared_content_hash.py +24 -0
  96. mesa_storage/alembic/versions/a94df5d14fce_add_connections_and_settings.py +55 -0
  97. mesa_storage/alembic/versions/b2e3f4a5c6d7_add_session_finalization_journal.py +32 -0
  98. mesa_storage/alembic/versions/b3c4d5e6f7a8_allow_unknown_revision_content_hash.py +29 -0
  99. mesa_storage/alembic/versions/bb2355d0cdd4_add_epistemic_columns.py +38 -0
  100. mesa_storage/alembic/versions/c3d4e5f6a7b8_replace_daily_limit_credential_subject.py +45 -0
  101. mesa_storage/alembic/versions/c4d5e6f7a8b9_add_assertion_fts_and_representation.py +119 -0
  102. mesa_storage/alembic/versions/c4f1a8e2d9b0_add_purge_journal.py +62 -0
  103. mesa_storage/alembic/versions/c5d6e7f8a9b0_add_mcp_operation_ledger.py +58 -0
  104. mesa_storage/alembic/versions/d4e5f6a7b8c9_add_v4_mutation_ledger.py +96 -0
  105. mesa_storage/alembic/versions/d5e6f7a8b9c0_add_typed_legal_identity.py +65 -0
  106. mesa_storage/alembic/versions/d6e7f8a9b0c1_add_mcp_client_credentials.py +35 -0
  107. mesa_storage/alembic/versions/e7f8a9b0c1d2_add_mcp_codex_profiles.py +33 -0
  108. mesa_storage/alembic/versions/e9b7c3a1d4f2_add_claim_leases.py +61 -0
  109. mesa_storage/alembic/versions/f6d4a7b8c9e0_add_dispatch_journal.py +48 -0
  110. mesa_storage/alembic/versions/f7e5b9c0d1a2_add_dispatch_queue_payload_bytes.py +33 -0
  111. mesa_storage/alembic/versions/f8a6c0d1e2b3_add_dispatch_completion_receipts.py +44 -0
  112. mesa_storage/alembic/versions/f8a9b0c1d2e3_generalize_mcp_context_profiles.py +41 -0
  113. mesa_storage/alembic/versions/f9a0b1c2d3e4_enforce_catalog_scope_integrity.py +44 -0
  114. mesa_storage/alembic/versions/fa1b2c3d4e5f_add_mcp_credential_expiry.py +16 -0
  115. mesa_storage/alembic/versions/fb2c3d4e5f6a_add_revision_manifest_hash.py +18 -0
  116. mesa_storage/alembic/versions/fc3d4e5f6a7b_add_durable_projection_operations.py +172 -0
  117. mesa_storage/alembic/versions/fd4e5f6a7b8c_add_embedding_provider_identity.py +20 -0
  118. mesa_storage/alembic/versions/fe5f6a7b8c9d_enforce_active_head_invariant.py +35 -0
  119. mesa_storage/alembic/versions/ff6a7b8c9d0e_freeze_revision_manifests.py +29 -0
  120. mesa_storage/alembic.ini +148 -0
  121. mesa_storage/control/__init__.py +0 -0
  122. mesa_storage/control/activity_repo.py +159 -0
  123. mesa_storage/control/approval_repo.py +141 -0
  124. mesa_storage/control/client_repo.py +175 -0
  125. mesa_storage/control/codex_profile_repo.py +105 -0
  126. mesa_storage/control/connection_repo.py +118 -0
  127. mesa_storage/control/credential_repo.py +196 -0
  128. mesa_storage/control/policy_repo.py +81 -0
  129. mesa_storage/control/settings_repo.py +56 -0
  130. mesa_storage/dao.py +10747 -0
  131. mesa_storage/embedding_identity.py +157 -0
  132. mesa_storage/kuzu_migration.py +450 -0
  133. mesa_storage/kuzu_provider.py +1768 -0
  134. mesa_storage/kuzu_schema_migration.py +340 -0
  135. mesa_storage/kuzu_setup.py +207 -0
  136. mesa_storage/legal_identity.py +473 -0
  137. mesa_storage/projection_generations.py +728 -0
  138. mesa_storage/rebuild_cutover.py +573 -0
  139. mesa_storage/rebuild_health.py +112 -0
  140. mesa_storage/rebuild_observability.py +92 -0
  141. mesa_storage/rebuild_preparation.py +533 -0
  142. mesa_storage/rebuild_replay.py +745 -0
  143. mesa_storage/recovery.py +503 -0
  144. mesa_storage/repositories/__init__.py +20 -0
  145. mesa_storage/repositories/catalog.py +230 -0
  146. mesa_storage/repositories/operations.py +690 -0
  147. mesa_storage/representation.py +99 -0
  148. mesa_storage/retrieval_scope.py +160 -0
  149. mesa_storage/schema_contract.py +453 -0
  150. mesa_storage/schemas.py +292 -0
  151. mesa_storage/sqlite_engine.py +498 -0
  152. mesa_storage/vector_engine.py +1697 -0
  153. mesa_storage/writer_lock.py +116 -0
  154. mesa_workers/__init__.py +6 -0
  155. mesa_workers/ingestion_worker.py +465 -0
  156. mesa_workers/projection_worker.py +322 -0
  157. mesa_workers/supervision.py +124 -0
@@ -0,0 +1,118 @@
1
+ import logging
2
+ from datetime import datetime, timezone
3
+ from typing import Any
4
+
5
+ from mesa_storage.sqlite_engine import AsyncEngine
6
+
7
+ logger = logging.getLogger(__name__)
8
+
9
+
10
+ class ConnectionRepository:
11
+ def __init__(self, sqlite_engine: AsyncEngine):
12
+ self._sql = sqlite_engine
13
+
14
+ async def register_connection(
15
+ self, connection_id: str, client_id: str, transport: str, **kwargs: Any
16
+ ) -> None:
17
+ now = datetime.now(timezone.utc).isoformat()
18
+ status = kwargs.get("status", "CONNECTED")
19
+
20
+ async with self._sql.transaction() as db:
21
+ await db.execute(
22
+ """
23
+ INSERT INTO mcp_connections (
24
+ connection_id, client_id, transport, status, connected_at, last_seen_at,
25
+ remote_address_hash, protocol_version, client_version, user_agent, session_id, project_id
26
+ ) VALUES (
27
+ :connection_id, :client_id, :transport, :status, :now, :now,
28
+ :remote_address_hash, :protocol_version, :client_version, :user_agent, :session_id, :project_id
29
+ )
30
+ """,
31
+ {
32
+ "connection_id": connection_id,
33
+ "client_id": client_id,
34
+ "transport": transport,
35
+ "status": status,
36
+ "now": now,
37
+ "remote_address_hash": kwargs.get("remote_address_hash"),
38
+ "protocol_version": kwargs.get("protocol_version"),
39
+ "client_version": kwargs.get("client_version"),
40
+ "user_agent": kwargs.get("user_agent"),
41
+ "session_id": kwargs.get("session_id"),
42
+ "project_id": kwargs.get("project_id"),
43
+ },
44
+ )
45
+ await db.commit()
46
+
47
+ async def update_connection_status(self, connection_id: str, status: str) -> None:
48
+ now = datetime.now(timezone.utc).isoformat()
49
+ disconnected_at = (
50
+ now if status in ("DISCONNECTED", "REVOKED", "ERROR") else None
51
+ )
52
+
53
+ async with self._sql.transaction() as db:
54
+ await db.execute(
55
+ """
56
+ UPDATE mcp_connections
57
+ SET status = :status, last_seen_at = :now, disconnected_at = COALESCE(disconnected_at, :disconnected_at)
58
+ WHERE connection_id = :connection_id
59
+ """,
60
+ {
61
+ "connection_id": connection_id,
62
+ "status": status,
63
+ "now": now,
64
+ "disconnected_at": disconnected_at,
65
+ },
66
+ )
67
+ await db.commit()
68
+
69
+ async def get_connection(self, connection_id: str) -> dict[str, Any] | None:
70
+ async with self._sql.connection() as db:
71
+ db.row_factory = __import__("aiosqlite").Row
72
+ async with db.execute(
73
+ "SELECT * FROM mcp_connections WHERE connection_id = :connection_id",
74
+ {"connection_id": connection_id},
75
+ ) as cursor:
76
+ row = await cursor.fetchone()
77
+ return dict(row) if row else None
78
+
79
+ async def list_active_connections(
80
+ self, client_id: str | None = None
81
+ ) -> list[dict[str, Any]]:
82
+ async with self._sql.connection() as db:
83
+ db.row_factory = __import__("aiosqlite").Row
84
+ query = "SELECT * FROM mcp_connections WHERE status = 'CONNECTED'"
85
+ params: dict[str, Any] = {}
86
+ if client_id:
87
+ query += " AND client_id = :client_id"
88
+ params["client_id"] = client_id
89
+
90
+ async with db.execute(query, params) as cursor:
91
+ rows = await cursor.fetchall()
92
+ return [dict(row) for row in rows]
93
+
94
+ async def list_all_connections(
95
+ self, limit: int = 50, status: str | None = None
96
+ ) -> list[dict[str, Any]]:
97
+ query = "SELECT * FROM mcp_connections WHERE 1=1"
98
+ params: dict[str, Any] = {}
99
+ if status:
100
+ query += " AND status = :status"
101
+ params["status"] = status
102
+ query += " ORDER BY connected_at DESC LIMIT :limit"
103
+ params["limit"] = min(limit, 200)
104
+
105
+ async with self._sql.connection() as db:
106
+ db.row_factory = __import__("aiosqlite").Row
107
+ async with db.execute(query, params) as cursor:
108
+ rows = await cursor.fetchall()
109
+ return [dict(row) for row in rows]
110
+
111
+ async def count_by_status(self) -> dict[str, int]:
112
+ async with self._sql.connection() as db:
113
+ db.row_factory = __import__("aiosqlite").Row
114
+ async with db.execute(
115
+ "SELECT status, COUNT(*) as cnt FROM mcp_connections GROUP BY status"
116
+ ) as cursor:
117
+ rows = await cursor.fetchall()
118
+ return {row["status"]: row["cnt"] for row in rows}
@@ -0,0 +1,196 @@
1
+ """Durable, binding-scoped bearer credentials for HTTP MCP clients."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import hashlib
6
+ import secrets
7
+ import uuid
8
+ from datetime import datetime, timedelta, timezone
9
+ from typing import Any
10
+
11
+ from mesa_storage.sqlite_engine import AsyncEngine
12
+
13
+ DEFAULT_CREDENTIAL_TTL_SECONDS = 90 * 24 * 60 * 60
14
+
15
+
16
+ class CredentialRepository:
17
+ """Issue and verify high-entropy credentials without retaining their secret."""
18
+
19
+ def __init__(self, sqlite_engine: AsyncEngine):
20
+ self._sql = sqlite_engine
21
+
22
+ async def issue(
23
+ self,
24
+ client_id: str,
25
+ binding_id: str,
26
+ *,
27
+ token_kind: str = "codex",
28
+ expires_in_seconds: int = DEFAULT_CREDENTIAL_TTL_SECONDS,
29
+ ) -> tuple[dict[str, Any], str]:
30
+ credential_id = f"cred_{uuid.uuid4().hex}"
31
+ secret = secrets.token_urlsafe(32)
32
+ if token_kind not in {"codex", "antigravity"}:
33
+ raise ValueError("unsupported credential token kind")
34
+ if expires_in_seconds <= 0:
35
+ raise ValueError("expires_in_seconds must be positive")
36
+ token = f"mesa_{token_kind}_{credential_id}_{secret}"
37
+ now = datetime.now(timezone.utc).isoformat()
38
+ expires_at = (
39
+ datetime.now(timezone.utc) + timedelta(seconds=expires_in_seconds)
40
+ ).isoformat()
41
+ record = {
42
+ "credential_id": credential_id,
43
+ "client_id": client_id,
44
+ "binding_id": binding_id,
45
+ "token_hash": _token_hash(token),
46
+ "token_prefix": token[:24],
47
+ "status": "ACTIVE",
48
+ "created_at": now,
49
+ "expires_at": expires_at,
50
+ }
51
+ async with self._sql.transaction() as db:
52
+ await db.execute(
53
+ """
54
+ INSERT INTO mcp_client_credentials
55
+ (credential_id, client_id, binding_id, token_hash, token_prefix,
56
+ status, created_at, updated_at, expires_at)
57
+ VALUES
58
+ (:credential_id, :client_id, :binding_id, :token_hash, :token_prefix,
59
+ :status, :created_at, :created_at, :expires_at)
60
+ """,
61
+ record,
62
+ )
63
+ await db.commit()
64
+ return record, token
65
+
66
+ async def rotate(
67
+ self,
68
+ credential_id: str,
69
+ *,
70
+ expires_in_seconds: int = DEFAULT_CREDENTIAL_TTL_SECONDS,
71
+ ) -> tuple[dict[str, Any], str]:
72
+ """Atomically replace one active credential within its existing binding."""
73
+ if expires_in_seconds <= 0:
74
+ raise ValueError("expires_in_seconds must be positive")
75
+ replacement_id = f"cred_{uuid.uuid4().hex}"
76
+ secret = secrets.token_urlsafe(32)
77
+ now = datetime.now(timezone.utc).isoformat()
78
+ expires_at = (
79
+ datetime.now(timezone.utc) + timedelta(seconds=expires_in_seconds)
80
+ ).isoformat()
81
+ async with self._sql.transaction() as db:
82
+ async with db.execute(
83
+ "SELECT client_id, binding_id, token_prefix FROM mcp_client_credentials "
84
+ "WHERE credential_id = ? AND status = 'ACTIVE'",
85
+ (credential_id,),
86
+ ) as cursor:
87
+ previous = await cursor.fetchone()
88
+ if previous is None:
89
+ raise ValueError("active credential not found")
90
+ token_kind = _token_kind_from_prefix(str(previous[2]))
91
+ token = f"mesa_{token_kind}_{replacement_id}_{secret}"
92
+ record: dict[str, Any] = {
93
+ "credential_id": replacement_id,
94
+ "client_id": str(previous[0]),
95
+ "binding_id": str(previous[1]),
96
+ "token_hash": _token_hash(token),
97
+ "token_prefix": token[:24],
98
+ "status": "ACTIVE",
99
+ "created_at": now,
100
+ "expires_at": expires_at,
101
+ }
102
+ await db.execute(
103
+ "INSERT INTO mcp_client_credentials "
104
+ "(credential_id, client_id, binding_id, token_hash, token_prefix, status, created_at, updated_at, expires_at) "
105
+ "VALUES (:credential_id, :client_id, :binding_id, :token_hash, :token_prefix, "
106
+ ":status, :created_at, :created_at, :expires_at)",
107
+ record,
108
+ )
109
+ cursor = await db.execute(
110
+ "UPDATE mcp_client_credentials SET status = 'REVOKED', revoked_at = :now, updated_at = :now "
111
+ "WHERE credential_id = :credential_id AND status = 'ACTIVE'",
112
+ {"credential_id": credential_id, "now": now},
113
+ )
114
+ if cursor.rowcount != 1:
115
+ raise RuntimeError("credential rotation fence lost")
116
+ await db.commit()
117
+ return record, token
118
+
119
+ async def authenticate(self, token: str) -> dict[str, Any] | None:
120
+ digest = _token_hash(token)
121
+ async with self._sql.connection() as db:
122
+ db.row_factory = __import__("aiosqlite").Row
123
+ async with db.execute(
124
+ """
125
+ SELECT credential.*, client.enabled AS client_enabled, binding.enabled AS binding_enabled
126
+ FROM mcp_client_credentials AS credential
127
+ JOIN mcp_clients AS client ON client.client_id = credential.client_id
128
+ JOIN mcp_project_bindings AS binding ON binding.binding_id = credential.binding_id
129
+ WHERE credential.token_hash = :token_hash AND credential.status = 'ACTIVE'
130
+ AND (credential.expires_at IS NULL OR credential.expires_at > :now)
131
+ """,
132
+ {"token_hash": digest, "now": datetime.now(timezone.utc).isoformat()},
133
+ ) as cursor:
134
+ row = await cursor.fetchone()
135
+ if row is None:
136
+ return None
137
+ result = dict(row)
138
+ if not result["client_enabled"] or not result["binding_enabled"]:
139
+ return None
140
+ now = datetime.now(timezone.utc).isoformat()
141
+ async with self._sql.transaction() as db:
142
+ await db.execute(
143
+ "UPDATE mcp_client_credentials SET last_used_at = :now, updated_at = :now WHERE credential_id = :credential_id",
144
+ {"now": now, "credential_id": result["credential_id"]},
145
+ )
146
+ await db.commit()
147
+ return result
148
+
149
+ async def revoke(self, credential_id: str) -> bool:
150
+ now = datetime.now(timezone.utc).isoformat()
151
+ async with self._sql.transaction() as db:
152
+ cursor = await db.execute(
153
+ """UPDATE mcp_client_credentials
154
+ SET status = 'REVOKED', revoked_at = :now, updated_at = :now
155
+ WHERE credential_id = :credential_id AND status = 'ACTIVE'""",
156
+ {"credential_id": credential_id, "now": now},
157
+ )
158
+ await db.commit()
159
+ return bool(cursor.rowcount)
160
+
161
+ async def list_for_binding(self, binding_id: str) -> list[dict[str, Any]]:
162
+ """Return secret-safe credential summaries; token hashes stay private."""
163
+ async with self._sql.connection() as db:
164
+ db.row_factory = __import__("aiosqlite").Row
165
+ async with db.execute(
166
+ """SELECT credential_id, client_id, binding_id, token_prefix, status,
167
+ created_at, updated_at, last_used_at, revoked_at, expires_at
168
+ FROM mcp_client_credentials
169
+ WHERE binding_id = ? ORDER BY created_at DESC""",
170
+ (binding_id,),
171
+ ) as cursor:
172
+ return [dict(row) for row in await cursor.fetchall()]
173
+
174
+ async def get_summary(self, credential_id: str) -> dict[str, Any] | None:
175
+ async with self._sql.connection() as db:
176
+ db.row_factory = __import__("aiosqlite").Row
177
+ async with db.execute(
178
+ """SELECT credential_id, client_id, binding_id, token_prefix, status,
179
+ created_at, updated_at, last_used_at, revoked_at, expires_at
180
+ FROM mcp_client_credentials WHERE credential_id = ?""",
181
+ (credential_id,),
182
+ ) as cursor:
183
+ row = await cursor.fetchone()
184
+ return dict(row) if row else None
185
+
186
+
187
+ def _token_hash(token: str) -> str:
188
+ return hashlib.sha256(token.encode("utf-8")).hexdigest()
189
+
190
+
191
+ def _token_kind_from_prefix(token_prefix: str) -> str:
192
+ if token_prefix.startswith("mesa_antigravity_"):
193
+ return "antigravity"
194
+ if token_prefix.startswith("mesa_codex_"):
195
+ return "codex"
196
+ raise ValueError("stored credential has an unsupported token kind")
@@ -0,0 +1,81 @@
1
+ import json
2
+ import logging
3
+ from datetime import datetime, timezone
4
+ from typing import Any
5
+
6
+ from mesa_storage.sqlite_engine import AsyncEngine
7
+
8
+ logger = logging.getLogger(__name__)
9
+
10
+
11
+ class PolicyRepository:
12
+ def __init__(self, sqlite_engine: AsyncEngine):
13
+ self._sql = sqlite_engine
14
+
15
+ async def create_rule(
16
+ self,
17
+ rule_id: str,
18
+ scope_type: str,
19
+ operation: str,
20
+ effect: str,
21
+ created_by: str,
22
+ scope_id: str | None = None,
23
+ priority: int = 100,
24
+ conditions: dict[str, Any] | None = None,
25
+ ) -> None:
26
+ now = datetime.now(timezone.utc).isoformat()
27
+ cond_json = json.dumps(conditions or {})
28
+
29
+ async with self._sql.transaction() as db:
30
+ await db.execute(
31
+ """
32
+ INSERT INTO mcp_policy_rules (
33
+ rule_id, scope_type, scope_id, operation, effect,
34
+ priority, conditions_json, created_by, created_at, updated_at
35
+ ) VALUES (
36
+ :rule_id, :scope_type, :scope_id, :operation, :effect,
37
+ :priority, :cond_json, :created_by, :now, :now
38
+ )
39
+ """,
40
+ {
41
+ "rule_id": rule_id,
42
+ "scope_type": scope_type,
43
+ "scope_id": scope_id,
44
+ "operation": operation,
45
+ "effect": effect,
46
+ "priority": priority,
47
+ "cond_json": cond_json,
48
+ "created_by": created_by,
49
+ "now": now,
50
+ },
51
+ )
52
+ await db.commit()
53
+
54
+ async def get_rule(self, rule_id: str) -> dict[str, Any] | None:
55
+ async with self._sql.connection() as db:
56
+ db.row_factory = __import__("aiosqlite").Row
57
+ async with db.execute(
58
+ "SELECT * FROM mcp_policy_rules WHERE rule_id = :rule_id",
59
+ {"rule_id": rule_id},
60
+ ) as cursor:
61
+ row = await cursor.fetchone()
62
+ return dict(row) if row else None
63
+
64
+ async def list_rules(
65
+ self, scope_type: str | None = None, operation: str | None = None
66
+ ) -> list[dict[str, Any]]:
67
+ query = "SELECT * FROM mcp_policy_rules WHERE 1=1"
68
+ params = {}
69
+ if scope_type:
70
+ query += " AND scope_type = :scope_type"
71
+ params["scope_type"] = scope_type
72
+ if operation:
73
+ query += " AND operation = :operation"
74
+ params["operation"] = operation
75
+ query += " ORDER BY priority DESC"
76
+
77
+ async with self._sql.connection() as db:
78
+ db.row_factory = __import__("aiosqlite").Row
79
+ async with db.execute(query, params) as cursor:
80
+ rows = await cursor.fetchall()
81
+ return [dict(row) for row in rows]
@@ -0,0 +1,56 @@
1
+ import json
2
+ import logging
3
+ from datetime import datetime, timezone
4
+ from typing import Any
5
+
6
+ from mesa_storage.sqlite_engine import AsyncEngine
7
+
8
+ logger = logging.getLogger(__name__)
9
+
10
+
11
+ class SettingsRepository:
12
+ def __init__(self, sqlite_engine: AsyncEngine):
13
+ self._sql = sqlite_engine
14
+
15
+ async def get_setting(self, setting_key: str) -> Any | None:
16
+ async with self._sql.connection() as db:
17
+ db.row_factory = __import__("aiosqlite").Row
18
+ async with db.execute(
19
+ "SELECT setting_value_json FROM control_plane_settings WHERE setting_key = :key",
20
+ {"key": setting_key},
21
+ ) as cursor:
22
+ row = await cursor.fetchone()
23
+ if not row:
24
+ return None
25
+ return json.loads(row["setting_value_json"])
26
+
27
+ async def get_all_settings(self) -> dict[str, Any]:
28
+ async with self._sql.connection() as db:
29
+ db.row_factory = __import__("aiosqlite").Row
30
+ async with db.execute(
31
+ "SELECT setting_key, setting_value_json FROM control_plane_settings"
32
+ ) as cursor:
33
+ rows = await cursor.fetchall()
34
+ return {
35
+ row["setting_key"]: json.loads(row["setting_value_json"])
36
+ for row in rows
37
+ }
38
+
39
+ async def set_setting(
40
+ self, setting_key: str, value: Any, updated_by: str = "system"
41
+ ) -> None:
42
+ now = datetime.now(timezone.utc).isoformat()
43
+ value_json = json.dumps(value)
44
+ async with self._sql.transaction() as db:
45
+ await db.execute(
46
+ """
47
+ INSERT INTO control_plane_settings (setting_key, setting_value_json, updated_at, updated_by)
48
+ VALUES (:key, :val, :now, :by)
49
+ ON CONFLICT(setting_key) DO UPDATE SET
50
+ setting_value_json = excluded.setting_value_json,
51
+ updated_at = excluded.updated_at,
52
+ updated_by = excluded.updated_by
53
+ """,
54
+ {"key": setting_key, "val": value_json, "now": now, "by": updated_by},
55
+ )
56
+ await db.commit()