@prmichaelsen/remember-mcp 2.8.0 → 3.12.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.
Files changed (250) hide show
  1. package/AGENT.md +296 -250
  2. package/CHANGELOG.md +468 -0
  3. package/README.md +163 -46
  4. package/agent/commands/acp.clarification-create.md +382 -0
  5. package/agent/commands/acp.command-create.md +0 -1
  6. package/agent/commands/acp.design-create.md +0 -1
  7. package/agent/commands/acp.init.md +0 -1
  8. package/agent/commands/acp.package-create.md +0 -1
  9. package/agent/commands/acp.package-info.md +0 -1
  10. package/agent/commands/acp.package-install.md +0 -1
  11. package/agent/commands/acp.package-list.md +0 -1
  12. package/agent/commands/acp.package-publish.md +0 -1
  13. package/agent/commands/acp.package-remove.md +0 -1
  14. package/agent/commands/acp.package-search.md +0 -1
  15. package/agent/commands/acp.package-update.md +0 -1
  16. package/agent/commands/acp.package-validate.md +0 -1
  17. package/agent/commands/acp.pattern-create.md +0 -1
  18. package/agent/commands/acp.plan.md +0 -1
  19. package/agent/commands/acp.proceed.md +0 -1
  20. package/agent/commands/acp.project-create.md +0 -1
  21. package/agent/commands/acp.project-info.md +309 -0
  22. package/agent/commands/acp.project-list.md +0 -1
  23. package/agent/commands/acp.project-remove.md +379 -0
  24. package/agent/commands/acp.project-set.md +0 -1
  25. package/agent/commands/acp.project-update.md +296 -0
  26. package/agent/commands/acp.report.md +0 -1
  27. package/agent/commands/acp.resume.md +0 -1
  28. package/agent/commands/acp.status.md +0 -1
  29. package/agent/commands/acp.sync.md +0 -1
  30. package/agent/commands/acp.task-create.md +17 -10
  31. package/agent/commands/acp.update.md +0 -1
  32. package/agent/commands/acp.validate.md +0 -1
  33. package/agent/commands/acp.version-check-for-updates.md +0 -1
  34. package/agent/commands/acp.version-check.md +0 -1
  35. package/agent/commands/acp.version-update.md +0 -1
  36. package/agent/commands/command.template.md +0 -5
  37. package/agent/commands/git.commit.md +13 -2
  38. package/agent/commands/git.init.md +0 -1
  39. package/agent/design/comment-memory-type.md +2 -2
  40. package/agent/design/local.collaborative-memory-sync.md +265 -0
  41. package/agent/design/local.content-flags.md +210 -0
  42. package/agent/design/local.ghost-persona-system.md +273 -0
  43. package/agent/design/local.group-acl-integration.md +338 -0
  44. package/agent/design/local.memory-acl-schema.md +352 -0
  45. package/agent/design/local.memory-collection-pattern-v2.md +348 -0
  46. package/agent/design/local.moderation-and-space-config.md +257 -0
  47. package/agent/design/local.v2-api-reference.md +621 -0
  48. package/agent/design/local.v2-migration-guide.md +191 -0
  49. package/agent/design/local.v2-usage-examples.md +265 -0
  50. package/agent/design/permissions-storage-architecture.md +11 -3
  51. package/agent/design/soft-delete-system.md +291 -0
  52. package/agent/design/trust-escalation-prevention.md +9 -2
  53. package/agent/design/trust-system-implementation.md +12 -3
  54. package/agent/milestones/milestone-13-soft-delete-system.md +306 -0
  55. package/agent/milestones/milestone-14-memory-collection-v2.md +182 -0
  56. package/agent/milestones/milestone-15-moderation-space-config.md +126 -0
  57. package/agent/package.template.yaml +0 -17
  58. package/agent/progress.yaml +762 -49
  59. package/agent/scripts/acp.common.sh +2 -0
  60. package/agent/scripts/acp.install.sh +15 -85
  61. package/agent/scripts/acp.package-install-optimized.sh +454 -0
  62. package/agent/scripts/acp.package-install.sh +248 -380
  63. package/agent/scripts/acp.package-validate.sh +0 -99
  64. package/agent/scripts/acp.project-info.sh +218 -0
  65. package/agent/scripts/acp.project-remove.sh +302 -0
  66. package/agent/scripts/acp.project-update.sh +296 -0
  67. package/agent/scripts/acp.yaml-parser.sh +128 -10
  68. package/agent/tasks/milestone-14-memory-collection-v2/task-165-core-infrastructure-setup.md +171 -0
  69. package/agent/tasks/milestone-14-memory-collection-v2/task-166-update-remember-publish.md +191 -0
  70. package/agent/tasks/milestone-14-memory-collection-v2/task-167-update-remember-retract.md +186 -0
  71. package/agent/tasks/milestone-14-memory-collection-v2/task-168-implement-remember-revise.md +184 -0
  72. package/agent/tasks/milestone-14-memory-collection-v2/task-169-update-remember-search-space.md +179 -0
  73. package/agent/tasks/milestone-14-memory-collection-v2/task-170-update-remember-create-update.md +139 -0
  74. package/agent/tasks/milestone-14-memory-collection-v2/task-172-performance-testing-optimization.md +161 -0
  75. package/agent/tasks/milestone-14-memory-collection-v2/task-173-documentation-examples.md +258 -0
  76. package/agent/tasks/milestone-15-moderation-space-config/task-174-add-moderation-schema-fields.md +57 -0
  77. package/agent/tasks/milestone-15-moderation-space-config/task-175-create-space-config-service.md +64 -0
  78. package/agent/tasks/milestone-15-moderation-space-config/task-176-wire-moderation-publish-flow.md +45 -0
  79. package/agent/tasks/milestone-15-moderation-space-config/task-177-add-moderation-search-filters.md +70 -0
  80. package/agent/tasks/milestone-15-moderation-space-config/task-178-create-remember-moderate-tool.md +69 -0
  81. package/agent/tasks/milestone-15-moderation-space-config/task-179-documentation-integration-tests.md +58 -0
  82. package/agent/tasks/milestone-16-ghost-system/task-187-ghost-config-firestore.md +41 -0
  83. package/agent/tasks/milestone-16-ghost-system/task-188-trust-filter-integration.md +44 -0
  84. package/agent/tasks/milestone-16-ghost-system/task-189-ghost-memory-filtering.md +43 -0
  85. package/agent/tasks/milestone-16-ghost-system/task-190-ghost-config-tools.md +45 -0
  86. package/agent/tasks/milestone-16-ghost-system/task-191-escalation-firestore.md +38 -0
  87. package/agent/tasks/milestone-16-ghost-system/task-192-documentation-verification.md +39 -0
  88. package/agent/tasks/milestone-7-trust-permissions/task-180-access-result-permission-types.md +69 -0
  89. package/agent/tasks/milestone-7-trust-permissions/task-181-firestore-permissions-access-logs.md +56 -0
  90. package/agent/tasks/milestone-7-trust-permissions/task-182-trust-enforcement-service.md +68 -0
  91. package/agent/tasks/milestone-7-trust-permissions/task-183-access-control-service.md +70 -0
  92. package/agent/tasks/milestone-7-trust-permissions/task-184-permission-tools.md +79 -0
  93. package/agent/tasks/milestone-7-trust-permissions/task-185-wire-trust-into-search-query.md +55 -0
  94. package/agent/tasks/milestone-7-trust-permissions/task-186-documentation-verification.md +56 -0
  95. package/agent/tasks/task-70-add-soft-delete-schema-fields.md +165 -0
  96. package/agent/tasks/task-71-implement-delete-confirmation-flow.md +257 -0
  97. package/agent/tasks/task-72-add-deleted-filter-to-search-tools.md +18 -0
  98. package/agent/tasks/task-73-update-relationship-handling.md +18 -0
  99. package/agent/tasks/task-74-add-unit-tests-soft-delete.md +18 -0
  100. package/agent/tasks/task-75-update-documentation-changelog.md +26 -0
  101. package/agent/tasks/task-76-fix-indexnullstate-schema-bug.md +197 -0
  102. package/dist/collections/composite-ids.d.ts +106 -0
  103. package/dist/collections/core-infrastructure.spec.d.ts +11 -0
  104. package/dist/collections/dot-notation.d.ts +106 -0
  105. package/dist/collections/tracking-arrays.d.ts +176 -0
  106. package/dist/constants/content-types.d.ts +1 -0
  107. package/dist/schema/v2-collections-comments.spec.d.ts +8 -0
  108. package/dist/schema/v2-collections.d.ts +210 -0
  109. package/dist/server-factory.d.ts +15 -0
  110. package/dist/server-factory.js +3261 -1316
  111. package/dist/server.js +2926 -1236
  112. package/dist/services/access-control.d.ts +103 -0
  113. package/dist/services/access-control.spec.d.ts +2 -0
  114. package/dist/services/credentials-provider.d.ts +24 -0
  115. package/dist/services/credentials-provider.spec.d.ts +2 -0
  116. package/dist/services/escalation.service.d.ts +22 -0
  117. package/dist/services/escalation.service.spec.d.ts +2 -0
  118. package/dist/services/ghost-config.service.d.ts +55 -0
  119. package/dist/services/ghost-config.service.spec.d.ts +2 -0
  120. package/dist/services/space-config.service.d.ts +23 -0
  121. package/dist/services/space-config.service.spec.d.ts +2 -0
  122. package/dist/services/trust-enforcement.d.ts +83 -0
  123. package/dist/services/trust-enforcement.spec.d.ts +2 -0
  124. package/dist/services/trust-validator.d.ts +43 -0
  125. package/dist/services/trust-validator.spec.d.ts +2 -0
  126. package/dist/tools/confirm-publish-moderation.spec.d.ts +8 -0
  127. package/dist/tools/confirm.d.ts +8 -1
  128. package/dist/tools/create-memory.d.ts +2 -1
  129. package/dist/tools/create-memory.spec.d.ts +10 -0
  130. package/dist/tools/create-relationship.d.ts +2 -1
  131. package/dist/tools/delete-memory.d.ts +7 -31
  132. package/dist/tools/delete-relationship.d.ts +2 -1
  133. package/dist/tools/deny.d.ts +2 -1
  134. package/dist/tools/find-similar.d.ts +10 -2
  135. package/dist/tools/get-preferences.d.ts +2 -1
  136. package/dist/tools/ghost-config.d.ts +27 -0
  137. package/dist/tools/ghost-config.spec.d.ts +2 -0
  138. package/dist/tools/moderate.d.ts +20 -0
  139. package/dist/tools/moderate.spec.d.ts +5 -0
  140. package/dist/tools/publish.d.ts +11 -3
  141. package/dist/tools/query-memory.d.ts +11 -2
  142. package/dist/tools/query-space.d.ts +4 -1
  143. package/dist/tools/retract.d.ts +29 -0
  144. package/dist/tools/revise.d.ts +45 -0
  145. package/dist/tools/revise.spec.d.ts +8 -0
  146. package/dist/tools/search-memory.d.ts +8 -1
  147. package/dist/tools/search-relationship.d.ts +10 -2
  148. package/dist/tools/search-space.d.ts +25 -5
  149. package/dist/tools/search-space.spec.d.ts +9 -0
  150. package/dist/tools/set-preference.d.ts +2 -1
  151. package/dist/tools/update-memory.d.ts +2 -1
  152. package/dist/tools/update-relationship.d.ts +2 -1
  153. package/dist/types/access-result.d.ts +48 -0
  154. package/dist/types/access-result.spec.d.ts +2 -0
  155. package/dist/types/auth.d.ts +46 -0
  156. package/dist/types/ghost-config.d.ts +36 -0
  157. package/dist/types/memory.d.ts +11 -1
  158. package/dist/types/preferences.d.ts +1 -1
  159. package/dist/types/space-memory.d.ts +3 -0
  160. package/dist/utils/auth-helpers.d.ts +14 -0
  161. package/dist/utils/auth-helpers.spec.d.ts +2 -0
  162. package/dist/utils/test-data-generator.d.ts +124 -0
  163. package/dist/utils/test-data-generator.spec.d.ts +12 -0
  164. package/dist/utils/weaviate-filters.d.ts +19 -0
  165. package/dist/v2-performance.e2e.d.ts +17 -0
  166. package/dist/v2-smoke.e2e.d.ts +14 -0
  167. package/dist/weaviate/client.d.ts +5 -8
  168. package/dist/weaviate/space-schema.d.ts +2 -2
  169. package/docs/performance/v2-benchmarks.md +80 -0
  170. package/jest.e2e.config.js +14 -3
  171. package/package.json +1 -1
  172. package/scripts/.collection-recreation-state.yaml +16 -0
  173. package/scripts/.gitkeep +5 -0
  174. package/scripts/README-collection-recreation.md +224 -0
  175. package/scripts/README.md +51 -0
  176. package/scripts/backup-collections.ts +543 -0
  177. package/scripts/delete-collection.ts +137 -0
  178. package/scripts/migrate-recreate-collections.ts +578 -0
  179. package/scripts/migrate-v1-to-v2.ts +1094 -0
  180. package/scripts/package-lock.json +1113 -0
  181. package/scripts/package.json +27 -0
  182. package/src/collections/composite-ids.ts +193 -0
  183. package/src/collections/core-infrastructure.spec.ts +353 -0
  184. package/src/collections/dot-notation.ts +212 -0
  185. package/src/collections/tracking-arrays.ts +298 -0
  186. package/src/constants/content-types.ts +20 -0
  187. package/src/schema/v2-collections-comments.spec.ts +141 -0
  188. package/src/schema/v2-collections.ts +433 -0
  189. package/src/server-factory.ts +89 -20
  190. package/src/server.ts +45 -17
  191. package/src/services/access-control.spec.ts +383 -0
  192. package/src/services/access-control.ts +291 -0
  193. package/src/services/credentials-provider.spec.ts +22 -0
  194. package/src/services/credentials-provider.ts +34 -0
  195. package/src/services/escalation.service.spec.ts +183 -0
  196. package/src/services/escalation.service.ts +150 -0
  197. package/src/services/ghost-config.service.spec.ts +339 -0
  198. package/src/services/ghost-config.service.ts +219 -0
  199. package/src/services/space-config.service.spec.ts +102 -0
  200. package/src/services/space-config.service.ts +79 -0
  201. package/src/services/trust-enforcement.spec.ts +309 -0
  202. package/src/services/trust-enforcement.ts +197 -0
  203. package/src/services/trust-validator.spec.ts +108 -0
  204. package/src/services/trust-validator.ts +105 -0
  205. package/src/tools/confirm-publish-moderation.spec.ts +240 -0
  206. package/src/tools/confirm.ts +914 -116
  207. package/src/tools/create-memory.spec.ts +126 -0
  208. package/src/tools/create-memory.ts +20 -27
  209. package/src/tools/create-relationship.ts +30 -8
  210. package/src/tools/delete-memory.ts +99 -64
  211. package/src/tools/delete-relationship.ts +15 -6
  212. package/src/tools/deny.ts +8 -1
  213. package/src/tools/find-similar.ts +44 -6
  214. package/src/tools/get-preferences.ts +10 -1
  215. package/src/tools/ghost-config.spec.ts +180 -0
  216. package/src/tools/ghost-config.ts +230 -0
  217. package/src/tools/moderate.spec.ts +277 -0
  218. package/src/tools/moderate.ts +219 -0
  219. package/src/tools/publish.ts +99 -41
  220. package/src/tools/query-memory.ts +44 -9
  221. package/src/tools/query-space.ts +39 -4
  222. package/src/tools/retract.ts +292 -0
  223. package/src/tools/revise.spec.ts +146 -0
  224. package/src/tools/revise.ts +283 -0
  225. package/src/tools/search-memory.ts +46 -10
  226. package/src/tools/search-relationship.ts +30 -7
  227. package/src/tools/search-space.spec.ts +341 -0
  228. package/src/tools/search-space.ts +323 -99
  229. package/src/tools/set-preference.ts +10 -1
  230. package/src/tools/update-memory.ts +24 -5
  231. package/src/tools/update-relationship.ts +10 -1
  232. package/src/types/access-result.spec.ts +193 -0
  233. package/src/types/access-result.ts +62 -0
  234. package/src/types/auth.ts +52 -0
  235. package/src/types/ghost-config.ts +46 -0
  236. package/src/types/memory.ts +20 -1
  237. package/src/types/preferences.ts +2 -2
  238. package/src/types/space-memory.ts +5 -0
  239. package/src/utils/auth-helpers.spec.ts +75 -0
  240. package/src/utils/auth-helpers.ts +25 -0
  241. package/src/utils/test-data-generator.spec.ts +317 -0
  242. package/src/utils/test-data-generator.ts +292 -0
  243. package/src/utils/weaviate-filters.ts +32 -5
  244. package/src/v2-performance.e2e.ts +173 -0
  245. package/src/v2-smoke.e2e.ts +401 -0
  246. package/src/weaviate/client.spec.ts +5 -5
  247. package/src/weaviate/client.ts +55 -35
  248. package/src/weaviate/schema.ts +11 -239
  249. package/src/weaviate/space-schema.spec.ts +28 -25
  250. package/src/weaviate/space-schema.ts +35 -11
