@prmichaelsen/remember-mcp 3.19.3 → 4.0.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 (140) hide show
  1. package/AGENT.md +10 -3
  2. package/CHANGELOG.md +38 -0
  3. package/README.md +1 -1
  4. package/agent/commands/acp.artifact-glossary.md +530 -0
  5. package/agent/commands/acp.artifact-reference.md +591 -0
  6. package/agent/commands/acp.artifact-research.md +594 -0
  7. package/agent/commands/acp.audit.md +345 -0
  8. package/agent/commands/acp.clarification-address.md +185 -88
  9. package/agent/commands/acp.clarification-capture.md +44 -44
  10. package/agent/commands/acp.clarification-create.md +41 -42
  11. package/agent/commands/acp.command-create.md +49 -49
  12. package/agent/commands/acp.design-create.md +53 -35
  13. package/agent/commands/acp.design-reference.md +42 -42
  14. package/agent/commands/acp.handoff.md +35 -35
  15. package/agent/commands/acp.index.md +47 -47
  16. package/agent/commands/acp.init.md +105 -69
  17. package/agent/commands/acp.package-create.md +41 -41
  18. package/agent/commands/acp.package-info.md +40 -40
  19. package/agent/commands/acp.package-install.md +48 -48
  20. package/agent/commands/acp.package-list.md +40 -40
  21. package/agent/commands/acp.package-publish.md +62 -62
  22. package/agent/commands/acp.package-remove.md +41 -41
  23. package/agent/commands/acp.package-search.md +48 -48
  24. package/agent/commands/acp.package-update.md +50 -50
  25. package/agent/commands/acp.package-validate.md +52 -52
  26. package/agent/commands/acp.pattern-create.md +61 -43
  27. package/agent/commands/acp.plan.md +70 -47
  28. package/agent/commands/acp.proceed.md +188 -66
  29. package/agent/commands/acp.project-create.md +42 -42
  30. package/agent/commands/acp.project-info.md +46 -46
  31. package/agent/commands/acp.project-list.md +41 -41
  32. package/agent/commands/acp.project-remove.md +36 -36
  33. package/agent/commands/acp.project-set.md +33 -33
  34. package/agent/commands/acp.project-update.md +57 -57
  35. package/agent/commands/acp.projects-restore.md +37 -37
  36. package/agent/commands/acp.projects-sync.md +39 -39
  37. package/agent/commands/acp.report.md +50 -50
  38. package/agent/commands/acp.resume.md +36 -36
  39. package/agent/commands/acp.sessions.md +46 -46
  40. package/agent/commands/acp.status.md +43 -43
  41. package/agent/commands/acp.sync.md +109 -56
  42. package/agent/commands/acp.task-create.md +51 -49
  43. package/agent/commands/acp.update.md +66 -45
  44. package/agent/commands/acp.validate.md +110 -52
  45. package/agent/commands/acp.version-check-for-updates.md +40 -40
  46. package/agent/commands/acp.version-check.md +36 -36
  47. package/agent/commands/acp.version-update.md +43 -43
  48. package/agent/commands/command.template.md +40 -40
  49. package/agent/commands/git.commit.md +28 -28
  50. package/agent/commands/git.init.md +48 -48
  51. package/agent/design/design.template.md +9 -9
  52. package/agent/design/local.admin-debugging-tools.md +242 -0
  53. package/agent/design/requirements.template.md +8 -8
  54. package/agent/index/.gitkeep +0 -0
  55. package/agent/index/acp.core.yaml +137 -0
  56. package/agent/index/local.main.template.yaml +37 -0
  57. package/agent/index/local.main.yaml +48 -0
  58. package/agent/manifest.yaml +64 -0
  59. package/agent/milestones/milestone-1-{title}.template.md +8 -8
  60. package/agent/milestones/milestone-22-admin-debugging-tools.md +61 -0
  61. package/agent/milestones/milestone-23-trust-level-protection.md +122 -0
  62. package/agent/patterns/pattern.template.md +22 -22
  63. package/agent/progress.template.yaml +13 -3
  64. package/agent/progress.yaml +173 -3
  65. package/agent/schemas/package.schema.yaml +276 -0
  66. package/agent/scripts/acp.project-update.sh +5 -6
  67. package/agent/tasks/milestone-22-admin-debugging-tools/task-520-admin-gate-infrastructure.md +99 -0
  68. package/agent/tasks/milestone-22-admin-debugging-tools/task-521-schema-and-collection-tools.md +108 -0
  69. package/agent/tasks/milestone-22-admin-debugging-tools/task-522-memory-inspection-tools.md +120 -0
  70. package/agent/tasks/milestone-22-admin-debugging-tools/task-523-user-inspection-tools.md +126 -0
  71. package/agent/tasks/milestone-22-admin-debugging-tools/task-524-health-and-drift-tools.md +120 -0
  72. package/agent/tasks/milestone-23-trust-level-protection/task-525-remove-trust-from-create-update.md +69 -0
  73. package/agent/tasks/milestone-23-trust-level-protection/task-526-add-request-set-trust-level-tool.md +108 -0
  74. package/agent/tasks/milestone-23-trust-level-protection/task-527-update-confirm-deny-secret-token.md +60 -0
  75. package/agent/tasks/milestone-23-trust-level-protection/task-528-update-trust-scale-references.md +73 -0
  76. package/agent/tasks/milestone-23-trust-level-protection/task-529-version-bump-and-release.md +87 -0
  77. package/agent/tasks/task-1-{title}.template.md +18 -18
  78. package/dist/server-factory.js +779 -87
  79. package/dist/server.js +141 -41
  80. package/dist/services/trust-validator.d.ts +16 -14
  81. package/dist/tools/admin-collection-stats.d.ts +24 -0
  82. package/dist/tools/admin-detect-weaviate-drift.d.ts +26 -0
  83. package/dist/tools/admin-get-weaviate-schema.d.ts +24 -0
  84. package/dist/tools/admin-health-drift.spec.d.ts +5 -0
  85. package/dist/tools/admin-health.d.ts +15 -0
  86. package/dist/tools/admin-inspect-memory.d.ts +29 -0
  87. package/dist/tools/admin-inspect-user.d.ts +73 -0
  88. package/dist/tools/admin-inspect-user.spec.d.ts +5 -0
  89. package/dist/tools/admin-list-collections.d.ts +23 -0
  90. package/dist/tools/admin-memory-inspection.spec.d.ts +7 -0
  91. package/dist/tools/admin-schema-collection.spec.d.ts +8 -0
  92. package/dist/tools/admin-search-across-users.d.ts +42 -0
  93. package/dist/tools/confirm.d.ts +1 -0
  94. package/dist/tools/confirm.spec.d.ts +5 -0
  95. package/dist/tools/create-internal-memory.d.ts +0 -7
  96. package/dist/tools/create-memory.d.ts +0 -7
  97. package/dist/tools/deny.d.ts +1 -0
  98. package/dist/tools/deny.spec.d.ts +5 -0
  99. package/dist/tools/query-memory.d.ts +2 -0
  100. package/dist/tools/request-set-trust-level.d.ts +32 -0
  101. package/dist/tools/request-set-trust-level.spec.d.ts +2 -0
  102. package/dist/tools/search-memory.d.ts +2 -0
  103. package/dist/tools/update-internal-memory.d.ts +0 -6
  104. package/dist/tools/update-memory.d.ts +0 -7
  105. package/dist/utils/admin.d.ts +21 -0
  106. package/dist/utils/admin.spec.d.ts +2 -0
  107. package/package.json +2 -2
  108. package/src/server-factory.ts +137 -42
  109. package/src/server.ts +6 -0
  110. package/src/services/trust-validator.spec.ts +57 -51
  111. package/src/services/trust-validator.ts +28 -26
  112. package/src/tools/admin-collection-stats.ts +67 -0
  113. package/src/tools/admin-detect-weaviate-drift.ts +110 -0
  114. package/src/tools/admin-get-weaviate-schema.ts +68 -0
  115. package/src/tools/admin-health-drift.spec.ts +193 -0
  116. package/src/tools/admin-health.ts +88 -0
  117. package/src/tools/admin-inspect-memory.ts +86 -0
  118. package/src/tools/admin-inspect-user.spec.ts +130 -0
  119. package/src/tools/admin-inspect-user.ts +148 -0
  120. package/src/tools/admin-list-collections.ts +73 -0
  121. package/src/tools/admin-memory-inspection.spec.ts +206 -0
  122. package/src/tools/admin-schema-collection.spec.ts +167 -0
  123. package/src/tools/admin-search-across-users.ts +104 -0
  124. package/src/tools/confirm.spec.ts +108 -0
  125. package/src/tools/confirm.ts +24 -1
  126. package/src/tools/create-internal-memory.ts +0 -3
  127. package/src/tools/create-memory.spec.ts +6 -2
  128. package/src/tools/create-memory.ts +1 -9
  129. package/src/tools/deny.spec.ts +59 -0
  130. package/src/tools/deny.ts +6 -1
  131. package/src/tools/ghost-config.ts +19 -19
  132. package/src/tools/query-memory.ts +4 -2
  133. package/src/tools/request-set-trust-level.spec.ts +87 -0
  134. package/src/tools/request-set-trust-level.ts +107 -0
  135. package/src/tools/search-memory.ts +4 -2
  136. package/src/tools/update-internal-memory.ts +0 -3
  137. package/src/tools/update-memory.ts +0 -8
  138. package/src/types/memory.ts +1 -1
  139. package/src/utils/admin.spec.ts +70 -0
  140. package/src/utils/admin.ts +27 -0
