@prmichaelsen/remember-mcp 0.1.0
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.
- package/.env.example +65 -0
- package/AGENT.md +840 -0
- package/README.md +72 -0
- package/agent/design/.gitkeep +0 -0
- package/agent/design/access-control-result-pattern.md +458 -0
- package/agent/design/action-audit-memory-types.md +637 -0
- package/agent/design/common-template-fields.md +282 -0
- package/agent/design/complete-tool-set.md +407 -0
- package/agent/design/content-types-expansion.md +521 -0
- package/agent/design/cross-database-id-strategy.md +358 -0
- package/agent/design/default-template-library.md +423 -0
- package/agent/design/firestore-wrapper-analysis.md +606 -0
- package/agent/design/llm-provider-abstraction.md +691 -0
- package/agent/design/location-handling-architecture.md +523 -0
- package/agent/design/memory-templates-design.md +364 -0
- package/agent/design/permissions-storage-architecture.md +680 -0
- package/agent/design/relationship-storage-strategy.md +361 -0
- package/agent/design/remember-mcp-implementation-tasks.md +417 -0
- package/agent/design/remember-mcp-progress.yaml +141 -0
- package/agent/design/requirements-enhancements.md +468 -0
- package/agent/design/requirements.md +56 -0
- package/agent/design/template-storage-strategy.md +412 -0
- package/agent/design/template-suggestion-system.md +853 -0
- package/agent/design/trust-escalation-prevention.md +343 -0
- package/agent/design/trust-system-implementation.md +592 -0
- package/agent/design/user-preferences.md +683 -0
- package/agent/design/weaviate-collection-strategy.md +461 -0
- package/agent/milestones/.gitkeep +0 -0
- package/agent/milestones/milestone-1-project-foundation.md +121 -0
- package/agent/milestones/milestone-2-core-memory-system.md +150 -0
- package/agent/milestones/milestone-3-relationships-graph.md +116 -0
- package/agent/milestones/milestone-4-user-preferences.md +103 -0
- package/agent/milestones/milestone-5-template-system.md +126 -0
- package/agent/milestones/milestone-6-auth-multi-tenancy.md +124 -0
- package/agent/milestones/milestone-7-trust-permissions.md +133 -0
- package/agent/milestones/milestone-8-testing-quality.md +137 -0
- package/agent/milestones/milestone-9-deployment-documentation.md +147 -0
- package/agent/patterns/.gitkeep +0 -0
- package/agent/patterns/bootstrap.md +1271 -0
- package/agent/patterns/firebase-admin-sdk-v8-usage.md +950 -0
- package/agent/patterns/firestore-users-pattern-best-practices.md +347 -0
- package/agent/patterns/library-services.md +454 -0
- package/agent/patterns/testing-colocated.md +316 -0
- package/agent/progress.yaml +395 -0
- package/agent/tasks/.gitkeep +0 -0
- package/agent/tasks/task-1-initialize-project-structure.md +266 -0
- package/agent/tasks/task-2-install-dependencies.md +199 -0
- package/agent/tasks/task-3-setup-weaviate-client.md +330 -0
- package/agent/tasks/task-4-setup-firestore-client.md +362 -0
- package/agent/tasks/task-5-create-basic-mcp-server.md +114 -0
- package/agent/tasks/task-6-create-integration-tests.md +195 -0
- package/agent/tasks/task-7-finalize-milestone-1.md +363 -0
- package/agent/tasks/task-8-setup-utility-scripts.md +382 -0
- package/agent/tasks/task-9-create-server-factory.md +404 -0
- package/dist/config.d.ts +26 -0
- package/dist/constants/content-types.d.ts +60 -0
- package/dist/firestore/init.d.ts +14 -0
- package/dist/firestore/paths.d.ts +53 -0
- package/dist/firestore/paths.spec.d.ts +2 -0
- package/dist/server-factory.d.ts +40 -0
- package/dist/server-factory.js +1741 -0
- package/dist/server-factory.spec.d.ts +2 -0
- package/dist/server.d.ts +3 -0
- package/dist/server.js +1690 -0
- package/dist/tools/create-memory.d.ts +94 -0
- package/dist/tools/delete-memory.d.ts +47 -0
- package/dist/tools/search-memory.d.ts +88 -0
- package/dist/types/memory.d.ts +183 -0
- package/dist/utils/logger.d.ts +7 -0
- package/dist/weaviate/client.d.ts +39 -0
- package/dist/weaviate/client.spec.d.ts +2 -0
- package/dist/weaviate/schema.d.ts +29 -0
- package/esbuild.build.js +60 -0
- package/esbuild.watch.js +25 -0
- package/jest.config.js +31 -0
- package/jest.e2e.config.js +17 -0
- package/package.json +68 -0
- package/src/.gitkeep +0 -0
- package/src/config.ts +56 -0
- package/src/constants/content-types.ts +454 -0
- package/src/firestore/init.ts +68 -0
- package/src/firestore/paths.spec.ts +75 -0
- package/src/firestore/paths.ts +124 -0
- package/src/server-factory.spec.ts +60 -0
- package/src/server-factory.ts +215 -0
- package/src/server.ts +243 -0
- package/src/tools/create-memory.ts +198 -0
- package/src/tools/delete-memory.ts +126 -0
- package/src/tools/search-memory.ts +216 -0
- package/src/types/memory.ts +276 -0
- package/src/utils/logger.ts +42 -0
- package/src/weaviate/client.spec.ts +58 -0
- package/src/weaviate/client.ts +114 -0
- package/src/weaviate/schema.ts +288 -0
- package/tsconfig.json +26 -0
|
@@ -0,0 +1,680 @@
|
|
|
1
|
+
# Permissions & Trust Storage Architecture
|
|
2
|
+
|
|
3
|
+
**Concept**: Storage strategy for user permissions and trust relationships
|
|
4
|
+
**Created**: 2026-02-11
|
|
5
|
+
**Status**: Design Specification
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## Overview
|
|
10
|
+
|
|
11
|
+
We need to store:
|
|
12
|
+
1. **User Permissions**: Which user_id can access which user's persona/memories
|
|
13
|
+
2. **Trust Relationships**: Trust levels between users
|
|
14
|
+
3. **Trust Context**: Why trust was granted, history, etc.
|
|
15
|
+
|
|
16
|
+
---
|
|
17
|
+
|
|
18
|
+
## Storage Options Analysis
|
|
19
|
+
|
|
20
|
+
### Option 1: Separate Database (e.g., Firestore)
|
|
21
|
+
|
|
22
|
+
**Pros**:
|
|
23
|
+
- ✅ Optimized for relational queries
|
|
24
|
+
- ✅ Real-time updates and subscriptions
|
|
25
|
+
- ✅ Built-in security rules
|
|
26
|
+
- ✅ Easy to query "who can access my memories?"
|
|
27
|
+
- ✅ Separate concerns (permissions vs memories)
|
|
28
|
+
- ✅ Better for complex permission logic
|
|
29
|
+
- ✅ Easier to audit and manage
|
|
30
|
+
- ✅ Can use Firebase Auth integration
|
|
31
|
+
|
|
32
|
+
**Cons**:
|
|
33
|
+
- ❌ Additional database to manage
|
|
34
|
+
- ❌ Cross-database queries more complex
|
|
35
|
+
- ❌ Potential consistency issues
|
|
36
|
+
- ❌ Extra network hop for permission checks
|
|
37
|
+
|
|
38
|
+
**Best For**: Complex permission scenarios, frequent permission queries, real-time updates
|
|
39
|
+
|
|
40
|
+
---
|
|
41
|
+
|
|
42
|
+
### Option 2: Weaviate Database (Structured)
|
|
43
|
+
|
|
44
|
+
**Pros**:
|
|
45
|
+
- ✅ Single database for everything
|
|
46
|
+
- ✅ No cross-database queries
|
|
47
|
+
- ✅ Simpler architecture
|
|
48
|
+
- ✅ Consistent data model
|
|
49
|
+
- ✅ Can leverage vector search for trust patterns
|
|
50
|
+
|
|
51
|
+
**Cons**:
|
|
52
|
+
- ❌ Weaviate not optimized for relational queries
|
|
53
|
+
- ❌ No built-in security rules
|
|
54
|
+
- ❌ Harder to query complex permission graphs
|
|
55
|
+
- ❌ Less flexible for permission logic
|
|
56
|
+
- ❌ Mixing concerns (permissions + memories)
|
|
57
|
+
|
|
58
|
+
**Best For**: Simple permission scenarios, minimal permission queries
|
|
59
|
+
|
|
60
|
+
---
|
|
61
|
+
|
|
62
|
+
## Recommended Approach: Hybrid Strategy
|
|
63
|
+
|
|
64
|
+
**Use Firestore for permissions, Weaviate for memories**
|
|
65
|
+
|
|
66
|
+
### Why Hybrid?
|
|
67
|
+
|
|
68
|
+
1. **Separation of Concerns**
|
|
69
|
+
- Firestore: User relationships, permissions, trust
|
|
70
|
+
- Weaviate: Memories, content, semantic search
|
|
71
|
+
|
|
72
|
+
2. **Optimized for Use Case**
|
|
73
|
+
- Firestore excels at relational data
|
|
74
|
+
- Weaviate excels at vector search
|
|
75
|
+
|
|
76
|
+
3. **Scalability**
|
|
77
|
+
- Permission checks are fast (Firestore)
|
|
78
|
+
- Memory search is fast (Weaviate)
|
|
79
|
+
- Each database does what it's best at
|
|
80
|
+
|
|
81
|
+
4. **Security**
|
|
82
|
+
- Firestore security rules for permissions
|
|
83
|
+
- Weaviate collection isolation for memories
|
|
84
|
+
|
|
85
|
+
---
|
|
86
|
+
|
|
87
|
+
## Architecture Design
|
|
88
|
+
|
|
89
|
+
### System Overview
|
|
90
|
+
|
|
91
|
+
```
|
|
92
|
+
┌─────────────────────────────────────────────────────────────┐
|
|
93
|
+
│ agentbase.me Platform │
|
|
94
|
+
│ - Firebase Auth (user authentication) │
|
|
95
|
+
│ - Firestore (permissions & trust relationships) │
|
|
96
|
+
└────────────────┬────────────────────────────────────────────┘
|
|
97
|
+
│
|
|
98
|
+
│ MCP Request with auth token
|
|
99
|
+
│
|
|
100
|
+
┌────────────────▼────────────────────────────────────────────┐
|
|
101
|
+
│ remember-mcp Server │
|
|
102
|
+
│ │
|
|
103
|
+
│ ┌──────────────────────────────────────────────────────┐ │
|
|
104
|
+
│ │ Permission Layer │ │
|
|
105
|
+
│ │ 1. Validate Firebase token → user_id │ │
|
|
106
|
+
│ │ 2. Check Firestore for permissions │ │
|
|
107
|
+
│ │ 3. Get trust relationships │ │
|
|
108
|
+
│ └────────────────┬─────────────────────────────────────┘ │
|
|
109
|
+
│ │ │
|
|
110
|
+
│ ┌────────────────▼─────────────────────────────────────┐ │
|
|
111
|
+
│ │ Memory Layer │ │
|
|
112
|
+
│ │ 1. Query Weaviate with user_id scope │ │
|
|
113
|
+
│ │ 2. Apply trust filtering │ │
|
|
114
|
+
│ │ 3. Format with trust context │ │
|
|
115
|
+
│ └──────────────────────────────────────────────────────┘ │
|
|
116
|
+
└─────────────────────────────────────────────────────────────┘
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
---
|
|
120
|
+
|
|
121
|
+
## Firestore Schema
|
|
122
|
+
|
|
123
|
+
### Collection: `user_permissions`
|
|
124
|
+
|
|
125
|
+
```typescript
|
|
126
|
+
// Document path: user_permissions/{owner_user_id}/allowed_accessors/{accessor_user_id}
|
|
127
|
+
interface UserPermission {
|
|
128
|
+
// Identity
|
|
129
|
+
owner_user_id: string; // User whose memories can be accessed
|
|
130
|
+
accessor_user_id: string; // User who can access
|
|
131
|
+
|
|
132
|
+
// Permission
|
|
133
|
+
can_access: boolean; // Is access allowed?
|
|
134
|
+
access_level: string; // "read", "read_write", "admin"
|
|
135
|
+
|
|
136
|
+
// Trust
|
|
137
|
+
trust_level: number; // 0-1, continuous trust score
|
|
138
|
+
trust_summary: string; // Brief explanation of trust
|
|
139
|
+
trust_reason: string; // Detailed reason for trust level
|
|
140
|
+
|
|
141
|
+
// Scope
|
|
142
|
+
allowed_memory_types: string[]; // Which memory types can be accessed
|
|
143
|
+
allowed_tags: string[]; // Which tags can be accessed
|
|
144
|
+
excluded_tags: string[]; // Which tags are forbidden
|
|
145
|
+
|
|
146
|
+
// Temporal
|
|
147
|
+
granted_at: Timestamp;
|
|
148
|
+
expires_at: Timestamp | null; // Optional expiration
|
|
149
|
+
last_accessed: Timestamp;
|
|
150
|
+
access_count: number;
|
|
151
|
+
|
|
152
|
+
// Metadata
|
|
153
|
+
granted_by: string; // Who granted this permission
|
|
154
|
+
revoked: boolean;
|
|
155
|
+
revoked_at: Timestamp | null;
|
|
156
|
+
revoked_reason: string | null;
|
|
157
|
+
}
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
**Example Document**:
|
|
161
|
+
```javascript
|
|
162
|
+
// user_permissions/alice_123/allowed_accessors/bob_456
|
|
163
|
+
{
|
|
164
|
+
owner_user_id: "alice_123",
|
|
165
|
+
accessor_user_id: "bob_456",
|
|
166
|
+
can_access: true,
|
|
167
|
+
access_level: "read",
|
|
168
|
+
trust_level: 0.7,
|
|
169
|
+
trust_summary: "Close friend, can see most memories",
|
|
170
|
+
trust_reason: "Bob is a trusted friend. We've known each other for 5 years...",
|
|
171
|
+
allowed_memory_types: ["note", "event", "location"],
|
|
172
|
+
allowed_tags: ["travel", "food", "movies"],
|
|
173
|
+
excluded_tags: ["medical", "financial", "private"],
|
|
174
|
+
granted_at: Timestamp.now(),
|
|
175
|
+
expires_at: null,
|
|
176
|
+
last_accessed: Timestamp.now(),
|
|
177
|
+
access_count: 42,
|
|
178
|
+
granted_by: "alice_123",
|
|
179
|
+
revoked: false
|
|
180
|
+
}
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
### Collection: `trust_history`
|
|
184
|
+
|
|
185
|
+
```typescript
|
|
186
|
+
// Document path: trust_history/{owner_user_id}/history/{history_id}
|
|
187
|
+
interface TrustHistoryEntry {
|
|
188
|
+
owner_user_id: string;
|
|
189
|
+
accessor_user_id: string;
|
|
190
|
+
|
|
191
|
+
// Change
|
|
192
|
+
previous_trust: number;
|
|
193
|
+
new_trust: number;
|
|
194
|
+
change_reason: string;
|
|
195
|
+
|
|
196
|
+
// Context
|
|
197
|
+
changed_at: Timestamp;
|
|
198
|
+
changed_by: string;
|
|
199
|
+
conversation_id: string | null;
|
|
200
|
+
|
|
201
|
+
// Evidence
|
|
202
|
+
evidence: {
|
|
203
|
+
type: string; // "positive_interaction", "violation", "manual_adjustment"
|
|
204
|
+
description: string;
|
|
205
|
+
severity: number; // 0-1
|
|
206
|
+
}[];
|
|
207
|
+
}
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
### Collection: `persona_access`
|
|
211
|
+
|
|
212
|
+
```typescript
|
|
213
|
+
// Document path: persona_access/{persona_id}/allowed_users/{user_id}
|
|
214
|
+
interface PersonaAccess {
|
|
215
|
+
persona_id: string; // Which persona/agent
|
|
216
|
+
user_id: string; // Which user can talk to it
|
|
217
|
+
|
|
218
|
+
// Access
|
|
219
|
+
can_interact: boolean;
|
|
220
|
+
interaction_level: string; // "basic", "full", "admin"
|
|
221
|
+
|
|
222
|
+
// Limits
|
|
223
|
+
rate_limit: number; // Requests per hour
|
|
224
|
+
daily_limit: number; // Requests per day
|
|
225
|
+
|
|
226
|
+
// Temporal
|
|
227
|
+
granted_at: Timestamp;
|
|
228
|
+
expires_at: Timestamp | null;
|
|
229
|
+
last_interaction: Timestamp;
|
|
230
|
+
interaction_count: number;
|
|
231
|
+
}
|
|
232
|
+
```
|
|
233
|
+
|
|
234
|
+
---
|
|
235
|
+
|
|
236
|
+
## Weaviate Schema
|
|
237
|
+
|
|
238
|
+
### Collection: `Memory_{user_id}`
|
|
239
|
+
|
|
240
|
+
```yaml
|
|
241
|
+
Memory:
|
|
242
|
+
# Core fields (as defined in requirements)
|
|
243
|
+
id: uuid
|
|
244
|
+
user_id: string
|
|
245
|
+
content: text
|
|
246
|
+
# ... other memory fields ...
|
|
247
|
+
|
|
248
|
+
# Trust metadata (stored but not used for filtering)
|
|
249
|
+
default_trust: float # Default trust for this memory
|
|
250
|
+
trust_override: object # Per-user trust overrides
|
|
251
|
+
user_id: string
|
|
252
|
+
trust: float
|
|
253
|
+
reason: string
|
|
254
|
+
```
|
|
255
|
+
|
|
256
|
+
**Note**: Trust relationships are primarily in Firestore, but memories can have default trust levels and per-user overrides stored in Weaviate for performance.
|
|
257
|
+
|
|
258
|
+
---
|
|
259
|
+
|
|
260
|
+
## Permission Check Flow
|
|
261
|
+
|
|
262
|
+
### 1. Request Arrives
|
|
263
|
+
|
|
264
|
+
```typescript
|
|
265
|
+
async function handleMCPRequest(
|
|
266
|
+
tool: string,
|
|
267
|
+
args: any,
|
|
268
|
+
context: RequestContext
|
|
269
|
+
): Promise<any> {
|
|
270
|
+
// 1. Validate Firebase token
|
|
271
|
+
const authResult = await firebaseAuth.verifyToken(context.auth_token);
|
|
272
|
+
const accessor_user_id = authResult.uid;
|
|
273
|
+
|
|
274
|
+
// 2. Determine target user
|
|
275
|
+
const target_user_id = args.user_id || accessor_user_id;
|
|
276
|
+
|
|
277
|
+
// 3. Check permissions (if accessing another user's memories)
|
|
278
|
+
if (accessor_user_id !== target_user_id) {
|
|
279
|
+
const permission = await checkPermission(target_user_id, accessor_user_id);
|
|
280
|
+
|
|
281
|
+
if (!permission.can_access) {
|
|
282
|
+
throw new Error('Access denied');
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
// Store permission context for trust filtering
|
|
286
|
+
context.permission = permission;
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
// 4. Execute tool with permission context
|
|
290
|
+
return await executeTool(tool, args, context);
|
|
291
|
+
}
|
|
292
|
+
```
|
|
293
|
+
|
|
294
|
+
### 2. Check Permission (Firestore)
|
|
295
|
+
|
|
296
|
+
```typescript
|
|
297
|
+
async function checkPermission(
|
|
298
|
+
owner_user_id: string,
|
|
299
|
+
accessor_user_id: string
|
|
300
|
+
): Promise<UserPermission | null> {
|
|
301
|
+
// Query Firestore
|
|
302
|
+
const doc = await firestore
|
|
303
|
+
.collection('user_permissions')
|
|
304
|
+
.doc(owner_user_id)
|
|
305
|
+
.collection('allowed_accessors')
|
|
306
|
+
.doc(accessor_user_id)
|
|
307
|
+
.get();
|
|
308
|
+
|
|
309
|
+
if (!doc.exists) {
|
|
310
|
+
return null; // No permission granted
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
const permission = doc.data() as UserPermission;
|
|
314
|
+
|
|
315
|
+
// Check if revoked
|
|
316
|
+
if (permission.revoked) {
|
|
317
|
+
return null;
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
// Check if expired
|
|
321
|
+
if (permission.expires_at && permission.expires_at < Timestamp.now()) {
|
|
322
|
+
return null;
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
// Update last accessed
|
|
326
|
+
await doc.ref.update({
|
|
327
|
+
last_accessed: Timestamp.now(),
|
|
328
|
+
access_count: FieldValue.increment(1)
|
|
329
|
+
});
|
|
330
|
+
|
|
331
|
+
return permission;
|
|
332
|
+
}
|
|
333
|
+
```
|
|
334
|
+
|
|
335
|
+
### 3. Query Memories with Trust Context
|
|
336
|
+
|
|
337
|
+
```typescript
|
|
338
|
+
async function searchMemories(
|
|
339
|
+
query: string,
|
|
340
|
+
owner_user_id: string,
|
|
341
|
+
context: RequestContext
|
|
342
|
+
): Promise<Memory[]> {
|
|
343
|
+
// 1. Query Weaviate (user's collection)
|
|
344
|
+
const memories = await weaviateClient.searchDocuments(
|
|
345
|
+
query,
|
|
346
|
+
{}, // filters
|
|
347
|
+
10, // limit
|
|
348
|
+
owner_user_id // collection scope
|
|
349
|
+
);
|
|
350
|
+
|
|
351
|
+
// 2. Apply trust filtering
|
|
352
|
+
const permission = context.permission;
|
|
353
|
+
|
|
354
|
+
if (permission) {
|
|
355
|
+
// Accessing another user's memories
|
|
356
|
+
return memories
|
|
357
|
+
.filter(m => isMemoryAllowed(m, permission))
|
|
358
|
+
.map(m => applyTrustLevel(m, permission.trust_level));
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
// Accessing own memories - full access
|
|
362
|
+
return memories;
|
|
363
|
+
}
|
|
364
|
+
```
|
|
365
|
+
|
|
366
|
+
### 4. Filter by Permission Scope
|
|
367
|
+
|
|
368
|
+
```typescript
|
|
369
|
+
function isMemoryAllowed(
|
|
370
|
+
memory: Memory,
|
|
371
|
+
permission: UserPermission
|
|
372
|
+
): boolean {
|
|
373
|
+
// Check memory type
|
|
374
|
+
if (permission.allowed_memory_types.length > 0) {
|
|
375
|
+
if (!permission.allowed_memory_types.includes(memory.type)) {
|
|
376
|
+
return false;
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
// Check excluded tags
|
|
381
|
+
if (permission.excluded_tags.length > 0) {
|
|
382
|
+
const hasExcludedTag = memory.tags.some(tag =>
|
|
383
|
+
permission.excluded_tags.includes(tag)
|
|
384
|
+
);
|
|
385
|
+
if (hasExcludedTag) {
|
|
386
|
+
return false;
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
// Check allowed tags (if specified)
|
|
391
|
+
if (permission.allowed_tags.length > 0) {
|
|
392
|
+
const hasAllowedTag = memory.tags.some(tag =>
|
|
393
|
+
permission.allowed_tags.includes(tag)
|
|
394
|
+
);
|
|
395
|
+
if (!hasAllowedTag) {
|
|
396
|
+
return false;
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
return true;
|
|
401
|
+
}
|
|
402
|
+
```
|
|
403
|
+
|
|
404
|
+
---
|
|
405
|
+
|
|
406
|
+
## Firestore Security Rules
|
|
407
|
+
|
|
408
|
+
```javascript
|
|
409
|
+
rules_version = '2';
|
|
410
|
+
service cloud.firestore {
|
|
411
|
+
match /databases/{database}/documents {
|
|
412
|
+
|
|
413
|
+
// User permissions
|
|
414
|
+
match /user_permissions/{owner_user_id}/allowed_accessors/{accessor_user_id} {
|
|
415
|
+
// Owner can read/write their own permissions
|
|
416
|
+
allow read, write: if request.auth.uid == owner_user_id;
|
|
417
|
+
|
|
418
|
+
// Accessor can read their permission (but not write)
|
|
419
|
+
allow read: if request.auth.uid == accessor_user_id;
|
|
420
|
+
|
|
421
|
+
// Prevent self-permission escalation
|
|
422
|
+
allow write: if request.auth.uid == owner_user_id
|
|
423
|
+
&& accessor_user_id != owner_user_id;
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
// Trust history
|
|
427
|
+
match /trust_history/{owner_user_id}/history/{history_id} {
|
|
428
|
+
// Owner can read their trust history
|
|
429
|
+
allow read: if request.auth.uid == owner_user_id;
|
|
430
|
+
|
|
431
|
+
// System can write (via admin SDK)
|
|
432
|
+
allow write: if false; // Only via admin SDK
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
// Persona access
|
|
436
|
+
match /persona_access/{persona_id}/allowed_users/{user_id} {
|
|
437
|
+
// User can read their own access
|
|
438
|
+
allow read: if request.auth.uid == user_id;
|
|
439
|
+
|
|
440
|
+
// Persona owner can manage access
|
|
441
|
+
allow write: if request.auth.uid == getPersonaOwner(persona_id);
|
|
442
|
+
}
|
|
443
|
+
}
|
|
444
|
+
}
|
|
445
|
+
```
|
|
446
|
+
|
|
447
|
+
---
|
|
448
|
+
|
|
449
|
+
## API Examples
|
|
450
|
+
|
|
451
|
+
### Grant Permission
|
|
452
|
+
|
|
453
|
+
```typescript
|
|
454
|
+
// User Alice grants Bob access to her memories
|
|
455
|
+
async function grantPermission(
|
|
456
|
+
owner_user_id: string,
|
|
457
|
+
accessor_user_id: string,
|
|
458
|
+
trust_level: number,
|
|
459
|
+
options: PermissionOptions
|
|
460
|
+
): Promise<void> {
|
|
461
|
+
const permission: UserPermission = {
|
|
462
|
+
owner_user_id,
|
|
463
|
+
accessor_user_id,
|
|
464
|
+
can_access: true,
|
|
465
|
+
access_level: options.access_level || "read",
|
|
466
|
+
trust_level,
|
|
467
|
+
trust_summary: options.trust_summary,
|
|
468
|
+
trust_reason: options.trust_reason,
|
|
469
|
+
allowed_memory_types: options.allowed_memory_types || [],
|
|
470
|
+
allowed_tags: options.allowed_tags || [],
|
|
471
|
+
excluded_tags: options.excluded_tags || [],
|
|
472
|
+
granted_at: Timestamp.now(),
|
|
473
|
+
expires_at: options.expires_at || null,
|
|
474
|
+
last_accessed: Timestamp.now(),
|
|
475
|
+
access_count: 0,
|
|
476
|
+
granted_by: owner_user_id,
|
|
477
|
+
revoked: false,
|
|
478
|
+
revoked_at: null,
|
|
479
|
+
revoked_reason: null
|
|
480
|
+
};
|
|
481
|
+
|
|
482
|
+
await firestore
|
|
483
|
+
.collection('user_permissions')
|
|
484
|
+
.doc(owner_user_id)
|
|
485
|
+
.collection('allowed_accessors')
|
|
486
|
+
.doc(accessor_user_id)
|
|
487
|
+
.set(permission);
|
|
488
|
+
}
|
|
489
|
+
```
|
|
490
|
+
|
|
491
|
+
### Update Trust Level
|
|
492
|
+
|
|
493
|
+
```typescript
|
|
494
|
+
async function updateTrustLevel(
|
|
495
|
+
owner_user_id: string,
|
|
496
|
+
accessor_user_id: string,
|
|
497
|
+
new_trust: number,
|
|
498
|
+
reason: string
|
|
499
|
+
): Promise<void> {
|
|
500
|
+
const permissionRef = firestore
|
|
501
|
+
.collection('user_permissions')
|
|
502
|
+
.doc(owner_user_id)
|
|
503
|
+
.collection('allowed_accessors')
|
|
504
|
+
.doc(accessor_user_id);
|
|
505
|
+
|
|
506
|
+
const doc = await permissionRef.get();
|
|
507
|
+
const current = doc.data() as UserPermission;
|
|
508
|
+
|
|
509
|
+
// Update permission
|
|
510
|
+
await permissionRef.update({
|
|
511
|
+
trust_level: new_trust,
|
|
512
|
+
trust_reason: reason
|
|
513
|
+
});
|
|
514
|
+
|
|
515
|
+
// Log history
|
|
516
|
+
await firestore
|
|
517
|
+
.collection('trust_history')
|
|
518
|
+
.doc(owner_user_id)
|
|
519
|
+
.collection('history')
|
|
520
|
+
.add({
|
|
521
|
+
owner_user_id,
|
|
522
|
+
accessor_user_id,
|
|
523
|
+
previous_trust: current.trust_level,
|
|
524
|
+
new_trust,
|
|
525
|
+
change_reason: reason,
|
|
526
|
+
changed_at: Timestamp.now(),
|
|
527
|
+
changed_by: owner_user_id,
|
|
528
|
+
conversation_id: null,
|
|
529
|
+
evidence: []
|
|
530
|
+
});
|
|
531
|
+
}
|
|
532
|
+
```
|
|
533
|
+
|
|
534
|
+
### Revoke Permission
|
|
535
|
+
|
|
536
|
+
```typescript
|
|
537
|
+
async function revokePermission(
|
|
538
|
+
owner_user_id: string,
|
|
539
|
+
accessor_user_id: string,
|
|
540
|
+
reason: string
|
|
541
|
+
): Promise<void> {
|
|
542
|
+
await firestore
|
|
543
|
+
.collection('user_permissions')
|
|
544
|
+
.doc(owner_user_id)
|
|
545
|
+
.collection('allowed_accessors')
|
|
546
|
+
.doc(accessor_user_id)
|
|
547
|
+
.update({
|
|
548
|
+
revoked: true,
|
|
549
|
+
revoked_at: Timestamp.now(),
|
|
550
|
+
revoked_reason: reason
|
|
551
|
+
});
|
|
552
|
+
}
|
|
553
|
+
```
|
|
554
|
+
|
|
555
|
+
### List Who Can Access My Memories
|
|
556
|
+
|
|
557
|
+
```typescript
|
|
558
|
+
async function listAccessors(owner_user_id: string): Promise<UserPermission[]> {
|
|
559
|
+
const snapshot = await firestore
|
|
560
|
+
.collection('user_permissions')
|
|
561
|
+
.doc(owner_user_id)
|
|
562
|
+
.collection('allowed_accessors')
|
|
563
|
+
.where('revoked', '==', false)
|
|
564
|
+
.orderBy('trust_level', 'desc')
|
|
565
|
+
.get();
|
|
566
|
+
|
|
567
|
+
return snapshot.docs.map(doc => doc.data() as UserPermission);
|
|
568
|
+
}
|
|
569
|
+
```
|
|
570
|
+
|
|
571
|
+
---
|
|
572
|
+
|
|
573
|
+
## Performance Optimization
|
|
574
|
+
|
|
575
|
+
### 1. Cache Permissions
|
|
576
|
+
|
|
577
|
+
```typescript
|
|
578
|
+
// In-memory cache for frequently checked permissions
|
|
579
|
+
const permissionCache = new Map<string, UserPermission>();
|
|
580
|
+
|
|
581
|
+
async function checkPermissionCached(
|
|
582
|
+
owner_user_id: string,
|
|
583
|
+
accessor_user_id: string
|
|
584
|
+
): Promise<UserPermission | null> {
|
|
585
|
+
const cacheKey = `${owner_user_id}:${accessor_user_id}`;
|
|
586
|
+
|
|
587
|
+
// Check cache
|
|
588
|
+
if (permissionCache.has(cacheKey)) {
|
|
589
|
+
const cached = permissionCache.get(cacheKey)!;
|
|
590
|
+
|
|
591
|
+
// Validate cache (5 minute TTL)
|
|
592
|
+
if (Date.now() - cached.last_accessed.toMillis() < 300000) {
|
|
593
|
+
return cached;
|
|
594
|
+
}
|
|
595
|
+
}
|
|
596
|
+
|
|
597
|
+
// Fetch from Firestore
|
|
598
|
+
const permission = await checkPermission(owner_user_id, accessor_user_id);
|
|
599
|
+
|
|
600
|
+
if (permission) {
|
|
601
|
+
permissionCache.set(cacheKey, permission);
|
|
602
|
+
}
|
|
603
|
+
|
|
604
|
+
return permission;
|
|
605
|
+
}
|
|
606
|
+
```
|
|
607
|
+
|
|
608
|
+
### 2. Batch Permission Checks
|
|
609
|
+
|
|
610
|
+
```typescript
|
|
611
|
+
async function checkPermissionsBatch(
|
|
612
|
+
owner_user_id: string,
|
|
613
|
+
accessor_user_ids: string[]
|
|
614
|
+
): Promise<Map<string, UserPermission>> {
|
|
615
|
+
const results = new Map();
|
|
616
|
+
|
|
617
|
+
// Batch read from Firestore
|
|
618
|
+
const refs = accessor_user_ids.map(id =>
|
|
619
|
+
firestore
|
|
620
|
+
.collection('user_permissions')
|
|
621
|
+
.doc(owner_user_id)
|
|
622
|
+
.collection('allowed_accessors')
|
|
623
|
+
.doc(id)
|
|
624
|
+
);
|
|
625
|
+
|
|
626
|
+
const docs = await firestore.getAll(...refs);
|
|
627
|
+
|
|
628
|
+
docs.forEach((doc, index) => {
|
|
629
|
+
if (doc.exists) {
|
|
630
|
+
results.set(accessor_user_ids[index], doc.data() as UserPermission);
|
|
631
|
+
}
|
|
632
|
+
});
|
|
633
|
+
|
|
634
|
+
return results;
|
|
635
|
+
}
|
|
636
|
+
```
|
|
637
|
+
|
|
638
|
+
---
|
|
639
|
+
|
|
640
|
+
## Migration Strategy
|
|
641
|
+
|
|
642
|
+
### Phase 1: Firestore Setup
|
|
643
|
+
1. Create Firestore collections
|
|
644
|
+
2. Define security rules
|
|
645
|
+
3. Set up indexes
|
|
646
|
+
4. Create admin tools for permission management
|
|
647
|
+
|
|
648
|
+
### Phase 2: Integration
|
|
649
|
+
1. Add Firestore client to remember-mcp
|
|
650
|
+
2. Implement permission check layer
|
|
651
|
+
3. Add caching
|
|
652
|
+
4. Test with sample permissions
|
|
653
|
+
|
|
654
|
+
### Phase 3: UI
|
|
655
|
+
1. Add permission management UI to agentbase.me
|
|
656
|
+
2. Allow users to grant/revoke access
|
|
657
|
+
3. Show trust history
|
|
658
|
+
4. Display who can access memories
|
|
659
|
+
|
|
660
|
+
---
|
|
661
|
+
|
|
662
|
+
## Recommendation
|
|
663
|
+
|
|
664
|
+
**Use Firestore for permissions** because:
|
|
665
|
+
|
|
666
|
+
1. ✅ **Optimized for relational queries** - "Who can access my memories?"
|
|
667
|
+
2. ✅ **Real-time updates** - Permission changes take effect immediately
|
|
668
|
+
3. ✅ **Security rules** - Built-in access control
|
|
669
|
+
4. ✅ **Firebase integration** - Works seamlessly with Firebase Auth
|
|
670
|
+
5. ✅ **Scalable** - Handles millions of permission records
|
|
671
|
+
6. ✅ **Auditable** - Easy to track permission changes
|
|
672
|
+
7. ✅ **Flexible** - Can add complex permission logic without affecting Weaviate
|
|
673
|
+
|
|
674
|
+
**Cost**: Minimal - permission checks are infrequent and can be cached
|
|
675
|
+
|
|
676
|
+
---
|
|
677
|
+
|
|
678
|
+
**Status**: Design Specification
|
|
679
|
+
**Recommendation**: Hybrid approach - Firestore for permissions, Weaviate for memories
|
|
680
|
+
**Next Step**: Implement Firestore schema and permission check layer
|