package/src/server.ts CHANGED
@@ -12,6 +12,7 @@ import { config, validateConfig } from './config.js';
12
12
  import { initWeaviateClient, testWeaviateConnection, getWeaviateClient } from './weaviate/client.js';
13
13
  import { initFirestore, testFirestoreConnection } from './firestore/init.js';
14
14
  import { logger } from './utils/logger.js';
15
+ import type { AuthContext } from './types/auth.js';
15
16
 
16
17
  // Import memory tools
17
18
  import { createMemoryTool, handleCreateMemory } from './tools/create-memory.js';
@@ -33,10 +34,14 @@ import { getPreferencesTool, handleGetPreferences } from './tools/get-preference
33
34
 
34
35
  // Import space tools
35
36
  import { publishTool, handlePublish } from './tools/publish.js';
37
+ import { retractTool, handleRetract } from './tools/retract.js';
38
+ import { reviseTool, handleRevise } from './tools/revise.js';
36
39
  import { confirmTool, handleConfirm } from './tools/confirm.js';
37
40
  import { denyTool, handleDeny } from './tools/deny.js';
38
41
  import { searchSpaceTool, handleSearchSpace } from './tools/search-space.js';
39
42
  import { querySpaceTool, handleQuerySpace } from './tools/query-space.js';
