@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
package/README.md
ADDED
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
# remember-mcp
|
|
2
|
+
|
|
3
|
+
Multi-tenant memory system MCP server with vector search, relationships, and trust-based access control.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
- 24 MCP tools for memory management
|
|
8
|
+
- Multi-tenant with per-user isolation
|
|
9
|
+
- Vector search with Weaviate
|
|
10
|
+
- Relationship graph between memories
|
|
11
|
+
- Template system with auto-suggestion
|
|
12
|
+
- Trust-based cross-user access control
|
|
13
|
+
- User preferences via conversation
|
|
14
|
+
|
|
15
|
+
## Quick Start
|
|
16
|
+
|
|
17
|
+
### Standalone (stdio transport)
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
# Install dependencies
|
|
21
|
+
npm install
|
|
22
|
+
|
|
23
|
+
# Set up environment
|
|
24
|
+
cp .env.example .env
|
|
25
|
+
# Edit .env with your configuration
|
|
26
|
+
|
|
27
|
+
# Run in development
|
|
28
|
+
npm run dev
|
|
29
|
+
|
|
30
|
+
# Build for production
|
|
31
|
+
npm run build
|
|
32
|
+
npm start
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
### With mcp-auth (multi-tenant production)
|
|
36
|
+
|
|
37
|
+
```typescript
|
|
38
|
+
import { wrapServer, JWTAuthProvider, APITokenResolver } from '@prmichaelsen/mcp-auth';
|
|
39
|
+
import { createServer } from '@prmichaelsen/remember-mcp/factory';
|
|
40
|
+
|
|
41
|
+
const wrapped = wrapServer({
|
|
42
|
+
serverFactory: createServer,
|
|
43
|
+
authProvider: new JWTAuthProvider({
|
|
44
|
+
jwtSecret: process.env.JWT_SECRET
|
|
45
|
+
}),
|
|
46
|
+
tokenResolver: new APITokenResolver({
|
|
47
|
+
tenantManagerUrl: process.env.TENANT_MANAGER_URL,
|
|
48
|
+
serviceToken: process.env.SERVICE_TOKEN
|
|
49
|
+
}),
|
|
50
|
+
resourceType: 'remember',
|
|
51
|
+
transport: { type: 'sse', port: 3000 }
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
await wrapped.start();
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
## Architecture
|
|
58
|
+
|
|
59
|
+
- **Weaviate**: Vector storage for memories, relationships, templates
|
|
60
|
+
- **Firestore**: Permissions, preferences, metadata
|
|
61
|
+
- **Firebase Auth**: User authentication
|
|
62
|
+
|
|
63
|
+
## Documentation
|
|
64
|
+
|
|
65
|
+
See `agent/` directory for:
|
|
66
|
+
- Design documents (`agent/design/`)
|
|
67
|
+
- Milestones (`agent/milestones/`)
|
|
68
|
+
- Implementation tasks (`agent/tasks/`)
|
|
69
|
+
|
|
70
|
+
## License
|
|
71
|
+
|
|
72
|
+
MIT
|
|
File without changes
|
|
@@ -0,0 +1,458 @@
|
|
|
1
|
+
# Access Control Result Pattern
|
|
2
|
+
|
|
3
|
+
**Concept**: Use discriminated unions instead of exceptions for access control
|
|
4
|
+
**Created**: 2026-02-11
|
|
5
|
+
**Status**: Design Specification
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## Problem with Throwing Errors
|
|
10
|
+
|
|
11
|
+
**Bad Pattern**:
|
|
12
|
+
```typescript
|
|
13
|
+
// ❌ Using exceptions for control flow
|
|
14
|
+
async function checkMemoryAccess(memory_id, accessor_user_id) {
|
|
15
|
+
if (blocked) {
|
|
16
|
+
throw new Error('Access blocked');
|
|
17
|
+
}
|
|
18
|
+
if (insufficient_trust) {
|
|
19
|
+
throw new Error('Insufficient trust');
|
|
20
|
+
}
|
|
21
|
+
return memory;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
// Caller has to catch and parse error messages
|
|
25
|
+
try {
|
|
26
|
+
const memory = await checkMemoryAccess(id, user);
|
|
27
|
+
} catch (error) {
|
|
28
|
+
// Hard to distinguish between different failure types
|
|
29
|
+
if (error.message.includes('blocked')) {
|
|
30
|
+
// Handle block
|
|
31
|
+
} else if (error.message.includes('trust')) {
|
|
32
|
+
// Handle trust failure
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
**Problems**:
|
|
38
|
+
- Exceptions for expected cases (not exceptional)
|
|
39
|
+
- Hard to distinguish failure types
|
|
40
|
+
- Error message parsing is brittle
|
|
41
|
+
- No type safety
|
|
42
|
+
- Poor developer experience
|
|
43
|
+
|
|
44
|
+
---
|
|
45
|
+
|
|
46
|
+
## Solution: Discriminated Union (Result Type)
|
|
47
|
+
|
|
48
|
+
**Good Pattern**:
|
|
49
|
+
```typescript
|
|
50
|
+
// ✅ Using discriminated union
|
|
51
|
+
type AccessResult =
|
|
52
|
+
| { status: 'granted'; memory: Memory }
|
|
53
|
+
| { status: 'insufficient_trust'; required: number; actual: number; attempts_remaining: number }
|
|
54
|
+
| { status: 'blocked'; reason: string; blocked_at: Date }
|
|
55
|
+
| { status: 'no_permission'; owner_user_id: string }
|
|
56
|
+
| { status: 'not_found'; memory_id: string };
|
|
57
|
+
|
|
58
|
+
async function checkMemoryAccess(
|
|
59
|
+
memory_id: string,
|
|
60
|
+
accessor_user_id: string
|
|
61
|
+
): Promise<AccessResult> {
|
|
62
|
+
// Implementation returns appropriate result
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// Caller has type-safe handling
|
|
66
|
+
const result = await checkMemoryAccess(id, user);
|
|
67
|
+
|
|
68
|
+
switch (result.status) {
|
|
69
|
+
case 'granted':
|
|
70
|
+
// TypeScript knows result.memory exists
|
|
71
|
+
return result.memory;
|
|
72
|
+
|
|
73
|
+
case 'insufficient_trust':
|
|
74
|
+
// TypeScript knows these fields exist
|
|
75
|
+
console.log(`Need ${result.required}, have ${result.actual}`);
|
|
76
|
+
console.log(`${result.attempts_remaining} attempts remaining`);
|
|
77
|
+
return null;
|
|
78
|
+
|
|
79
|
+
case 'blocked':
|
|
80
|
+
console.log(`Blocked: ${result.reason} at ${result.blocked_at}`);
|
|
81
|
+
return null;
|
|
82
|
+
|
|
83
|
+
case 'no_permission':
|
|
84
|
+
console.log(`No permission to access ${result.owner_user_id}'s memories`);
|
|
85
|
+
return null;
|
|
86
|
+
|
|
87
|
+
case 'not_found':
|
|
88
|
+
console.log(`Memory ${result.memory_id} not found`);
|
|
89
|
+
return null;
|
|
90
|
+
}
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
**Benefits**:
|
|
94
|
+
- ✅ Type-safe
|
|
95
|
+
- ✅ Exhaustive checking (TypeScript ensures all cases handled)
|
|
96
|
+
- ✅ Clear failure reasons
|
|
97
|
+
- ✅ Rich context for each failure type
|
|
98
|
+
- ✅ Better developer experience
|
|
99
|
+
- ✅ No exception handling needed
|
|
100
|
+
|
|
101
|
+
---
|
|
102
|
+
|
|
103
|
+
## Complete Implementation
|
|
104
|
+
|
|
105
|
+
### Access Result Types
|
|
106
|
+
|
|
107
|
+
```typescript
|
|
108
|
+
// Success case
|
|
109
|
+
interface AccessGranted {
|
|
110
|
+
status: 'granted';
|
|
111
|
+
memory: Memory;
|
|
112
|
+
access_level: 'owner' | 'trusted'; // Was this owner or cross-user access?
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// Trust insufficient (but not blocked yet)
|
|
116
|
+
interface AccessInsufficientTrust {
|
|
117
|
+
status: 'insufficient_trust';
|
|
118
|
+
memory_id: string;
|
|
119
|
+
required_trust: number;
|
|
120
|
+
actual_trust: number;
|
|
121
|
+
trust_deficit: number; // How much more trust needed
|
|
122
|
+
attempts_made: number;
|
|
123
|
+
attempts_remaining: number;
|
|
124
|
+
new_trust_level: number; // After penalty applied
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// Access blocked (after 3 attempts)
|
|
128
|
+
interface AccessBlocked {
|
|
129
|
+
status: 'blocked';
|
|
130
|
+
memory_id: string;
|
|
131
|
+
reason: string;
|
|
132
|
+
blocked_at: Date;
|
|
133
|
+
attempt_count: number;
|
|
134
|
+
contact_owner: boolean; // Should user contact owner?
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
// No permission granted at all
|
|
138
|
+
interface AccessNoPermission {
|
|
139
|
+
status: 'no_permission';
|
|
140
|
+
owner_user_id: string;
|
|
141
|
+
accessor_user_id: string;
|
|
142
|
+
message: string;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
// Memory doesn't exist
|
|
146
|
+
interface AccessNotFound {
|
|
147
|
+
status: 'not_found';
|
|
148
|
+
memory_id: string;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
// Memory was deleted
|
|
152
|
+
interface AccessDeleted {
|
|
153
|
+
status: 'deleted';
|
|
154
|
+
memory_id: string;
|
|
155
|
+
deleted_at: Date;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
// Union type
|
|
159
|
+
type AccessResult =
|
|
160
|
+
| AccessGranted
|
|
161
|
+
| AccessInsufficientTrust
|
|
162
|
+
| AccessBlocked
|
|
163
|
+
| AccessNoPermission
|
|
164
|
+
| AccessNotFound
|
|
165
|
+
| AccessDeleted;
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
### Implementation
|
|
169
|
+
|
|
170
|
+
```typescript
|
|
171
|
+
async function checkMemoryAccess(
|
|
172
|
+
memory_id: string,
|
|
173
|
+
accessor_user_id: string
|
|
174
|
+
): Promise<AccessResult> {
|
|
175
|
+
// Get memory
|
|
176
|
+
const memory = await getMemory(memory_id);
|
|
177
|
+
|
|
178
|
+
if (!memory) {
|
|
179
|
+
return {
|
|
180
|
+
status: 'not_found',
|
|
181
|
+
memory_id
|
|
182
|
+
};
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
if (memory.deleted) {
|
|
186
|
+
return {
|
|
187
|
+
status: 'deleted',
|
|
188
|
+
memory_id,
|
|
189
|
+
deleted_at: memory.deleted_at
|
|
190
|
+
};
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
// Owner always has access
|
|
194
|
+
if (accessor_user_id === memory.user_id) {
|
|
195
|
+
return {
|
|
196
|
+
status: 'granted',
|
|
197
|
+
memory,
|
|
198
|
+
access_level: 'owner'
|
|
199
|
+
};
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
// Cross-user access - check permission
|
|
203
|
+
const permission = await getPermission(memory.user_id, accessor_user_id);
|
|
204
|
+
|
|
205
|
+
if (!permission || !permission.can_access) {
|
|
206
|
+
return {
|
|
207
|
+
status: 'no_permission',
|
|
208
|
+
owner_user_id: memory.user_id,
|
|
209
|
+
accessor_user_id,
|
|
210
|
+
message: 'No permission granted to access this user\'s memories'
|
|
211
|
+
};
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
// Check if blocked
|
|
215
|
+
const blockKey = `${accessor_user_id}:${memory_id}`;
|
|
216
|
+
const block = await getBlock(blockKey);
|
|
217
|
+
|
|
218
|
+
if (block) {
|
|
219
|
+
return {
|
|
220
|
+
status: 'blocked',
|
|
221
|
+
memory_id,
|
|
222
|
+
reason: 'Access blocked due to repeated unauthorized attempts',
|
|
223
|
+
blocked_at: block.blocked_at,
|
|
224
|
+
attempt_count: block.attempt_count,
|
|
225
|
+
contact_owner: true
|
|
226
|
+
};
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
// Check trust level
|
|
230
|
+
if (permission.trust_level < memory.trust) {
|
|
231
|
+
// Apply penalty
|
|
232
|
+
const attemptCount = await incrementAttemptCount(blockKey);
|
|
233
|
+
const newTrust = Math.max(0, permission.trust_level - 0.1);
|
|
234
|
+
|
|
235
|
+
await updateTrustLevel(
|
|
236
|
+
memory.user_id,
|
|
237
|
+
accessor_user_id,
|
|
238
|
+
newTrust,
|
|
239
|
+
`Unauthorized access attempt to memory ${memory_id}`
|
|
240
|
+
);
|
|
241
|
+
|
|
242
|
+
// Block after 3 attempts
|
|
243
|
+
if (attemptCount >= 3) {
|
|
244
|
+
await blockMemoryAccess(blockKey, attemptCount);
|
|
245
|
+
|
|
246
|
+
return {
|
|
247
|
+
status: 'blocked',
|
|
248
|
+
memory_id,
|
|
249
|
+
reason: 'Access blocked after 3 unauthorized attempts',
|
|
250
|
+
blocked_at: new Date(),
|
|
251
|
+
attempt_count: attemptCount,
|
|
252
|
+
contact_owner: true
|
|
253
|
+
};
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
return {
|
|
257
|
+
status: 'insufficient_trust',
|
|
258
|
+
memory_id,
|
|
259
|
+
required_trust: memory.trust,
|
|
260
|
+
actual_trust: permission.trust_level,
|
|
261
|
+
trust_deficit: memory.trust - permission.trust_level,
|
|
262
|
+
attempts_made: attemptCount,
|
|
263
|
+
attempts_remaining: 3 - attemptCount,
|
|
264
|
+
new_trust_level: newTrust
|
|
265
|
+
};
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
// Access granted
|
|
269
|
+
return {
|
|
270
|
+
status: 'granted',
|
|
271
|
+
memory,
|
|
272
|
+
access_level: 'trusted'
|
|
273
|
+
};
|
|
274
|
+
}
|
|
275
|
+
```
|
|
276
|
+
|
|
277
|
+
### Usage in Tools
|
|
278
|
+
|
|
279
|
+
```typescript
|
|
280
|
+
// remember_search_memory tool
|
|
281
|
+
async function searchMemory(args: SearchArgs, context: RequestContext) {
|
|
282
|
+
const memories = await weaviate.search(args.query);
|
|
283
|
+
|
|
284
|
+
// Filter by access
|
|
285
|
+
const accessible = [];
|
|
286
|
+
const access_denied = [];
|
|
287
|
+
|
|
288
|
+
for (const memory of memories) {
|
|
289
|
+
const result = await checkMemoryAccess(memory.id, context.user_id);
|
|
290
|
+
|
|
291
|
+
switch (result.status) {
|
|
292
|
+
case 'granted':
|
|
293
|
+
accessible.push(result.memory);
|
|
294
|
+
break;
|
|
295
|
+
|
|
296
|
+
case 'insufficient_trust':
|
|
297
|
+
access_denied.push({
|
|
298
|
+
memory_id: memory.id,
|
|
299
|
+
reason: 'insufficient_trust',
|
|
300
|
+
details: `Need trust ${result.required_trust.toFixed(2)}, have ${result.actual_trust.toFixed(2)}`
|
|
301
|
+
});
|
|
302
|
+
break;
|
|
303
|
+
|
|
304
|
+
case 'blocked':
|
|
305
|
+
access_denied.push({
|
|
306
|
+
memory_id: memory.id,
|
|
307
|
+
reason: 'blocked',
|
|
308
|
+
details: result.reason
|
|
309
|
+
});
|
|
310
|
+
break;
|
|
311
|
+
|
|
312
|
+
// Other cases...
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
return {
|
|
317
|
+
results: accessible,
|
|
318
|
+
access_denied: access_denied,
|
|
319
|
+
total: accessible.length
|
|
320
|
+
};
|
|
321
|
+
}
|
|
322
|
+
```
|
|
323
|
+
|
|
324
|
+
### User-Friendly Messages
|
|
325
|
+
|
|
326
|
+
```typescript
|
|
327
|
+
function formatAccessResult(result: AccessResult): string {
|
|
328
|
+
switch (result.status) {
|
|
329
|
+
case 'granted':
|
|
330
|
+
return 'Access granted';
|
|
331
|
+
|
|
332
|
+
case 'insufficient_trust':
|
|
333
|
+
return `Insufficient trust level. You need ${result.required_trust.toFixed(2)} but have ${result.actual_trust.toFixed(2)}. Your trust has been reduced to ${result.new_trust_level.toFixed(2)}. ${result.attempts_remaining} attempts remaining before access is blocked.`;
|
|
334
|
+
|
|
335
|
+
case 'blocked':
|
|
336
|
+
return `Access to this memory has been blocked due to ${result.attempt_count} unauthorized access attempts. Please contact the memory owner to reset access.`;
|
|
337
|
+
|
|
338
|
+
case 'no_permission':
|
|
339
|
+
return `You don't have permission to access this user's memories. Please request access from the owner.`;
|
|
340
|
+
|
|
341
|
+
case 'not_found':
|
|
342
|
+
return `Memory not found.`;
|
|
343
|
+
|
|
344
|
+
case 'deleted':
|
|
345
|
+
return `This memory was deleted on ${result.deleted_at.toLocaleDateString()}.`;
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
```
|
|
349
|
+
|
|
350
|
+
---
|
|
351
|
+
|
|
352
|
+
## Benefits Summary
|
|
353
|
+
|
|
354
|
+
### 1. **Type Safety**
|
|
355
|
+
```typescript
|
|
356
|
+
const result = await checkMemoryAccess(id, user);
|
|
357
|
+
|
|
358
|
+
if (result.status === 'granted') {
|
|
359
|
+
// TypeScript knows result.memory exists
|
|
360
|
+
console.log(result.memory.content);
|
|
361
|
+
|
|
362
|
+
// TypeScript error: Property 'required_trust' does not exist
|
|
363
|
+
// console.log(result.required_trust); ❌
|
|
364
|
+
}
|
|
365
|
+
```
|
|
366
|
+
|
|
367
|
+
### 2. **Exhaustive Checking**
|
|
368
|
+
```typescript
|
|
369
|
+
function handleAccess(result: AccessResult) {
|
|
370
|
+
switch (result.status) {
|
|
371
|
+
case 'granted':
|
|
372
|
+
return result.memory;
|
|
373
|
+
case 'insufficient_trust':
|
|
374
|
+
return null;
|
|
375
|
+
case 'blocked':
|
|
376
|
+
return null;
|
|
377
|
+
// TypeScript error if we forget a case!
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
```
|
|
381
|
+
|
|
382
|
+
### 3. **Rich Context**
|
|
383
|
+
```typescript
|
|
384
|
+
// Each failure type has specific, relevant information
|
|
385
|
+
if (result.status === 'insufficient_trust') {
|
|
386
|
+
console.log(`Need ${result.trust_deficit.toFixed(2)} more trust`);
|
|
387
|
+
console.log(`${result.attempts_remaining} attempts left`);
|
|
388
|
+
}
|
|
389
|
+
```
|
|
390
|
+
|
|
391
|
+
### 4. **Better Error Handling**
|
|
392
|
+
```typescript
|
|
393
|
+
// No try/catch needed for expected failures
|
|
394
|
+
const result = await checkMemoryAccess(id, user);
|
|
395
|
+
|
|
396
|
+
// Handle each case appropriately
|
|
397
|
+
if (result.status !== 'granted') {
|
|
398
|
+
logAccessDenied(result);
|
|
399
|
+
return null;
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
// Continue with granted access
|
|
403
|
+
processMemory(result.memory);
|
|
404
|
+
```
|
|
405
|
+
|
|
406
|
+
---
|
|
407
|
+
|
|
408
|
+
## Comparison
|
|
409
|
+
|
|
410
|
+
### Exceptions (Bad)
|
|
411
|
+
```typescript
|
|
412
|
+
try {
|
|
413
|
+
const memory = await checkMemoryAccess(id, user);
|
|
414
|
+
return memory;
|
|
415
|
+
} catch (error) {
|
|
416
|
+
// String parsing, no type safety
|
|
417
|
+
if (error.message.includes('blocked')) {
|
|
418
|
+
// How many attempts? When blocked? Unknown!
|
|
419
|
+
return null;
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
```
|
|
423
|
+
|
|
424
|
+
### Result Type (Good)
|
|
425
|
+
```typescript
|
|
426
|
+
const result = await checkMemoryAccess(id, user);
|
|
427
|
+
|
|
428
|
+
if (result.status === 'blocked') {
|
|
429
|
+
// All info available, type-safe
|
|
430
|
+
console.log(`Blocked at ${result.blocked_at}`);
|
|
431
|
+
console.log(`After ${result.attempt_count} attempts`);
|
|
432
|
+
return null;
|
|
433
|
+
}
|
|
434
|
+
```
|
|
435
|
+
|
|
436
|
+
---
|
|
437
|
+
|
|
438
|
+
## Recommendation
|
|
439
|
+
|
|
440
|
+
**Use discriminated unions (Result types) for all access control operations**:
|
|
441
|
+
|
|
442
|
+
✅ `checkMemoryAccess()` → `AccessResult`
|
|
443
|
+
✅ `checkPermission()` → `PermissionResult`
|
|
444
|
+
✅ `validateTrust()` → `TrustResult`
|
|
445
|
+
✅ `createMemory()` → `CreateResult`
|
|
446
|
+
✅ `updateMemory()` → `UpdateResult`
|
|
447
|
+
|
|
448
|
+
**Reserve exceptions for truly exceptional cases**:
|
|
449
|
+
- Database connection failures
|
|
450
|
+
- Network errors
|
|
451
|
+
- Programming errors (bugs)
|
|
452
|
+
- System failures
|
|
453
|
+
|
|
454
|
+
---
|
|
455
|
+
|
|
456
|
+
**Status**: Design Specification
|
|
457
|
+
**Pattern**: Discriminated Union (Result Type)
|
|
458
|
+
**Benefit**: Type-safe, exhaustive, rich context for each failure case
|