@@ -1,6 +1,9 @@
1
1
  /**
2
2
  * Trust validator — validation and suggestions for trust-sensitive operations.
3
3
  *
4
+ * Uses integer TrustLevel 1–5 scale (higher = more confidential).
5
+ * Aligned with remember-core's trust-validator.service.
6
+ *
4
7
  * See agent/design/local.ghost-persona-system.md
5
8
  */
6
9
 
@@ -16,21 +19,20 @@ export interface TrustValidationResult {
16
19
 
17
20
  /**
18
21
  * Validate a trust level assignment.
19
- * Warns if trust < 0.25 (very private existence-only for most accessors).
22
+ * Warns if trust >= 4 (Restricted/Secretvery restrictive).
20
23
  * Returns invalid for out-of-range values.
21
24
  *
22
- * @param trustLevel - The trust level being assigned (0-1)
23
- * @param content - Optional content for context-aware validation
25
+ * @param trustLevel - The trust level being assigned (1-5 integer)
24
26
  */
25
- export function validateTrustAssignment(trustLevel: number, content?: string): TrustValidationResult {
26
- if (trustLevel < 0 || trustLevel > 1) {
27
- return { valid: false, warning: `Trust level must be between 0 and 1, got ${trustLevel}` };
27
+ export function validateTrustAssignment(trustLevel: number): TrustValidationResult {
28
+ if (!Number.isInteger(trustLevel) || trustLevel < 1 || trustLevel > 5) {
29
+ return { valid: false, warning: `Trust level must be an integer between 1 and 5, got ${trustLevel}` };
28
30
  }
29
31
 
30
- if (trustLevel < 0.25) {
32
+ if (trustLevel >= 4) {
31
33
  return {
32
34
  valid: true,
33
- warning: `Trust level ${trustLevel} is very restrictive — most accessors will see only that this memory exists. Consider 0.25+ for metadata visibility.`,
35
+ warning: `Trust level ${trustLevel} is very restrictive — most accessors will have limited visibility. Consider level 2 (INTERNAL) or 3 (CONFIDENTIAL) for broader access.`,
34
36
  };
35
37
  }
36
38
 
@@ -40,31 +42,31 @@ export function validateTrustAssignment(trustLevel: number, content?: string): T
40
42
  /**
41
43
  * Suggest an appropriate trust level based on content type and tags.
42
44
  *
43
- * Guidelines:
44
- * - System/audit/action: 0.5 (internal, summary access for trusted users)
45
- * - Personal (journal, memory, event): 0.75 (share with close friends)
46
- * - Business (invoice, contract): 0.5 (summary only for collaborators)
47
- * - Communication (email, conversation): 0.5 (summary only)
48
- * - Creative/content: 0.25 (metadata for discovery, full access for trusted)
49
- * - Default: 0.25 (conservativemetadata only)
45
+ * Guidelines (1-5 integer scale):
46
+ * - Personal (journal, memory, event): RESTRICTED (4) close contacts only
47
+ * - System/audit/action: CONFIDENTIAL (3) trusted friends
48
+ * - Business (invoice, contract): CONFIDENTIAL (3)
49
+ * - Communication (email, conversation): CONFIDENTIAL (3)
50
+ * - Ghost conversations: RESTRICTED (4)
51
+ * - Default: INTERNAL (2)conservative
50
52
  *
51
53
  * Tag overrides:
52
- * - 'private' or 'secret': 0.1 (near-hidden)
53
- * - 'public': 1.0 (open to all)
54
+ * - 'private' or 'secret': SECRET (5)
55
+ * - 'public': PUBLIC (1)
54
56
  *
55
57
  * @param contentType - The type of content
56
58
  * @param tags - Optional tags that may affect suggestion
57
- * @returns Suggested trust level (0-1)
59
+ * @returns Suggested trust level (1-5)
58
60
  */
59
61
  export function suggestTrustLevel(contentType: ContentType, tags?: string[]): number {
60
62
  // Tag-based overrides take priority
61
63
  if (tags && tags.length > 0) {
62
64
  const lowerTags = tags.map(t => t.toLowerCase());
63
65
  if (lowerTags.includes('private') || lowerTags.includes('secret')) {
64
- return 0.1;
66
+ return 5; // SECRET
65
67
  }
66
68
  if (lowerTags.includes('public')) {
67
- return 1.0;
69
+ return 1; // PUBLIC
68
70
  }
69
71
  }
70
72
 
@@ -74,32 +76,32 @@ export function suggestTrustLevel(contentType: ContentType, tags?: string[]): nu
74
76
  case 'journal':
75
77
  case 'memory':
76
78
  case 'event':
77
- return 0.75;
79
+ return 4; // RESTRICTED
78
80
 
79
81
  // System/internal
80
82
  case 'system':
81
83
  case 'audit':
82
84
  case 'action':
83
85
  case 'history':
84
- return 0.5;
86
+ return 3; // CONFIDENTIAL
85
87
 
86
88
  // Business
87
89
  case 'invoice':
88
90
  case 'contract':
89
- return 0.5;
91
+ return 3; // CONFIDENTIAL
90
92
 
91
93
  // Communication
92
94
  case 'email':
93
95
  case 'conversation':
94
96
  case 'meeting':
95
- return 0.5;
97
+ return 3; // CONFIDENTIAL
96
98
 
97
99
  // Ghost conversations — private by default
98
100
  case 'ghost':
99
- return 0.75;
101
+ return 4; // RESTRICTED
100
102
 
101
103
  // Default — conservative
102
104
  default:
103
- return 0.25;
105
+ return 2; // INTERNAL
104
106
  }
105
107
  }
@@ -0,0 +1,67 @@
1
+ /**
2
+ * remember_admin_collection_stats tool
3
+ * Returns stats for a specific Weaviate collection.
4
+ */
5
+
6
+ import { handleToolError } from '../utils/error-handler.js';
7
+ import { createDebugLogger } from '../utils/debug.js';
8
+ import type { AuthContext } from '../types/auth.js';
9
+ import { isAdmin, adminPermissionError } from '../utils/admin.js';
10
+ import { getWeaviateClient } from '../weaviate/client.js';
11
+
12
+ export const adminCollectionStatsTool = {
13
+ name: 'remember_admin_collection_stats',
14
+ description: `[Admin] Get stats for a Weaviate collection — object count, property count, configuration.
15
+
16
+ Requires admin access (ADMIN_USER_IDS).`,
17
+ inputSchema: {
18
+ type: 'object',
19
+ properties: {
20
+ collection_name: {
21
+ type: 'string',
22
+ description: 'Collection name to get stats for',
23
+ },
24
+ },
25
+ required: ['collection_name'],
26
+ },
27
+ };
28
+
29
+ export interface AdminCollectionStatsArgs {
30
+ collection_name: string;
31
+ }
32
+
33
+ export async function handleAdminCollectionStats(
34
+ args: AdminCollectionStatsArgs,
35
+ userId: string,
36
+ _authContext?: AuthContext
37
+ ): Promise<string> {
38
+ const debug = createDebugLogger({ tool: 'remember_admin_collection_stats', userId, operation: 'collection stats' });
39
+ try {
40
+ if (!isAdmin(userId)) {
41
+ return JSON.stringify(adminPermissionError());
42
+ }
43
+
44
+ debug.info('Tool invoked', { collection_name: args.collection_name });
45
+
46
+ const client = getWeaviateClient();
47
+ const collection = client.collections.get(args.collection_name);
48
+
49
+ // Get config and count in parallel
50
+ const [config, objectCount] = await Promise.all([
51
+ collection.config.get(),
52
+ collection.length(),
53
+ ]);
54
+
55
+ return JSON.stringify({
56
+ collection_name: args.collection_name,
57
+ object_count: objectCount,
58
+ property_count: config.properties.length,
59
+ properties: config.properties.map((p: any) => p.name),
60
+ vectorizers: config.vectorizers,
61
+ multiTenancy: config.multiTenancy,
62
+ replication: config.replication,
63
+ }, null, 2);
64
+ } catch (error) {
65
+ return handleToolError(error, { toolName: 'remember_admin_collection_stats', userId, operation: 'collection stats' });
66
+ }
67
+ }
@@ -0,0 +1,110 @@
1
+ /**
2
+ * remember_admin_detect_weaviate_drift tool
3
+ * Compares expected schema properties (from code) against actual Weaviate schema.
4
+ */
5
+
6
+ import { handleToolError } from '../utils/error-handler.js';
7
+ import { createDebugLogger } from '../utils/debug.js';
8
+ import type { AuthContext } from '../types/auth.js';
9
+ import { isAdmin, adminPermissionError } from '../utils/admin.js';
10
+ import { getWeaviateClient } from '../weaviate/client.js';
11
+ import {
12
+ getUserCollectionProperties,
13
+ getPublishedCollectionProperties,
14
+ } from '@prmichaelsen/remember-core/database/weaviate';
15
+
16
+ export const adminDetectWeaviateDriftTool = {
17
+ name: 'remember_admin_detect_weaviate_drift',
18
+ description: `[Admin] Compare expected vs actual Weaviate schema properties per collection.
19
+
20
+ Reports missing properties, extra properties, and overall drift status.
21
+ If no collection_ids provided, checks a sample of collections.
22
+ Requires admin access (ADMIN_USER_IDS).`,
23
+ inputSchema: {
24
+ type: 'object',
25
+ properties: {
26
+ collection_ids: {
27
+ type: 'array',
28
+ items: { type: 'string' },
29
+ description: 'Optional — specific collections to check. If omitted, samples available collections.',
30
+ },
31
+ },
32
+ },
33
+ };
34
+
35
+ export interface AdminDetectWeaviateDriftArgs {
36
+ collection_ids?: string[];
37
+ }
38
+
39
+ function getExpectedProperties(collectionName: string): string[] {
40
+ if (collectionName.startsWith('Memory_users_')) {
41
+ return getUserCollectionProperties();
42
+ }
43
+ // Space, group, and friends collections use published properties
44
+ return getPublishedCollectionProperties();
45
+ }
46
+
47
+ export async function handleAdminDetectWeaviateDrift(
48
+ args: AdminDetectWeaviateDriftArgs,
49
+ userId: string,
50
+ _authContext?: AuthContext
51
+ ): Promise<string> {
52
+ const debug = createDebugLogger({ tool: 'remember_admin_detect_weaviate_drift', userId, operation: 'detect drift' });
53
+ try {
54
+ if (!isAdmin(userId)) {
55
+ return JSON.stringify(adminPermissionError());
56
+ }
57
+
58
+ debug.info('Tool invoked', { collection_ids: args.collection_ids });
59
+
60
+ const client = getWeaviateClient();
61
+
62
+ // Determine which collections to check
63
+ let collectionNames: string[];
64
+ if (args.collection_ids && args.collection_ids.length > 0) {
65
+ collectionNames = args.collection_ids;
66
+ } else {
67
+ // Sample: list all and take first few of each type
68
+ const all = await client.collections.listAll();
69
+ const names = all.map((c: any) => c.name).filter((n: string) => n.startsWith('Memory_'));
70
+ collectionNames = names.slice(0, 5); // Sample up to 5
71
+ }
72
+
73
+ const results = [];
74
+
75
+ for (const name of collectionNames) {
76
+ try {
77
+ const collection = client.collections.get(name);
78
+ const config = await collection.config.get();
79
+ const actualProperties = config.properties.map((p: any) => p.name);
80
+ const expectedProperties = getExpectedProperties(name);
81
+
82
+ const missing = expectedProperties.filter(p => !actualProperties.includes(p));
83
+ const extra = actualProperties.filter((p: string) => !expectedProperties.includes(p));
84
+ const status = missing.length === 0 && extra.length === 0 ? 'match' : 'drift';
85
+
86
+ results.push({
87
+ collection: name,
88
+ status,
89
+ expected_count: expectedProperties.length,
90
+ actual_count: actualProperties.length,
91
+ missing_properties: missing,
92
+ extra_properties: extra,
93
+ });
94
+ } catch (err) {
95
+ results.push({
96
+ collection: name,
97
+ status: 'error',
98
+ error: err instanceof Error ? err.message : String(err),
99
+ });
100
+ }
101
+ }
102
+
103
+ return JSON.stringify({
104
+ collections_checked: results.length,
105
+ results,
106
+ }, null, 2);
107
+ } catch (error) {
108
+ return handleToolError(error, { toolName: 'remember_admin_detect_weaviate_drift', userId, operation: 'detect drift' });
109
+ }
110
+ }
@@ -0,0 +1,68 @@
1
+ /**
2
+ * remember_admin_get_weaviate_schema tool
3
+ * Inspects a Weaviate collection's schema, property types, and index config.
4
+ */
5
+
6
+ import { handleToolError } from '../utils/error-handler.js';
7
+ import { createDebugLogger } from '../utils/debug.js';
8
+ import type { AuthContext } from '../types/auth.js';
9
+ import { isAdmin, adminPermissionError } from '../utils/admin.js';
10
+ import { getWeaviateClient } from '../weaviate/client.js';
11
+
12
+ export const adminGetWeaviateSchemaTool = {
13
+ name: 'remember_admin_get_weaviate_schema',
14
+ description: `[Admin] Inspect a Weaviate collection's schema — property names, types, and configuration.
15
+
16
+ Requires admin access (ADMIN_USER_IDS).`,
17
+ inputSchema: {
18
+ type: 'object',
19
+ properties: {
20
+ collection_name: {
21
+ type: 'string',
22
+ description: 'Collection name (e.g. "Memory_users_abc123", "Memory_spaces_public")',
23
+ },
24
+ },
25
+ required: ['collection_name'],
26
+ },
27
+ };
28
+
29
+ export interface AdminGetWeaviateSchemaArgs {
30
+ collection_name: string;
31
+ }
32
+
33
+ export async function handleAdminGetWeaviateSchema(
34
+ args: AdminGetWeaviateSchemaArgs,
35
+ userId: string,
36
+ _authContext?: AuthContext
37
+ ): Promise<string> {
38
+ const debug = createDebugLogger({ tool: 'remember_admin_get_weaviate_schema', userId, operation: 'get schema' });
39
+ try {
40
+ if (!isAdmin(userId)) {
41
+ return JSON.stringify(adminPermissionError());
42
+ }
43
+
44
+ debug.info('Tool invoked', { collection_name: args.collection_name });
45
+
46
+ const client = getWeaviateClient();
47
+ const collection = client.collections.get(args.collection_name);
48
+ const config = await collection.config.get();
49
+
50
+ return JSON.stringify({
51
+ collection_name: args.collection_name,
52
+ properties: config.properties.map((p: any) => ({
53
+ name: p.name,
54
+ dataType: p.dataType,
55
+ description: p.description,
56
+ indexFilterable: p.indexFilterable,
57
+ indexSearchable: p.indexSearchable,
58
+ tokenization: p.tokenization,
59
+ })),
60
+ vectorizers: config.vectorizers,
61
+ generative: config.generative,
62
+ multiTenancy: config.multiTenancy,
63
+ replication: config.replication,
64
+ }, null, 2);
65
+ } catch (error) {
66
+ return handleToolError(error, { toolName: 'remember_admin_get_weaviate_schema', userId, operation: 'get schema' });
67
+ }
68
+ }
@@ -0,0 +1,193 @@
1
+ /**
2
+ * Tests for admin health and drift tools.
3
+ */
4
+
5
+ const mockTestWeaviate = jest.fn();
6
+ const mockTestFirestore = jest.fn();
7
+ const mockConfigGet = jest.fn();
8
+ const mockListAll = jest.fn();
9
+ const mockGetUserProps = jest.fn();
10
+ const mockGetPublishedProps = jest.fn();
11
+
12
+ jest.mock('../weaviate/client.js', () => ({
13
+ testWeaviateConnection: mockTestWeaviate,
14
+ getWeaviateClient: () => ({
15
+ collections: {
16
+ get: () => ({ config: { get: mockConfigGet } }),
17
+ listAll: mockListAll,
18
+ },
19
+ }),
20
+ }));
21
+
22
+ jest.mock('../firestore/init.js', () => ({
23
+ testFirestoreConnection: mockTestFirestore,
24
+ }));
25
+
26
+ jest.mock('@prmichaelsen/remember-core/database/weaviate', () => ({
27
+ getUserCollectionProperties: mockGetUserProps,
28
+ getPublishedCollectionProperties: mockGetPublishedProps,
29
+ }));
30
+
31
+ import { handleAdminHealth } from './admin-health.js';
32
+ import { handleAdminDetectWeaviateDrift } from './admin-detect-weaviate-drift.js';
33
+
34
+ describe('remember_admin_health', () => {
35
+ const originalEnv = process.env.ADMIN_USER_IDS;
36
+
37
+ beforeEach(() => {
38
+ process.env.ADMIN_USER_IDS = 'admin_user';
39
+ jest.clearAllMocks();
40
+ });
41
+
42
+ afterEach(() => {
43
+ if (originalEnv !== undefined) {
44
+ process.env.ADMIN_USER_IDS = originalEnv;
45
+ } else {
46
+ delete process.env.ADMIN_USER_IDS;
47
+ }
48
+ });
49
+
50
+ it('returns healthy when both services are up', async () => {
51
+ mockTestWeaviate.mockResolvedValue(true);
52
+ mockTestFirestore.mockResolvedValue(true);
53
+
54
+ const result = await handleAdminHealth({}, 'admin_user');
55
+ const parsed = JSON.parse(result);
56
+ expect(parsed.overall).toBe('healthy');
57
+ expect(parsed.weaviate.status).toBe('ok');
58
+ expect(parsed.firestore.status).toBe('ok');
59
+ expect(parsed.weaviate.latency_ms).toBeGreaterThanOrEqual(0);
60
+ });
61
+
62
+ it('returns degraded when one service is down', async () => {
63
+ mockTestWeaviate.mockResolvedValue(false);
64
+ mockTestFirestore.mockResolvedValue(true);
65
+
66
+ const result = await handleAdminHealth({}, 'admin_user');
67
+ const parsed = JSON.parse(result);
68
+ expect(parsed.overall).toBe('degraded');
69
+ });
70
+
71
+ it('returns unhealthy when both services are down', async () => {
72
+ mockTestWeaviate.mockResolvedValue(false);
73
+ mockTestFirestore.mockResolvedValue(false);
74
+
75
+ const result = await handleAdminHealth({}, 'admin_user');
76
+ const parsed = JSON.parse(result);
77
+ expect(parsed.overall).toBe('unhealthy');
78
+ });
79
+
80
+ it('handles exceptions gracefully', async () => {
81
+ mockTestWeaviate.mockRejectedValue(new Error('connection refused'));
82
+ mockTestFirestore.mockResolvedValue(true);
83
+
84
+ const result = await handleAdminHealth({}, 'admin_user');
85
+ const parsed = JSON.parse(result);
86
+ expect(parsed.overall).toBe('degraded');
87
+ expect(parsed.weaviate.status).toBe('error');
88
+ expect(parsed.weaviate.message).toContain('connection refused');
89
+ });
90
+
91
+ it('returns permission error for non-admin', async () => {
92
+ const result = await handleAdminHealth({}, 'regular_user');
93
+ const parsed = JSON.parse(result);
94
+ expect(parsed.isError).toBe(true);
95
+ });
96
+ });
97
+
98
+ describe('remember_admin_detect_weaviate_drift', () => {
99
+ const originalEnv = process.env.ADMIN_USER_IDS;
100
+
101
+ beforeEach(() => {
102
+ process.env.ADMIN_USER_IDS = 'admin_user';
103
+ jest.clearAllMocks();
104
+ mockGetUserProps.mockReturnValue(['content', 'weight', 'tags']);
105
+ mockGetPublishedProps.mockReturnValue(['content', 'weight', 'tags', 'spaces']);
106
+ });
107
+
108
+ afterEach(() => {
109
+ if (originalEnv !== undefined) {
110
+ process.env.ADMIN_USER_IDS = originalEnv;
111
+ } else {
112
+ delete process.env.ADMIN_USER_IDS;
113
+ }
114
+ });
115
+
116
+ it('reports match when schema matches expected', async () => {
117
+ mockConfigGet.mockResolvedValue({
118
+ properties: [
119
+ { name: 'content' },
120
+ { name: 'weight' },
121
+ { name: 'tags' },
122
+ ],
123
+ });
124
+
125
+ const result = await handleAdminDetectWeaviateDrift(
126
+ { collection_ids: ['Memory_users_abc'] },
127
+ 'admin_user'
128
+ );
129
+ const parsed = JSON.parse(result);
130
+ expect(parsed.results[0].status).toBe('match');
131
+ expect(parsed.results[0].missing_properties).toEqual([]);
132
+ expect(parsed.results[0].extra_properties).toEqual([]);
133
+ });
134
+
135
+ it('reports drift when properties are missing', async () => {
136
+ mockConfigGet.mockResolvedValue({
137
+ properties: [{ name: 'content' }],
138
+ });
139
+
140
+ const result = await handleAdminDetectWeaviateDrift(
141
+ { collection_ids: ['Memory_users_abc'] },
142
+ 'admin_user'
143
+ );
144
+ const parsed = JSON.parse(result);
145
+ expect(parsed.results[0].status).toBe('drift');
146
+ expect(parsed.results[0].missing_properties).toContain('weight');
147
+ expect(parsed.results[0].missing_properties).toContain('tags');
148
+ });
149
+
150
+ it('reports extra properties in Weaviate', async () => {
151
+ mockConfigGet.mockResolvedValue({
152
+ properties: [
153
+ { name: 'content' },
154
+ { name: 'weight' },
155
+ { name: 'tags' },
156
+ { name: 'legacy_field' },
157
+ ],
158
+ });
159
+
160
+ const result = await handleAdminDetectWeaviateDrift(
161
+ { collection_ids: ['Memory_users_abc'] },
162
+ 'admin_user'
163
+ );
164
+ const parsed = JSON.parse(result);
165
+ expect(parsed.results[0].status).toBe('drift');
166
+ expect(parsed.results[0].extra_properties).toContain('legacy_field');
167
+ });
168
+
169
+ it('uses published properties for space collections', async () => {
170
+ mockConfigGet.mockResolvedValue({
171
+ properties: [
172
+ { name: 'content' },
173
+ { name: 'weight' },
174
+ { name: 'tags' },
175
+ { name: 'spaces' },
176
+ ],
177
+ });
178
+
179
+ const result = await handleAdminDetectWeaviateDrift(
180
+ { collection_ids: ['Memory_spaces_public'] },
181
+ 'admin_user'
182
+ );
183
+ const parsed = JSON.parse(result);
184
+ expect(parsed.results[0].status).toBe('match');
185
+ expect(mockGetPublishedProps).toHaveBeenCalled();
186
+ });
187
+
188
+ it('returns permission error for non-admin', async () => {
189
+ const result = await handleAdminDetectWeaviateDrift({}, 'regular_user');
190
+ const parsed = JSON.parse(result);
191
+ expect(parsed.isError).toBe(true);
192
+ });
193
+ });
@@ -0,0 +1,88 @@
1
+ /**
2
+ * remember_admin_health tool
3
+ * Deep health check — Weaviate and Firestore connectivity with latency.
4
+ */
5
+
6
+ import { handleToolError } from '../utils/error-handler.js';
7
+ import { createDebugLogger } from '../utils/debug.js';
8
+ import type { AuthContext } from '../types/auth.js';
9
+ import { isAdmin, adminPermissionError } from '../utils/admin.js';
10
+ import { testWeaviateConnection } from '../weaviate/client.js';
11
+ import { testFirestoreConnection } from '../firestore/init.js';
12
+
13
+ export const adminHealthTool = {
14
+ name: 'remember_admin_health',
15
+ description: `[Admin] Deep health check — Weaviate and Firestore connectivity with latency.
16
+
17
+ Returns overall status (healthy/degraded/unhealthy).
18
+ Requires admin access (ADMIN_USER_IDS).`,
19
+ inputSchema: {
20
+ type: 'object',
21
+ properties: {},
22
+ },
23
+ };
24
+
25
+ export async function handleAdminHealth(
26
+ _args: Record<string, unknown>,
27
+ userId: string,
28
+ _authContext?: AuthContext
29
+ ): Promise<string> {
30
+ const debug = createDebugLogger({ tool: 'remember_admin_health', userId, operation: 'health check' });
31
+ try {
32
+ if (!isAdmin(userId)) {
33
+ return JSON.stringify(adminPermissionError());
34
+ }
35
+
36
+ debug.info('Tool invoked');
37
+
38
+ // Check Weaviate
39
+ const weaviateStart = Date.now();
40
+ let weaviateOk: boolean;
41
+ let weaviateError: string | undefined;
42
+ try {
43
+ weaviateOk = await testWeaviateConnection();
44
+ } catch (err) {
45
+ weaviateOk = false;
46
+ weaviateError = err instanceof Error ? err.message : String(err);
47
+ }
48
+ const weaviateLatency = Date.now() - weaviateStart;
49
+
50
+ // Check Firestore
51
+ const firestoreStart = Date.now();
52
+ let firestoreOk: boolean;
53
+ let firestoreError: string | undefined;
54
+ try {
55
+ firestoreOk = await testFirestoreConnection();
56
+ } catch (err) {
57
+ firestoreOk = false;
58
+ firestoreError = err instanceof Error ? err.message : String(err);
59
+ }
60
+ const firestoreLatency = Date.now() - firestoreStart;
61
+
62
+ // Determine overall status
63
+ let overall: 'healthy' | 'degraded' | 'unhealthy';
64
+ if (weaviateOk && firestoreOk) {
65
+ overall = 'healthy';
66
+ } else if (!weaviateOk && !firestoreOk) {
67
+ overall = 'unhealthy';
68
+ } else {
69
+ overall = 'degraded';
70
+ }
71
+
72
+ return JSON.stringify({
73
+ weaviate: {
74
+ status: weaviateOk ? 'ok' : 'error',
75
+ latency_ms: weaviateLatency,
76
+ message: weaviateError,
77
+ },
78
+ firestore: {
79
+ status: firestoreOk ? 'ok' : 'error',
80
+ latency_ms: firestoreLatency,
81
+ message: firestoreError,
82
+ },
83
+ overall,
84
+ }, null, 2);
85
+ } catch (error) {
86
+ return handleToolError(error, { toolName: 'remember_admin_health', userId, operation: 'health check' });
87
+ }
88
+ }