@prmichaelsen/remember-mcp 3.14.10 → 3.14.12
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/dist/server-factory.js +3306 -3017
- package/dist/server.js +9 -121
- package/package.json +2 -3
- package/src/services/access-control.ts +1 -1
- package/src/tools/query-memory.ts +1 -2
- package/src/tools/search-memory.ts +1 -2
- package/dist/services/trust-enforcement.d.ts +0 -83
- package/dist/services/trust-enforcement.spec.d.ts +0 -2
- package/dist/utils/weaviate-filters.d.ts +0 -56
- package/dist/utils/weaviate-filters.spec.d.ts +0 -8
- package/src/services/trust-enforcement.spec.ts +0 -309
- package/src/services/trust-enforcement.ts +0 -197
- package/src/utils/weaviate-filters.spec.ts +0 -312
- package/src/utils/weaviate-filters.ts +0 -236
|
@@ -1,197 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Trust enforcement service — 3 configurable modes for cross-user memory access.
|
|
3
|
-
*
|
|
4
|
-
* - query mode (default): memories above trust threshold never returned from Weaviate
|
|
5
|
-
* - prompt mode: all memories returned, formatted/redacted by trust level
|
|
6
|
-
* - hybrid mode: query filter for trust 0.0, prompt filter for rest
|
|
7
|
-
*
|
|
8
|
-
* See agent/design/local.ghost-persona-system.md
|
|
9
|
-
*/
|
|
10
|
-
|
|
11
|
-
import type { Memory } from '../types/memory.js';
|
|
12
|
-
import type { TrustEnforcementMode } from '../types/ghost-config.js';
|
|
13
|
-
|
|
14
|
-
// ─── Trust Level Thresholds ────────────────────────────────────────────────
|
|
15
|
-
|
|
16
|
-
/** Trust level thresholds mapping continuous 0-1 values to discrete behavior tiers */
|
|
17
|
-
export const TRUST_THRESHOLDS = {
|
|
18
|
-
FULL_ACCESS: 1.0,
|
|
19
|
-
PARTIAL_ACCESS: 0.75,
|
|
20
|
-
SUMMARY_ONLY: 0.5,
|
|
21
|
-
METADATA_ONLY: 0.25,
|
|
22
|
-
EXISTENCE_ONLY: 0.0,
|
|
23
|
-
} as const;
|
|
24
|
-
|
|
25
|
-
// ─── Query-Level Enforcement ───────────────────────────────────────────────
|
|
26
|
-
|
|
27
|
-
/**
|
|
28
|
-
* Build a Weaviate filter that restricts memories by trust score.
|
|
29
|
-
* Only returns memories where trust_score <= accessorTrustLevel.
|
|
30
|
-
*
|
|
31
|
-
* Trust 1.0 memories require accessor trust >= 1.0 to even appear in results.
|
|
32
|
-
* When they do appear, formatMemoryForPrompt caps output to existence-only.
|
|
33
|
-
*
|
|
34
|
-
* @param collection - Weaviate collection instance
|
|
35
|
-
* @param accessorTrustLevel - The accessor's trust level (0-1)
|
|
36
|
-
* @returns Weaviate filter object
|
|
37
|
-
*/
|
|
38
|
-
export function buildTrustFilter(collection: any, accessorTrustLevel: number): any {
|
|
39
|
-
return collection.filter.byProperty('trust_score').lessThanOrEqual(accessorTrustLevel);
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
// ─── Prompt-Level Enforcement ──────────────────────────────────────────────
|
|
43
|
-
|
|
44
|
-
/**
|
|
45
|
-
* Formatted memory representation for prompt-level enforcement.
|
|
46
|
-
* Content is redacted/formatted based on trust level.
|
|
47
|
-
*/
|
|
48
|
-
export interface FormattedMemory {
|
|
49
|
-
memory_id: string;
|
|
50
|
-
trust_tier: string;
|
|
51
|
-
content: string;
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
/**
|
|
55
|
-
* Format a memory for inclusion in an LLM prompt, redacted by trust level.
|
|
56
|
-
*
|
|
57
|
-
* Trust tiers:
|
|
58
|
-
* - 1.0 Full Access: full content, all details
|
|
59
|
-
* - 0.75 Partial Access: content with sensitive fields redacted
|
|
60
|
-
* - 0.5 Summary Only: title + summary, no content
|
|
61
|
-
* - 0.25 Metadata Only: type, date, tags — no content or summary
|
|
62
|
-
* - 0.0 Existence Only: "A memory exists about this topic"
|
|
63
|
-
*
|
|
64
|
-
* Trust 1.0 memories are always existence-only for cross-users, regardless of
|
|
65
|
-
* accessor trust level. Use `isSelfAccess = true` to bypass for owner access.
|
|
66
|
-
*
|
|
67
|
-
* @param memory - The memory to format
|
|
68
|
-
* @param accessorTrustLevel - The accessor's trust level (0-1)
|
|
69
|
-
* @param isSelfAccess - True if the accessor is the memory owner (bypasses trust 1.0 cap)
|
|
70
|
-
* @returns Formatted memory for prompt inclusion
|
|
71
|
-
*/
|
|
72
|
-
export function formatMemoryForPrompt(memory: Memory, accessorTrustLevel: number, isSelfAccess = false): FormattedMemory {
|
|
73
|
-
// Trust 1.0 = existence-only for cross-users (acknowledged but never revealed)
|
|
74
|
-
if (!isSelfAccess && memory.trust >= 1.0) {
|
|
75
|
-
return {
|
|
76
|
-
memory_id: memory.id,
|
|
77
|
-
trust_tier: 'Existence Only',
|
|
78
|
-
content: 'A memory exists about this topic.',
|
|
79
|
-
};
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
const tier = getTrustLevelLabel(accessorTrustLevel);
|
|
83
|
-
|
|
84
|
-
if (accessorTrustLevel >= TRUST_THRESHOLDS.FULL_ACCESS) {
|
|
85
|
-
// Full Access — all content
|
|
86
|
-
const parts = [`[${memory.type}] ${memory.title || 'Untitled'}`];
|
|
87
|
-
parts.push(memory.content);
|
|
88
|
-
if (memory.summary) parts.push(`Summary: ${memory.summary}`);
|
|
89
|
-
if (memory.tags.length > 0) parts.push(`Tags: ${memory.tags.join(', ')}`);
|
|
90
|
-
if (memory.created_at) parts.push(`Created: ${memory.created_at}`);
|
|
91
|
-
return { memory_id: memory.id, trust_tier: tier, content: parts.join('\n') };
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
if (accessorTrustLevel >= TRUST_THRESHOLDS.PARTIAL_ACCESS) {
|
|
95
|
-
// Partial Access — redact sensitive fields
|
|
96
|
-
const redacted = redactSensitiveFields(memory, accessorTrustLevel);
|
|
97
|
-
const parts = [`[${redacted.type}] ${redacted.title || 'Untitled'}`];
|
|
98
|
-
parts.push(redacted.content);
|
|
99
|
-
if (redacted.tags.length > 0) parts.push(`Tags: ${redacted.tags.join(', ')}`);
|
|
100
|
-
return { memory_id: memory.id, trust_tier: tier, content: parts.join('\n') };
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
if (accessorTrustLevel >= TRUST_THRESHOLDS.SUMMARY_ONLY) {
|
|
104
|
-
// Summary Only — title + summary, no content body
|
|
105
|
-
const parts = [`[${memory.type}] ${memory.title || 'Untitled'}`];
|
|
106
|
-
if (memory.summary) {
|
|
107
|
-
parts.push(memory.summary);
|
|
108
|
-
} else {
|
|
109
|
-
parts.push('(No summary available)');
|
|
110
|
-
}
|
|
111
|
-
return { memory_id: memory.id, trust_tier: tier, content: parts.join('\n') };
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
if (accessorTrustLevel >= TRUST_THRESHOLDS.METADATA_ONLY) {
|
|
115
|
-
// Metadata Only — type, date, tags
|
|
116
|
-
const parts = [`[${memory.type}]`];
|
|
117
|
-
if (memory.created_at) parts.push(`Created: ${memory.created_at}`);
|
|
118
|
-
if (memory.tags.length > 0) parts.push(`Tags: ${memory.tags.join(', ')}`);
|
|
119
|
-
return { memory_id: memory.id, trust_tier: tier, content: parts.join('\n') };
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
// Existence Only — minimal hint
|
|
123
|
-
return {
|
|
124
|
-
memory_id: memory.id,
|
|
125
|
-
trust_tier: tier,
|
|
126
|
-
content: 'A memory exists about this topic.',
|
|
127
|
-
};
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
// ─── Shared Utilities ──────────────────────────────────────────────────────
|
|
131
|
-
|
|
132
|
-
/**
|
|
133
|
-
* Get a human-readable label for a trust level.
|
|
134
|
-
*/
|
|
135
|
-
export function getTrustLevelLabel(trust: number): string {
|
|
136
|
-
if (trust >= TRUST_THRESHOLDS.FULL_ACCESS) return 'Full Access';
|
|
137
|
-
if (trust >= TRUST_THRESHOLDS.PARTIAL_ACCESS) return 'Partial Access';
|
|
138
|
-
if (trust >= TRUST_THRESHOLDS.SUMMARY_ONLY) return 'Summary Only';
|
|
139
|
-
if (trust >= TRUST_THRESHOLDS.METADATA_ONLY) return 'Metadata Only';
|
|
140
|
-
return 'Existence Only';
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
/**
|
|
144
|
-
* Get LLM instruction text describing what to reveal at a given trust level.
|
|
145
|
-
*/
|
|
146
|
-
export function getTrustInstructions(trust: number): string {
|
|
147
|
-
if (trust >= TRUST_THRESHOLDS.FULL_ACCESS) {
|
|
148
|
-
return 'You have full access to this memory. Share all content and details freely.';
|
|
149
|
-
}
|
|
150
|
-
if (trust >= TRUST_THRESHOLDS.PARTIAL_ACCESS) {
|
|
151
|
-
return 'You have partial access. Share the main content but do not reveal sensitive personal details like exact locations, contact information, or financial data.';
|
|
152
|
-
}
|
|
153
|
-
if (trust >= TRUST_THRESHOLDS.SUMMARY_ONLY) {
|
|
154
|
-
return 'You have summary-level access. Share the title and summary only. Do not reveal the full content of this memory.';
|
|
155
|
-
}
|
|
156
|
-
if (trust >= TRUST_THRESHOLDS.METADATA_ONLY) {
|
|
157
|
-
return 'You have metadata-level access only. You may mention the type, date, and tags, but do not reveal any content or summary.';
|
|
158
|
-
}
|
|
159
|
-
return 'You may only acknowledge that a memory exists about this topic. Do not reveal any details.';
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
/**
|
|
163
|
-
* Redact sensitive fields from a memory for partial access.
|
|
164
|
-
* Returns a copy with location, context, and references cleared.
|
|
165
|
-
*/
|
|
166
|
-
export function redactSensitiveFields(memory: Memory, _trust: number): Memory {
|
|
167
|
-
return {
|
|
168
|
-
...memory,
|
|
169
|
-
// Clear sensitive location data
|
|
170
|
-
location: { gps: null, address: null, source: 'unavailable', confidence: 0, is_approximate: true },
|
|
171
|
-
// Strip context details
|
|
172
|
-
context: {
|
|
173
|
-
...memory.context,
|
|
174
|
-
participants: undefined,
|
|
175
|
-
environment: undefined,
|
|
176
|
-
notes: undefined,
|
|
177
|
-
},
|
|
178
|
-
// Clear references (may contain private URLs)
|
|
179
|
-
references: undefined,
|
|
180
|
-
};
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
/**
|
|
184
|
-
* Check whether an accessor's trust level is sufficient for a memory.
|
|
185
|
-
* Access is granted when accessorTrust >= memoryTrust.
|
|
186
|
-
*/
|
|
187
|
-
export function isTrustSufficient(memoryTrust: number, accessorTrust: number): boolean {
|
|
188
|
-
return accessorTrust >= memoryTrust;
|
|
189
|
-
}
|
|
190
|
-
|
|
191
|
-
/**
|
|
192
|
-
* Determine the enforcement mode to use.
|
|
193
|
-
* Convenience function that returns the mode from GhostConfig or falls back to 'query'.
|
|
194
|
-
*/
|
|
195
|
-
export function resolveEnforcementMode(mode?: TrustEnforcementMode): TrustEnforcementMode {
|
|
196
|
-
return mode ?? 'query';
|
|
197
|
-
}
|
|
@@ -1,312 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Unit tests for Weaviate v3 filter builders
|
|
3
|
-
*
|
|
4
|
-
* Note: These tests verify filter builder logic, not exact Weaviate filter structure.
|
|
5
|
-
* The actual Filters.and() and Filters.or() methods return Weaviate internal objects.
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
import {
|
|
9
|
-
buildCombinedSearchFilters,
|
|
10
|
-
buildMemoryOnlyFilters,
|
|
11
|
-
buildRelationshipOnlyFilters,
|
|
12
|
-
hasFilters,
|
|
13
|
-
} from './weaviate-filters.js';
|
|
14
|
-
import type { SearchFilters } from '../types/memory.js';
|
|
15
|
-
|
|
16
|
-
/**
|
|
17
|
-
* Mock Weaviate collection with filter builder
|
|
18
|
-
*/
|
|
19
|
-
function createMockCollection() {
|
|
20
|
-
const mockFilter = {
|
|
21
|
-
byProperty: (property: string) => ({
|
|
22
|
-
equal: (value: any) => ({ property, operator: 'equal', value }),
|
|
23
|
-
greaterThanOrEqual: (value: any) => ({ property, operator: 'gte', value }),
|
|
24
|
-
lessThanOrEqual: (value: any) => ({ property, operator: 'lte', value }),
|
|
25
|
-
containsAny: (values: any[]) => ({ property, operator: 'containsAny', values }),
|
|
26
|
-
}),
|
|
27
|
-
};
|
|
28
|
-
|
|
29
|
-
return {
|
|
30
|
-
filter: mockFilter,
|
|
31
|
-
};
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
describe('weaviate-filters', () => {
|
|
35
|
-
let mockCollection: any;
|
|
36
|
-
|
|
37
|
-
beforeEach(() => {
|
|
38
|
-
mockCollection = createMockCollection();
|
|
39
|
-
});
|
|
40
|
-
|
|
41
|
-
describe('buildMemoryOnlyFilters', () => {
|
|
42
|
-
it('should build filter with only doc_type when no other filters provided', () => {
|
|
43
|
-
const result = buildMemoryOnlyFilters(mockCollection);
|
|
44
|
-
expect(result).toBeDefined();
|
|
45
|
-
expect(result.property).toBe('doc_type');
|
|
46
|
-
expect(result.value).toBe('memory');
|
|
47
|
-
});
|
|
48
|
-
|
|
49
|
-
it('should build filter with doc_type and content type', () => {
|
|
50
|
-
const filters: SearchFilters = {
|
|
51
|
-
types: ['note'],
|
|
52
|
-
};
|
|
53
|
-
|
|
54
|
-
const result = buildMemoryOnlyFilters(mockCollection, filters);
|
|
55
|
-
expect(result).toBeDefined();
|
|
56
|
-
// Filters.and() returns Weaviate internal structure
|
|
57
|
-
// Just verify it's defined and has the filters property
|
|
58
|
-
expect(result.filters || result.operands).toBeDefined();
|
|
59
|
-
});
|
|
60
|
-
|
|
61
|
-
it('should build filter with weight_min', () => {
|
|
62
|
-
const filters: SearchFilters = {
|
|
63
|
-
weight_min: 0.5,
|
|
64
|
-
};
|
|
65
|
-
|
|
66
|
-
const result = buildMemoryOnlyFilters(mockCollection, filters);
|
|
67
|
-
expect(result).toBeDefined();
|
|
68
|
-
});
|
|
69
|
-
|
|
70
|
-
it('should build filter with trust and date filters', () => {
|
|
71
|
-
const filters: SearchFilters = {
|
|
72
|
-
trust_min: 0.3,
|
|
73
|
-
date_from: '2024-01-01',
|
|
74
|
-
};
|
|
75
|
-
|
|
76
|
-
const result = buildMemoryOnlyFilters(mockCollection, filters);
|
|
77
|
-
expect(result).toBeDefined();
|
|
78
|
-
});
|
|
79
|
-
|
|
80
|
-
it('should build filter with tags', () => {
|
|
81
|
-
const filters: SearchFilters = {
|
|
82
|
-
tags: ['work', 'important'],
|
|
83
|
-
};
|
|
84
|
-
|
|
85
|
-
const result = buildMemoryOnlyFilters(mockCollection, filters);
|
|
86
|
-
expect(result).toBeDefined();
|
|
87
|
-
});
|
|
88
|
-
|
|
89
|
-
it('should build complex filter with multiple criteria', () => {
|
|
90
|
-
const filters: SearchFilters = {
|
|
91
|
-
types: ['note', 'todo'],
|
|
92
|
-
weight_min: 0.5,
|
|
93
|
-
trust_min: 0.3,
|
|
94
|
-
date_from: '2024-01-01',
|
|
95
|
-
tags: ['work'],
|
|
96
|
-
};
|
|
97
|
-
|
|
98
|
-
const result = buildMemoryOnlyFilters(mockCollection, filters);
|
|
99
|
-
expect(result).toBeDefined();
|
|
100
|
-
});
|
|
101
|
-
});
|
|
102
|
-
|
|
103
|
-
describe('buildRelationshipOnlyFilters', () => {
|
|
104
|
-
it('should build filter with only doc_type', () => {
|
|
105
|
-
const result = buildRelationshipOnlyFilters(mockCollection);
|
|
106
|
-
expect(result).toBeDefined();
|
|
107
|
-
expect(result.property).toBe('doc_type');
|
|
108
|
-
expect(result.value).toBe('relationship');
|
|
109
|
-
});
|
|
110
|
-
|
|
111
|
-
it('should NOT include type filter for relationships', () => {
|
|
112
|
-
const filters: SearchFilters = {
|
|
113
|
-
types: ['note'],
|
|
114
|
-
};
|
|
115
|
-
|
|
116
|
-
const result = buildRelationshipOnlyFilters(mockCollection, filters);
|
|
117
|
-
expect(result).toBeDefined();
|
|
118
|
-
expect(result.property).toBe('doc_type');
|
|
119
|
-
});
|
|
120
|
-
|
|
121
|
-
it('should build filter with weight and trust', () => {
|
|
122
|
-
const filters: SearchFilters = {
|
|
123
|
-
weight_min: 0.5,
|
|
124
|
-
trust_min: 0.3,
|
|
125
|
-
};
|
|
126
|
-
|
|
127
|
-
const result = buildRelationshipOnlyFilters(mockCollection, filters);
|
|
128
|
-
expect(result).toBeDefined();
|
|
129
|
-
});
|
|
130
|
-
|
|
131
|
-
it('should build filter with date and tags', () => {
|
|
132
|
-
const filters: SearchFilters = {
|
|
133
|
-
date_from: '2024-01-01',
|
|
134
|
-
tags: ['important'],
|
|
135
|
-
};
|
|
136
|
-
|
|
137
|
-
const result = buildRelationshipOnlyFilters(mockCollection, filters);
|
|
138
|
-
expect(result).toBeDefined();
|
|
139
|
-
});
|
|
140
|
-
});
|
|
141
|
-
|
|
142
|
-
describe('buildCombinedSearchFilters', () => {
|
|
143
|
-
it('should combine memory and relationship filters', () => {
|
|
144
|
-
const result = buildCombinedSearchFilters(mockCollection);
|
|
145
|
-
expect(result).toBeDefined();
|
|
146
|
-
// Filters.or() returns Weaviate internal structure
|
|
147
|
-
expect(result.filters || result.operands).toBeDefined();
|
|
148
|
-
});
|
|
149
|
-
|
|
150
|
-
it('should handle filters that apply to both types', () => {
|
|
151
|
-
const filters: SearchFilters = {
|
|
152
|
-
weight_min: 0.5,
|
|
153
|
-
trust_min: 0.3,
|
|
154
|
-
};
|
|
155
|
-
|
|
156
|
-
const result = buildCombinedSearchFilters(mockCollection, filters);
|
|
157
|
-
expect(result).toBeDefined();
|
|
158
|
-
});
|
|
159
|
-
|
|
160
|
-
it('should handle type filter (only for memories)', () => {
|
|
161
|
-
const filters: SearchFilters = {
|
|
162
|
-
types: ['note'],
|
|
163
|
-
weight_min: 0.5,
|
|
164
|
-
};
|
|
165
|
-
|
|
166
|
-
const result = buildCombinedSearchFilters(mockCollection, filters);
|
|
167
|
-
expect(result).toBeDefined();
|
|
168
|
-
});
|
|
169
|
-
|
|
170
|
-
it('should handle complex filters', () => {
|
|
171
|
-
const filters: SearchFilters = {
|
|
172
|
-
types: ['note'],
|
|
173
|
-
weight_min: 0.5,
|
|
174
|
-
weight_max: 0.9,
|
|
175
|
-
trust_min: 0.3,
|
|
176
|
-
date_from: '2024-01-01',
|
|
177
|
-
date_to: '2024-12-31',
|
|
178
|
-
tags: ['work', 'important'],
|
|
179
|
-
};
|
|
180
|
-
|
|
181
|
-
const result = buildCombinedSearchFilters(mockCollection, filters);
|
|
182
|
-
expect(result).toBeDefined();
|
|
183
|
-
});
|
|
184
|
-
});
|
|
185
|
-
|
|
186
|
-
describe('hasFilters', () => {
|
|
187
|
-
it('should return true for defined filter', () => {
|
|
188
|
-
const filter = { property: 'doc_type', operator: 'equal', value: 'memory' };
|
|
189
|
-
expect(hasFilters(filter)).toBe(true);
|
|
190
|
-
});
|
|
191
|
-
|
|
192
|
-
it('should return false for undefined', () => {
|
|
193
|
-
expect(hasFilters(undefined)).toBe(false);
|
|
194
|
-
});
|
|
195
|
-
|
|
196
|
-
it('should return false for null', () => {
|
|
197
|
-
expect(hasFilters(null)).toBe(false);
|
|
198
|
-
});
|
|
199
|
-
|
|
200
|
-
it('should return true for empty object', () => {
|
|
201
|
-
expect(hasFilters({})).toBe(true);
|
|
202
|
-
});
|
|
203
|
-
|
|
204
|
-
it('should return true for complex filter', () => {
|
|
205
|
-
const filter = {
|
|
206
|
-
filters: [
|
|
207
|
-
{ property: 'doc_type', operator: 'equal', value: 'memory' },
|
|
208
|
-
{ property: 'weight', operator: 'gte', value: 0.5 },
|
|
209
|
-
],
|
|
210
|
-
};
|
|
211
|
-
expect(hasFilters(filter)).toBe(true);
|
|
212
|
-
});
|
|
213
|
-
});
|
|
214
|
-
|
|
215
|
-
describe('edge cases', () => {
|
|
216
|
-
it('should handle empty types array', () => {
|
|
217
|
-
const filters: SearchFilters = {
|
|
218
|
-
types: [],
|
|
219
|
-
};
|
|
220
|
-
|
|
221
|
-
const result = buildMemoryOnlyFilters(mockCollection, filters);
|
|
222
|
-
expect(result).toBeDefined();
|
|
223
|
-
expect(result.property).toBe('doc_type');
|
|
224
|
-
});
|
|
225
|
-
|
|
226
|
-
it('should handle empty tags array', () => {
|
|
227
|
-
const filters: SearchFilters = {
|
|
228
|
-
tags: [],
|
|
229
|
-
};
|
|
230
|
-
|
|
231
|
-
const result = buildMemoryOnlyFilters(mockCollection, filters);
|
|
232
|
-
expect(result).toBeDefined();
|
|
233
|
-
});
|
|
234
|
-
|
|
235
|
-
it('should handle weight_min of 0', () => {
|
|
236
|
-
const filters: SearchFilters = {
|
|
237
|
-
weight_min: 0,
|
|
238
|
-
};
|
|
239
|
-
|
|
240
|
-
const result = buildMemoryOnlyFilters(mockCollection, filters);
|
|
241
|
-
expect(result).toBeDefined();
|
|
242
|
-
});
|
|
243
|
-
|
|
244
|
-
it('should handle trust_min of 0', () => {
|
|
245
|
-
const filters: SearchFilters = {
|
|
246
|
-
trust_min: 0,
|
|
247
|
-
};
|
|
248
|
-
|
|
249
|
-
const result = buildMemoryOnlyFilters(mockCollection, filters);
|
|
250
|
-
expect(result).toBeDefined();
|
|
251
|
-
});
|
|
252
|
-
|
|
253
|
-
it('should handle weight range', () => {
|
|
254
|
-
const filters: SearchFilters = {
|
|
255
|
-
weight_min: 0.3,
|
|
256
|
-
weight_max: 0.7,
|
|
257
|
-
};
|
|
258
|
-
|
|
259
|
-
const result = buildMemoryOnlyFilters(mockCollection, filters);
|
|
260
|
-
expect(result).toBeDefined();
|
|
261
|
-
});
|
|
262
|
-
|
|
263
|
-
it('should handle trust range', () => {
|
|
264
|
-
const filters: SearchFilters = {
|
|
265
|
-
trust_min: 0.2,
|
|
266
|
-
trust_max: 0.8,
|
|
267
|
-
};
|
|
268
|
-
|
|
269
|
-
const result = buildMemoryOnlyFilters(mockCollection, filters);
|
|
270
|
-
expect(result).toBeDefined();
|
|
271
|
-
});
|
|
272
|
-
});
|
|
273
|
-
|
|
274
|
-
describe('undefined/null filter handling', () => {
|
|
275
|
-
it('should not create Or operator with empty operands', () => {
|
|
276
|
-
const emptyCollection = {
|
|
277
|
-
filter: {
|
|
278
|
-
byProperty: () => ({
|
|
279
|
-
equal: () => undefined,
|
|
280
|
-
greaterThanOrEqual: () => undefined,
|
|
281
|
-
lessThanOrEqual: () => undefined,
|
|
282
|
-
containsAny: () => undefined,
|
|
283
|
-
}),
|
|
284
|
-
},
|
|
285
|
-
};
|
|
286
|
-
|
|
287
|
-
const result = buildCombinedSearchFilters(emptyCollection);
|
|
288
|
-
expect(result).toBeUndefined();
|
|
289
|
-
});
|
|
290
|
-
|
|
291
|
-
it('should not create And operator with empty operands', () => {
|
|
292
|
-
const emptyCollection = {
|
|
293
|
-
filter: {
|
|
294
|
-
byProperty: () => ({
|
|
295
|
-
equal: () => undefined,
|
|
296
|
-
}),
|
|
297
|
-
},
|
|
298
|
-
};
|
|
299
|
-
|
|
300
|
-
const result = buildMemoryOnlyFilters(emptyCollection);
|
|
301
|
-
expect(result).toBeUndefined();
|
|
302
|
-
});
|
|
303
|
-
|
|
304
|
-
it('should handle mixed valid and undefined filters', () => {
|
|
305
|
-
const result = buildCombinedSearchFilters(mockCollection, {
|
|
306
|
-
types: ['note'],
|
|
307
|
-
});
|
|
308
|
-
|
|
309
|
-
expect(result).toBeDefined();
|
|
310
|
-
});
|
|
311
|
-
});
|
|
312
|
-
});
|