43
+ import { moderateTool, handleModerate } from './tools/moderate.js';
44
+ import { ghostConfigTool, handleGhostConfig } from './tools/ghost-config.js';
40
45
 
41
46
  /**
42
47
  * Initialize remember-mcp server
@@ -107,10 +112,14 @@ function registerHandlers(server: Server): void {
107
112
  getPreferencesTool,
108
113
  // Space tools
109
114
  publishTool,
115
+ retractTool,
116
+ reviseTool,
110
117
  confirmTool,
111
118
  denyTool,
112
119
  searchSpaceTool,
113
120
  querySpaceTool,
121
+ moderateTool,
122
+ ghostConfigTool,
114
123
  ],
115
124
  };
116
125
  });
@@ -126,73 +135,92 @@ function registerHandlers(server: Server): void {
126
135
  // TODO: Get userId from authentication context in M6
127
136
  const userId = (args as any).user_id || 'default_user';
128
137
 
138
+ // Standalone mode: no access token or credentials
139
+ const authContext: AuthContext = { accessToken: null, credentials: null };
140
+
129
141
  switch (name) {
130
142
  case 'remember_create_memory':
131
- result = await handleCreateMemory(args as any, userId);
143
+ result = await handleCreateMemory(args as any, userId, authContext);
132
144
  break;
133
145
 
134
146
  case 'remember_search_memory':
135
- result = await handleSearchMemory(args as any, userId);
147
+ result = await handleSearchMemory(args as any, userId, authContext);
136
148
  break;
137
149
 
138
150
  case 'remember_delete_memory':
139
- result = await handleDeleteMemory(args as any, userId);
151
+ result = await handleDeleteMemory(args as any, userId, authContext);
140
152
  break;
141
153
 
142
154
  case 'remember_update_memory':
143
- result = await handleUpdateMemory(args as any, userId);
155
+ result = await handleUpdateMemory(args as any, userId, authContext);
144
156
  break;
145
157
 
146
158
  case 'remember_find_similar':
147
- result = await handleFindSimilar(args as any, userId);
159
+ result = await handleFindSimilar(args as any, userId, authContext);
148
160
  break;
149
161
 
150
162
  case 'remember_query_memory':
151
- result = await handleQueryMemory(args as any, userId);
163
+ result = await handleQueryMemory(args as any, userId, authContext);
152
164
  break;
153
165
 
154
166
  case 'remember_create_relationship':
155
- result = await handleCreateRelationship(args as any, userId);
167
+ result = await handleCreateRelationship(args as any, userId, authContext);
156
168
  break;
157
169
 
158
170
  case 'remember_update_relationship':
159
- result = await handleUpdateRelationship(args as any, userId);
171
+ result = await handleUpdateRelationship(args as any, userId, authContext);
160
172
  break;
161
173
 
162
174
  case 'remember_search_relationship':
163
- result = await handleSearchRelationship(args as any, userId);
175
+ result = await handleSearchRelationship(args as any, userId, authContext);
164
176
  break;
165
177
 
166
178
  case 'remember_delete_relationship':
167
- result = await handleDeleteRelationship(args as any, userId);
179
+ result = await handleDeleteRelationship(args as any, userId, authContext);
168
180
  break;
169
181
 
170
182
  case 'remember_set_preference':
171
- result = await handleSetPreference(args as any, userId);
183
+ result = await handleSetPreference(args as any, userId, authContext);
172
184
  break;
173
185
 
174
186
  case 'remember_get_preferences':
175
- result = await handleGetPreferences(args as any, userId);
187
+ result = await handleGetPreferences(args as any, userId, authContext);
176
188
  break;
177
189
 
178
190
  case 'remember_publish':
179
- result = await handlePublish(args as any, userId);
191
+ result = await handlePublish(args as any, userId, authContext);
192
+ break;
193
+
194
+ case 'remember_retract':
195
+ result = await handleRetract(args as any, userId, authContext);
196
+ break;
197
+
198
+ case 'remember_revise':
199
+ result = await handleRevise(args as any, userId, authContext);
180
200
  break;
181
201
 
182
202
  case 'remember_confirm':
183
- result = await handleConfirm(args as any, userId);
203
+ result = await handleConfirm(args as any, userId, authContext);
184
204
  break;
185
205
 
186
206
  case 'remember_deny':
187
- result = await handleDeny(args as any, userId);
207
+ result = await handleDeny(args as any, userId, authContext);
188
208
  break;
189
209
 
190
210
  case 'remember_search_space':
191
- result = await handleSearchSpace(args as any, userId);
211
+ result = await handleSearchSpace(args as any, userId, authContext);
192
212
  break;
193
213
 
194
214
  case 'remember_query_space':
195
- result = await handleQuerySpace(args as any, userId);
215
+ result = await handleQuerySpace(args as any, userId, authContext);
216
+ break;
217
+
218
+ case 'remember_moderate':
219
+ result = await handleModerate(args as any, userId, authContext);
220
+ break;
221
+
222
+ case 'remember_ghost_config':
223
+ result = await handleGhostConfig(args as any, userId, authContext);
196
224
  break;
197
225
 
198
226
  default:
@@ -0,0 +1,383 @@
1
+ import {
2
+ checkMemoryAccess,
3
+ handleInsufficientTrust,
4
+ isMemoryBlocked,
5
+ resetBlock,
6
+ resolveAccessorTrustLevel,
7
+ formatAccessResultMessage,
8
+ StubGhostConfigProvider,
9
+ InMemoryEscalationStore,
10
+ } from './access-control.js';
11
+ import type { Memory } from '../types/memory.js';
12
+ import type { AccessResult } from '../types/access-result.js';
13
+ import type { GhostConfig } from '../types/ghost-config.js';
14
+ import { DEFAULT_GHOST_CONFIG } from '../types/ghost-config.js';
15
+
16
+ // ─── Fixtures ──────────────────────────────────────────────────────────────
17
+
18
+ function createMemory(overrides: Partial<Memory> = {}): Memory {
19
+ return {
20
+ id: 'mem-1',
21
+ user_id: 'owner',
22
+ doc_type: 'memory',
23
+ content: 'Test content',
24
+ type: 'note',
25
+ weight: 0.5,
26
+ trust: 0.5,
27
+ location: { gps: null, address: null, source: 'unavailable', confidence: 0, is_approximate: true },
28
+ context: { timestamp: '2026-01-01T00:00:00Z', source: { type: 'manual' } },
29
+ relationships: [],
30
+ access_count: 0,
31
+ created_at: '2026-01-01T00:00:00Z',
32
+ updated_at: '2026-01-01T00:00:00Z',
33
+ version: 1,
34
+ tags: [],
35
+ base_weight: 0.5,
36
+ ...overrides,
37
+ } as Memory;
38
+ }
39
+
40
+ function createEnabledGhostConfig(overrides: Partial<GhostConfig> = {}): GhostConfig {
41
+ return {
42
+ ...DEFAULT_GHOST_CONFIG,
43
+ enabled: true,
44
+ public_ghost_enabled: true,
45
+ default_public_trust: 0.75,
46
+ ...overrides,
47
+ };
48
+ }
49
+
50
+ // ─── checkMemoryAccess ─────────────────────────────────────────────────────
51
+
52
+ describe('checkMemoryAccess', () => {
53
+ let configProvider: StubGhostConfigProvider;
54
+ let escalationStore: InMemoryEscalationStore;
55
+
56
+ beforeEach(() => {
57
+ configProvider = new StubGhostConfigProvider();
58
+ escalationStore = new InMemoryEscalationStore();
59
+ });
60
+
61
+ it('grants access to owner (self-access)', async () => {
62
+ const memory = createMemory({ user_id: 'alice', trust: 1.0 });
63
+ const result = await checkMemoryAccess('alice', memory, configProvider, escalationStore);
64
+ expect(result.status).toBe('granted');
65
+ if (result.status === 'granted') {
66
+ expect(result.access_level).toBe('owner');
67
+ expect(result.memory.id).toBe('mem-1');
68
+ }
69
+ });
70
+
71
+ it('self-access works even with high trust memory', async () => {
72
+ const memory = createMemory({ user_id: 'alice', trust: 1.0 });
73
+ const result = await checkMemoryAccess('alice', memory, configProvider, escalationStore);
74
+ expect(result.status).toBe('granted');
75
+ });
76
+
77
+ it('returns no_permission when ghost not enabled', async () => {
78
+ const memory = createMemory({ user_id: 'owner' });
79
+ // No ghost config set — returns null
80
+ const result = await checkMemoryAccess('accessor', memory, configProvider, escalationStore);
81
+ expect(result.status).toBe('no_permission');
82
+ if (result.status === 'no_permission') {
83
+ expect(result.owner_user_id).toBe('owner');
84
+ expect(result.accessor_user_id).toBe('accessor');
85
+ }
86
+ });
87
+
88
+ it('returns no_permission when ghost explicitly disabled', async () => {
89
+ configProvider.setGhostConfig('owner', { ...DEFAULT_GHOST_CONFIG, enabled: false });
90
+ const memory = createMemory({ user_id: 'owner' });
91
+ const result = await checkMemoryAccess('accessor', memory, configProvider, escalationStore);
92
+ expect(result.status).toBe('no_permission');
93
+ });
94
+
95
+ it('returns no_permission when accessor is in blocked_users', async () => {
96
+ configProvider.setGhostConfig('owner', createEnabledGhostConfig({ blocked_users: ['bad-actor'] }));
97
+ const memory = createMemory({ user_id: 'owner', trust: 0.25 });
98
+ const result = await checkMemoryAccess('bad-actor', memory, configProvider, escalationStore);
99
+ expect(result.status).toBe('no_permission');
100
+ });
101
+
102
+ it('grants trust 1.0 memories when accessor trust is 1.0 (existence-only handled by formatting)', async () => {
103
+ configProvider.setGhostConfig('owner', createEnabledGhostConfig({
104
+ per_user_trust: { 'trusted-accessor': 1.0 },
105
+ }));
106
+ const memory = createMemory({ user_id: 'owner', trust: 1.0 });
107
+ const result = await checkMemoryAccess('trusted-accessor', memory, configProvider, escalationStore);
108
+ expect(result.status).toBe('granted');
109
+ if (result.status === 'granted') {
110
+ expect(result.access_level).toBe('trusted');
111
+ }
112
+ });
113
+
114
+ it('trust 1.0 memories trigger insufficient_trust when accessor trust < 1.0', async () => {
115
+ configProvider.setGhostConfig('owner', createEnabledGhostConfig({ default_public_trust: 0.5 }));
116
+ const memory = createMemory({ user_id: 'owner', trust: 1.0 });
117
+ const result = await checkMemoryAccess('accessor', memory, configProvider, escalationStore);
118
+ expect(result.status).toBe('insufficient_trust');
119
+ });
120
+
121
+ it('grants access when trust is sufficient', async () => {
122
+ configProvider.setGhostConfig('owner', createEnabledGhostConfig({ default_public_trust: 0.75 }));
123
+ const memory = createMemory({ user_id: 'owner', trust: 0.5 });
124
+ const result = await checkMemoryAccess('accessor', memory, configProvider, escalationStore);
125
+ expect(result.status).toBe('granted');
126
+ if (result.status === 'granted') {
127
+ expect(result.access_level).toBe('trusted');
128
+ }
129
+ });
130
+
131
+ it('returns insufficient_trust when trust is too low', async () => {
132
+ configProvider.setGhostConfig('owner', createEnabledGhostConfig({ default_public_trust: 0.25 }));
133
+ const memory = createMemory({ user_id: 'owner', trust: 0.75 });
134
+ const result = await checkMemoryAccess('accessor', memory, configProvider, escalationStore);
135
+ expect(result.status).toBe('insufficient_trust');
136
+ if (result.status === 'insufficient_trust') {
137
+ expect(result.required_trust).toBe(0.75);
138
+ expect(result.actual_trust).toBe(0.15); // 0.25 - 0.1 penalty
139
+ expect(result.attempts_remaining).toBe(2);
140
+ }
141
+ });
142
+
143
+ it('blocks after 3 failed attempts', async () => {
144
+ configProvider.setGhostConfig('owner', createEnabledGhostConfig({ default_public_trust: 0.1 }));
145
+ const memory = createMemory({ user_id: 'owner', trust: 0.75 });
146
+
147
+ // Attempt 1
148
+ const r1 = await checkMemoryAccess('accessor', memory, configProvider, escalationStore);
149
+ expect(r1.status).toBe('insufficient_trust');
150
+ if (r1.status === 'insufficient_trust') expect(r1.attempts_remaining).toBe(2);
151
+
152
+ // Attempt 2
153
+ const r2 = await checkMemoryAccess('accessor', memory, configProvider, escalationStore);
154
+ expect(r2.status).toBe('insufficient_trust');
155
+ if (r2.status === 'insufficient_trust') expect(r2.attempts_remaining).toBe(1);
156
+
157
+ // Attempt 3 — should block
158
+ const r3 = await checkMemoryAccess('accessor', memory, configProvider, escalationStore);
159
+ expect(r3.status).toBe('blocked');
160
+ if (r3.status === 'blocked') {
161
+ expect(r3.reason).toContain('3 unauthorized attempts');
162
+ }
163
+ });
164
+
165
+ it('returns blocked on subsequent accesses after block', async () => {
166
+ configProvider.setGhostConfig('owner', createEnabledGhostConfig({ default_public_trust: 0.1 }));
167
+ const memory = createMemory({ user_id: 'owner', trust: 0.75 });
168
+
169
+ // Trigger block
170
+ for (let i = 0; i < 3; i++) {
171
+ await checkMemoryAccess('accessor', memory, configProvider, escalationStore);
172
+ }
173
+
174
+ // 4th attempt — already blocked
175
+ const result = await checkMemoryAccess('accessor', memory, configProvider, escalationStore);
176
+ expect(result.status).toBe('blocked');
177
+ });
178
+
179
+ it('uses per_user_trust override', async () => {
180
+ configProvider.setGhostConfig('owner', createEnabledGhostConfig({
181
+ default_public_trust: 0.1,
182
+ per_user_trust: { 'trusted-friend': 0.9 },
183
+ }));
184
+ const memory = createMemory({ user_id: 'owner', trust: 0.75 });
185
+ const result = await checkMemoryAccess('trusted-friend', memory, configProvider, escalationStore);
186
+ expect(result.status).toBe('granted');
187
+ if (result.status === 'granted') expect(result.access_level).toBe('trusted');
188
+ });
189
+
190
+ it('block is memory-specific — other memories still accessible', async () => {
191
+ configProvider.setGhostConfig('owner', createEnabledGhostConfig({ default_public_trust: 0.1 }));
192
+ const memHigh = createMemory({ id: 'mem-high', user_id: 'owner', trust: 0.75 });
193
+ const memLow = createMemory({ id: 'mem-low', user_id: 'owner', trust: 0.05 });
194
+
195
+ // Block on high-trust memory
196
+ for (let i = 0; i < 3; i++) {
197
+ await checkMemoryAccess('accessor', memHigh, configProvider, escalationStore);
198
+ }
199
+ expect((await checkMemoryAccess('accessor', memHigh, configProvider, escalationStore)).status).toBe('blocked');
200
+
201
+ // Low-trust memory still accessible
202
+ const lowResult = await checkMemoryAccess('accessor', memLow, configProvider, escalationStore);
203
+ expect(lowResult.status).toBe('granted');
204
+ });
205
+ });
206
+
207
+ // ─── resolveAccessorTrustLevel ─────────────────────────────────────────────
208
+
209
+ describe('resolveAccessorTrustLevel', () => {
210
+ it('returns per_user_trust when set', () => {
211
+ const config = createEnabledGhostConfig({ per_user_trust: { 'alice': 0.9 } });
212
+ expect(resolveAccessorTrustLevel(config, 'alice')).toBe(0.9);
213
+ });
214
+
215
+ it('falls through to default_public_trust when no per_user_trust', () => {
216
+ const config = createEnabledGhostConfig({ default_public_trust: 0.3 });
217
+ expect(resolveAccessorTrustLevel(config, 'stranger')).toBe(0.3);
218
+ });
219
+
220
+ it('returns 0 when default_public_trust not set', () => {
221
+ const config = createEnabledGhostConfig({ default_public_trust: 0 });
222
+ expect(resolveAccessorTrustLevel(config, 'unknown')).toBe(0);
223
+ });
224
+
225
+ it('per_user_trust takes priority over default', () => {
226
+ const config = createEnabledGhostConfig({
227
+ default_public_trust: 0.1,
228
+ per_user_trust: { 'bob': 0.8 },
229
+ });
230
+ expect(resolveAccessorTrustLevel(config, 'bob')).toBe(0.8);
231
+ expect(resolveAccessorTrustLevel(config, 'carol')).toBe(0.1);
232
+ });
233
+
234
+ it('per_user_trust of 0 is used (not falsy fallthrough)', () => {
235
+ const config = createEnabledGhostConfig({
236
+ default_public_trust: 0.5,
237
+ per_user_trust: { 'restricted': 0 },
238
+ });
239
+ expect(resolveAccessorTrustLevel(config, 'restricted')).toBe(0);
240
+ });
241
+ });
242
+
243
+ // ─── isMemoryBlocked / resetBlock ──────────────────────────────────────────
244
+
245
+ describe('isMemoryBlocked', () => {
246
+ it('returns false when no block exists', async () => {
247
+ const store = new InMemoryEscalationStore();
248
+ expect(await isMemoryBlocked('owner', 'accessor', 'mem-1', store)).toBe(false);
249
+ });
250
+
251
+ it('returns true when block exists', async () => {
252
+ const store = new InMemoryEscalationStore();
253
+ await store.setBlock('owner', 'accessor', 'mem-1', {
254
+ blocked_at: '2026-01-01T00:00:00Z',
255
+ reason: 'test block',
256
+ attempt_count: 3,
257
+ });
258
+ expect(await isMemoryBlocked('owner', 'accessor', 'mem-1', store)).toBe(true);
259
+ });
260
+ });
261
+
262
+ describe('resetBlock', () => {
263
+ it('removes an existing block', async () => {
264
+ const store = new InMemoryEscalationStore();
265
+ await store.setBlock('owner', 'accessor', 'mem-1', {
266
+ blocked_at: '2026-01-01T00:00:00Z',
267
+ reason: 'test block',
268
+ attempt_count: 3,
269
+ });
270
+ expect(await isMemoryBlocked('owner', 'accessor', 'mem-1', store)).toBe(true);
271
+
272
+ await resetBlock('owner', 'accessor', 'mem-1', store);
273
+ expect(await isMemoryBlocked('owner', 'accessor', 'mem-1', store)).toBe(false);
274
+ });
275
+
276
+ it('is safe to call when no block exists', async () => {
277
+ const store = new InMemoryEscalationStore();
278
+ await expect(resetBlock('owner', 'accessor', 'mem-1', store)).resolves.toBeUndefined();
279
+ });
280
+ });
281
+
282
+ // ─── formatAccessResultMessage ─────────────────────────────────────────────
283
+
284
+ describe('formatAccessResultMessage', () => {
285
+ const memory = createMemory();
286
+
287
+ it('formats granted (owner)', () => {
288
+ const result: AccessResult = { status: 'granted', memory, access_level: 'owner' };
289
+ expect(formatAccessResultMessage(result)).toBe('Access granted (owner).');
290
+ });
291
+
292
+ it('formats granted (trusted)', () => {
293
+ const result: AccessResult = { status: 'granted', memory, access_level: 'trusted' };
294
+ expect(formatAccessResultMessage(result)).toBe('Access granted (trusted).');
295
+ });
296
+
297
+ it('formats insufficient_trust', () => {
298
+ const result: AccessResult = {
299
+ status: 'insufficient_trust',
300
+ memory_id: 'mem-1',
301
+ required_trust: 0.75,
302
+ actual_trust: 0.15,
303
+ attempts_remaining: 2,
304
+ };
305
+ const msg = formatAccessResultMessage(result);
306
+ expect(msg).toContain('0.75');
307
+ expect(msg).toContain('0.15');
308
+ expect(msg).toContain('2 attempt(s) remaining');
309
+ });
310
+
311
+ it('formats blocked', () => {
312
+ const result: AccessResult = {
313
+ status: 'blocked',
314
+ memory_id: 'mem-1',
315
+ reason: 'Too many attempts',
316
+ blocked_at: '2026-01-01T00:00:00Z',
317
+ };
318
+ expect(formatAccessResultMessage(result)).toContain('Too many attempts');
319
+ });
320
+
321
+ it('formats no_permission', () => {
322
+ const result: AccessResult = {
323
+ status: 'no_permission',
324
+ owner_user_id: 'owner',
325
+ accessor_user_id: 'accessor',
326
+ };
327
+ expect(formatAccessResultMessage(result)).toContain('No permission');
328
+ });
329
+
330
+ it('formats not_found', () => {
331
+ const result: AccessResult = { status: 'not_found', memory_id: 'mem-404' };
332
+ expect(formatAccessResultMessage(result)).toContain('mem-404');
333
+ expect(formatAccessResultMessage(result)).toContain('not found');
334
+ });
335
+
336
+ it('formats deleted', () => {
337
+ const result: AccessResult = {
338
+ status: 'deleted',
339
+ memory_id: 'mem-del',
340
+ deleted_at: '2026-01-10T00:00:00Z',
341
+ };
342
+ expect(formatAccessResultMessage(result)).toContain('mem-del');
343
+ expect(formatAccessResultMessage(result)).toContain('deleted');
344
+ });
345
+ });
346
+
347
+ // ─── handleInsufficientTrust ───────────────────────────────────────────────
348
+
349
+ describe('handleInsufficientTrust', () => {
350
+ it('applies -0.1 trust penalty', async () => {
351
+ const store = new InMemoryEscalationStore();
352
+ const result = await handleInsufficientTrust('owner', 'accessor', 'mem-1', 0.75, 0.25, store);
353
+ expect(result.status).toBe('insufficient_trust');
354
+ if (result.status === 'insufficient_trust') {
355
+ expect(result.actual_trust).toBe(0.15); // 0.25 - 0.1
356
+ }
357
+ });
358
+
359
+ it('does not let actual_trust go below 0', async () => {
360
+ const store = new InMemoryEscalationStore();
361
+ const result = await handleInsufficientTrust('owner', 'accessor', 'mem-1', 0.75, 0.05, store);
362
+ if (result.status === 'insufficient_trust') {
363
+ expect(result.actual_trust).toBe(0); // max(0, 0.05 - 0.1)
364
+ }
365
+ });
366
+
367
+ it('blocks after 3 attempts', async () => {
368
+ const store = new InMemoryEscalationStore();
369
+ await handleInsufficientTrust('owner', 'accessor', 'mem-1', 0.75, 0.25, store);
370
+ await handleInsufficientTrust('owner', 'accessor', 'mem-1', 0.75, 0.25, store);
371
+ const result = await handleInsufficientTrust('owner', 'accessor', 'mem-1', 0.75, 0.25, store);
372
+ expect(result.status).toBe('blocked');
373
+ });
374
+
375
+ it('tracks attempts_remaining correctly', async () => {
376
+ const store = new InMemoryEscalationStore();
377
+ const r1 = await handleInsufficientTrust('owner', 'accessor', 'mem-1', 0.75, 0.25, store);
378
+ if (r1.status === 'insufficient_trust') expect(r1.attempts_remaining).toBe(2);
379
+
380
+ const r2 = await handleInsufficientTrust('owner', 'accessor', 'mem-1', 0.75, 0.25, store);
381
+ if (r2.status === 'insufficient_trust') expect(r2.attempts_remaining).toBe(1);
382
+ });
383
+ });