@prmichaelsen/remember-mcp 3.14.11 → 3.14.14
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/CHANGELOG.md +20 -0
- package/agent/milestones/milestone-18-performance-tuning.md +45 -0
- package/agent/progress.yaml +86 -2
- package/agent/tasks/task-77-parallelize-checkiffriend.md +62 -0
- package/agent/tasks/task-78-cache-checkiffriend.md +78 -0
- package/agent/tasks/task-79-memoize-core-services.md +75 -0
- package/agent/tasks/task-80-parallelize-startup-health-checks.md +57 -0
- package/agent/tasks/task-81-optimize-ghost-config-block-unblock.md +64 -0
- package/agent/tasks/task-82-native-weaviate-offset.md +69 -0
- package/agent/tasks/task-83-eliminate-redundant-validatetoken.md +53 -0
- package/agent/tasks/task-84-static-imports-server-factory.md +52 -0
- package/dist/core-services.d.ts +3 -1
- package/dist/server-factory.js +250 -482
- package/dist/server.js +29 -140
- package/dist/services/access-control.d.ts +5 -5
- package/dist/services/ghost-config.service.d.ts +2 -0
- package/package.json +1 -1
- package/src/core-services.ts +16 -2
- package/src/server-factory.ts +5 -3
- package/src/server.ts +5 -3
- package/src/services/access-control.spec.ts +11 -11
- package/src/services/access-control.ts +74 -8
- package/src/services/ghost-config.service.spec.ts +22 -15
- package/src/services/ghost-config.service.ts +9 -15
- package/src/tools/query-memory.ts +1 -2
- package/src/tools/search-memory.ts +5 -8
- 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
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
* See agent/design/local.ghost-persona-system.md
|
|
10
10
|
*/
|
|
11
11
|
|
|
12
|
-
import { getDocument, setDocument } from '../firestore/init.js';
|
|
12
|
+
import { getDocument, setDocument, FieldValue } from '../firestore/init.js';
|
|
13
13
|
import { BASE } from '../firestore/paths.js';
|
|
14
14
|
import { logger } from '../utils/logger.js';
|
|
15
15
|
import type { GhostConfig, TrustEnforcementMode } from '../types/ghost-config.js';
|
|
@@ -121,19 +121,16 @@ export async function removeUserTrust(
|
|
|
121
121
|
|
|
122
122
|
/**
|
|
123
123
|
* Block a user from ghost access.
|
|
124
|
+
* Uses FieldValue.arrayUnion for atomic add without reading first (idempotent).
|
|
124
125
|
*/
|
|
125
126
|
export async function blockUser(
|
|
126
127
|
ownerUserId: string,
|
|
127
128
|
targetUserId: string
|
|
128
129
|
): Promise<void> {
|
|
129
|
-
const current = await getGhostConfig(ownerUserId);
|
|
130
|
-
if (current.blocked_users.includes(targetUserId)) {
|
|
131
|
-
return; // already blocked
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
const blocked_users = [...current.blocked_users, targetUserId];
|
|
135
130
|
const { collectionPath, docId } = getGhostConfigPath(ownerUserId);
|
|
136
|
-
await setDocument(collectionPath, docId, {
|
|
131
|
+
await setDocument(collectionPath, docId, {
|
|
132
|
+
blocked_users: FieldValue.arrayUnion(targetUserId),
|
|
133
|
+
}, { merge: true });
|
|
137
134
|
|
|
138
135
|
logger.info('User blocked from ghost access', {
|
|
139
136
|
service: SERVICE,
|
|
@@ -144,19 +141,16 @@ export async function blockUser(
|
|
|
144
141
|
|
|
145
142
|
/**
|
|
146
143
|
* Unblock a user from ghost access.
|
|
144
|
+
* Uses FieldValue.arrayRemove for atomic remove without reading first (safe if not present).
|
|
147
145
|
*/
|
|
148
146
|
export async function unblockUser(
|
|
149
147
|
ownerUserId: string,
|
|
150
148
|
targetUserId: string
|
|
151
149
|
): Promise<void> {
|
|
152
|
-
const current = await getGhostConfig(ownerUserId);
|
|
153
|
-
if (!current.blocked_users.includes(targetUserId)) {
|
|
154
|
-
return; // not blocked
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
const blocked_users = current.blocked_users.filter(id => id !== targetUserId);
|
|
158
150
|
const { collectionPath, docId } = getGhostConfigPath(ownerUserId);
|
|
159
|
-
await setDocument(collectionPath, docId, {
|
|
151
|
+
await setDocument(collectionPath, docId, {
|
|
152
|
+
blocked_users: FieldValue.arrayRemove(targetUserId),
|
|
153
|
+
}, { merge: true });
|
|
160
154
|
|
|
161
155
|
logger.info('User unblocked from ghost access', {
|
|
162
156
|
service: SERVICE,
|
|
@@ -7,8 +7,7 @@ import type { Memory, SearchFilters, DeletedFilter } from '../types/memory.js';
|
|
|
7
7
|
import { getMemoryCollection } from '../weaviate/schema.js';
|
|
8
8
|
import { logger } from '../utils/logger.js';
|
|
9
9
|
import { handleToolError } from '../utils/error-handler.js';
|
|
10
|
-
import { buildCombinedSearchFilters, buildDeletedFilter, combineFiltersWithAnd } from '
|
|
11
|
-
import { buildTrustFilter } from '../services/trust-enforcement.js';
|
|
10
|
+
import { buildCombinedSearchFilters, buildDeletedFilter, combineFiltersWithAnd, buildTrustFilter } from '@prmichaelsen/remember-core';
|
|
12
11
|
import { createDebugLogger } from '../utils/debug.js';
|
|
13
12
|
import type { AuthContext } from '../types/auth.js';
|
|
14
13
|
|
|
@@ -7,8 +7,7 @@ import type { Memory, Relationship, SearchOptions, SearchResult, SearchFilters }
|
|
|
7
7
|
import { getMemoryCollection } from '../weaviate/schema.js';
|
|
8
8
|
import { logger } from '../utils/logger.js';
|
|
9
9
|
import { handleToolError } from '../utils/error-handler.js';
|
|
10
|
-
import { buildCombinedSearchFilters, buildMemoryOnlyFilters, buildDeletedFilter, combineFiltersWithAnd } from '
|
|
11
|
-
import { buildTrustFilter } from '../services/trust-enforcement.js';
|
|
10
|
+
import { buildCombinedSearchFilters, buildMemoryOnlyFilters, buildDeletedFilter, combineFiltersWithAnd, buildTrustFilter } from '@prmichaelsen/remember-core';
|
|
12
11
|
import { createDebugLogger } from '../utils/debug.js';
|
|
13
12
|
import type { AuthContext } from '../types/auth.js';
|
|
14
13
|
|
|
@@ -182,10 +181,11 @@ export async function handleSearchMemory(
|
|
|
182
181
|
// Combine deleted filter, trust filter, ghost exclusion, and search filters
|
|
183
182
|
const combinedFilters = combineFiltersWithAnd([deletedFilter, trustFilter, ghostExclusionFilter, searchFilters].filter(f => f !== null));
|
|
184
183
|
|
|
185
|
-
// Build search options
|
|
184
|
+
// Build search options (native offset handled by Weaviate)
|
|
186
185
|
const searchOptions: any = {
|
|
187
186
|
alpha: alpha,
|
|
188
|
-
limit: limit
|
|
187
|
+
limit: limit,
|
|
188
|
+
offset: offset,
|
|
189
189
|
};
|
|
190
190
|
|
|
191
191
|
// Add filters if present
|
|
@@ -204,14 +204,11 @@ export async function handleSearchMemory(
|
|
|
204
204
|
// Perform hybrid search with Weaviate v3 API
|
|
205
205
|
const results = await collection.query.hybrid(args.query, searchOptions);
|
|
206
206
|
|
|
207
|
-
// Apply offset
|
|
208
|
-
const paginatedResults = results.objects.slice(offset);
|
|
209
|
-
|
|
210
207
|
// Separate memories and relationships
|
|
211
208
|
const memories: Partial<Memory>[] = [];
|
|
212
209
|
const relationships: Partial<Relationship>[] = [];
|
|
213
210
|
|
|
214
|
-
for (const obj of
|
|
211
|
+
for (const obj of results.objects) {
|
|
215
212
|
const doc: any = {
|
|
216
213
|
id: obj.uuid,
|
|
217
214
|
...obj.properties,
|
|
@@ -1,83 +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
|
-
import type { Memory } from '../types/memory.js';
|
|
11
|
-
import type { TrustEnforcementMode } from '../types/ghost-config.js';
|
|
12
|
-
/** Trust level thresholds mapping continuous 0-1 values to discrete behavior tiers */
|
|
13
|
-
export declare const TRUST_THRESHOLDS: {
|
|
14
|
-
readonly FULL_ACCESS: 1;
|
|
15
|
-
readonly PARTIAL_ACCESS: 0.75;
|
|
16
|
-
readonly SUMMARY_ONLY: 0.5;
|
|
17
|
-
readonly METADATA_ONLY: 0.25;
|
|
18
|
-
readonly EXISTENCE_ONLY: 0;
|
|
19
|
-
};
|
|
20
|
-
/**
|
|
21
|
-
* Build a Weaviate filter that restricts memories by trust score.
|
|
22
|
-
* Only returns memories where trust_score <= accessorTrustLevel.
|
|
23
|
-
*
|
|
24
|
-
* Trust 1.0 memories require accessor trust >= 1.0 to even appear in results.
|
|
25
|
-
* When they do appear, formatMemoryForPrompt caps output to existence-only.
|
|
26
|
-
*
|
|
27
|
-
* @param collection - Weaviate collection instance
|
|
28
|
-
* @param accessorTrustLevel - The accessor's trust level (0-1)
|
|
29
|
-
* @returns Weaviate filter object
|
|
30
|
-
*/
|
|
31
|
-
export declare function buildTrustFilter(collection: any, accessorTrustLevel: number): any;
|
|
32
|
-
/**
|
|
33
|
-
* Formatted memory representation for prompt-level enforcement.
|
|
34
|
-
* Content is redacted/formatted based on trust level.
|
|
35
|
-
*/
|
|
36
|
-
export interface FormattedMemory {
|
|
37
|
-
memory_id: string;
|
|
38
|
-
trust_tier: string;
|
|
39
|
-
content: string;
|
|
40
|
-
}
|
|
41
|
-
/**
|
|
42
|
-
* Format a memory for inclusion in an LLM prompt, redacted by trust level.
|
|
43
|
-
*
|
|
44
|
-
* Trust tiers:
|
|
45
|
-
* - 1.0 Full Access: full content, all details
|
|
46
|
-
* - 0.75 Partial Access: content with sensitive fields redacted
|
|
47
|
-
* - 0.5 Summary Only: title + summary, no content
|
|
48
|
-
* - 0.25 Metadata Only: type, date, tags — no content or summary
|
|
49
|
-
* - 0.0 Existence Only: "A memory exists about this topic"
|
|
50
|
-
*
|
|
51
|
-
* Trust 1.0 memories are always existence-only for cross-users, regardless of
|
|
52
|
-
* accessor trust level. Use `isSelfAccess = true` to bypass for owner access.
|
|
53
|
-
*
|
|
54
|
-
* @param memory - The memory to format
|
|
55
|
-
* @param accessorTrustLevel - The accessor's trust level (0-1)
|
|
56
|
-
* @param isSelfAccess - True if the accessor is the memory owner (bypasses trust 1.0 cap)
|
|
57
|
-
* @returns Formatted memory for prompt inclusion
|
|
58
|
-
*/
|
|
59
|
-
export declare function formatMemoryForPrompt(memory: Memory, accessorTrustLevel: number, isSelfAccess?: boolean): FormattedMemory;
|
|
60
|
-
/**
|
|
61
|
-
* Get a human-readable label for a trust level.
|
|
62
|
-
*/
|
|
63
|
-
export declare function getTrustLevelLabel(trust: number): string;
|
|
64
|
-
/**
|
|
65
|
-
* Get LLM instruction text describing what to reveal at a given trust level.
|
|
66
|
-
*/
|
|
67
|
-
export declare function getTrustInstructions(trust: number): string;
|
|
68
|
-
/**
|
|
69
|
-
* Redact sensitive fields from a memory for partial access.
|
|
70
|
-
* Returns a copy with location, context, and references cleared.
|
|
71
|
-
*/
|
|
72
|
-
export declare function redactSensitiveFields(memory: Memory, _trust: number): Memory;
|
|
73
|
-
/**
|
|
74
|
-
* Check whether an accessor's trust level is sufficient for a memory.
|
|
75
|
-
* Access is granted when accessorTrust >= memoryTrust.
|
|
76
|
-
*/
|
|
77
|
-
export declare function isTrustSufficient(memoryTrust: number, accessorTrust: number): boolean;
|
|
78
|
-
/**
|
|
79
|
-
* Determine the enforcement mode to use.
|
|
80
|
-
* Convenience function that returns the mode from GhostConfig or falls back to 'query'.
|
|
81
|
-
*/
|
|
82
|
-
export declare function resolveEnforcementMode(mode?: TrustEnforcementMode): TrustEnforcementMode;
|
|
83
|
-
//# sourceMappingURL=trust-enforcement.d.ts.map
|
|
@@ -1,56 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Weaviate v3 Filter Builder Utilities
|
|
3
|
-
*
|
|
4
|
-
* Provides helper functions to build Weaviate v3 filters using the fluent API.
|
|
5
|
-
* Replaces old v2 filter format (path/operator/valueText) with v3 collection.filter.byProperty()
|
|
6
|
-
*/
|
|
7
|
-
import type { SearchFilters } from '../types/memory.js';
|
|
8
|
-
/**
|
|
9
|
-
* Deleted filter options
|
|
10
|
-
*/
|
|
11
|
-
export type DeletedFilter = 'exclude' | 'include' | 'only';
|
|
12
|
-
/**
|
|
13
|
-
* Build filters for searching both memories and relationships
|
|
14
|
-
* Uses OR logic: (doc_type=memory AND memory_filters) OR (doc_type=relationship AND relationship_filters)
|
|
15
|
-
*
|
|
16
|
-
* @param collection - Weaviate collection instance
|
|
17
|
-
* @param filters - Optional search filters
|
|
18
|
-
* @returns Combined filter or undefined if no filters
|
|
19
|
-
*/
|
|
20
|
-
export declare function buildCombinedSearchFilters(collection: any, filters?: SearchFilters): any;
|
|
21
|
-
/**
|
|
22
|
-
* Build filters for memory-only search (backward compatibility)
|
|
23
|
-
*
|
|
24
|
-
* @param collection - Weaviate collection instance
|
|
25
|
-
* @param filters - Optional search filters
|
|
26
|
-
* @returns Combined filter or undefined if no filters
|
|
27
|
-
*/
|
|
28
|
-
export declare function buildMemoryOnlyFilters(collection: any, filters?: SearchFilters): any;
|
|
29
|
-
/**
|
|
30
|
-
* Build filters specifically for relationship-only search
|
|
31
|
-
*
|
|
32
|
-
* @param collection - Weaviate collection instance
|
|
33
|
-
* @param filters - Optional search filters
|
|
34
|
-
* @returns Combined filter or undefined if no filters
|
|
35
|
-
*/
|
|
36
|
-
export declare function buildRelationshipOnlyFilters(collection: any, filters?: SearchFilters): any;
|
|
37
|
-
/**
|
|
38
|
-
* Combine multiple filters with AND logic
|
|
39
|
-
*
|
|
40
|
-
* @param filters - Array of filter objects
|
|
41
|
-
* @returns Combined filter or undefined
|
|
42
|
-
*/
|
|
43
|
-
export declare function combineFiltersWithAnd(filters: any[]): any;
|
|
44
|
-
/**
|
|
45
|
-
* Helper to check if a filter result is empty
|
|
46
|
-
*/
|
|
47
|
-
export declare function hasFilters(filter: any): boolean;
|
|
48
|
-
/**
|
|
49
|
-
* Build filter for deleted_at field based on deleted_filter parameter
|
|
50
|
-
*
|
|
51
|
-
* @param collection - Weaviate collection instance
|
|
52
|
-
* @param deletedFilter - Filter mode: 'exclude' (default), 'include', or 'only'
|
|
53
|
-
* @returns Filter for deleted_at field, or null if no filter needed
|
|
54
|
-
*/
|
|
55
|
-
export declare function buildDeletedFilter(collection: any, deletedFilter?: DeletedFilter): any | null;
|
|
56
|
-
//# sourceMappingURL=weaviate-filters.d.ts.map
|
|
@@ -1,8 +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
|
-
export {};
|
|
8
|
-
//# sourceMappingURL=weaviate-filters.spec.d.ts.map
|
|
@@ -1,309 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
buildTrustFilter,
|
|
3
|
-
formatMemoryForPrompt,
|
|
4
|
-
getTrustLevelLabel,
|
|
5
|
-
getTrustInstructions,
|
|
6
|
-
redactSensitiveFields,
|
|
7
|
-
isTrustSufficient,
|
|
8
|
-
resolveEnforcementMode,
|
|
9
|
-
TRUST_THRESHOLDS,
|
|
10
|
-
} from './trust-enforcement.js';
|
|
11
|
-
import type { Memory } from '../types/memory.js';
|
|
12
|
-
|
|
13
|
-
// ─── Test Fixtures ─────────────────────────────────────────────────────────
|
|
14
|
-
|
|
15
|
-
const mockMemory: Memory = {
|
|
16
|
-
id: 'mem-1',
|
|
17
|
-
user_id: 'user-owner',
|
|
18
|
-
doc_type: 'memory',
|
|
19
|
-
content: 'My private journal entry about my feelings.',
|
|
20
|
-
title: 'Journal Entry: Feb 2026',
|
|
21
|
-
summary: 'Reflections on the month of February.',
|
|
22
|
-
type: 'journal',
|
|
23
|
-
weight: 0.5,
|
|
24
|
-
trust: 0.75,
|
|
25
|
-
location: {
|
|
26
|
-
gps: { latitude: 37.7749, longitude: -122.4194, timestamp: '2026-01-01T00:00:00Z' },
|
|
27
|
-
address: { formatted: '123 Main St, San Francisco, CA' },
|
|
28
|
-
source: 'gps',
|
|
29
|
-
confidence: 0.9,
|
|
30
|
-
is_approximate: false,
|
|
31
|
-
},
|
|
32
|
-
context: {
|
|
33
|
-
timestamp: '2026-02-15T10:00:00Z',
|
|
34
|
-
source: { type: 'manual' },
|
|
35
|
-
participants: [{ user_id: 'user-owner', role: 'user' }],
|
|
36
|
-
environment: { device: 'laptop' },
|
|
37
|
-
notes: 'Written during morning coffee',
|
|
38
|
-
},
|
|
39
|
-
relationships: ['rel-1'],
|
|
40
|
-
access_count: 5,
|
|
41
|
-
last_accessed_at: '2026-02-20T08:00:00Z',
|
|
42
|
-
created_at: '2026-02-15T10:00:00Z',
|
|
43
|
-
updated_at: '2026-02-20T08:00:00Z',
|
|
44
|
-
version: 2,
|
|
45
|
-
tags: ['personal', 'reflection'],
|
|
46
|
-
references: ['https://private-blog.example.com/entry-1'],
|
|
47
|
-
base_weight: 0.5,
|
|
48
|
-
};
|
|
49
|
-
|
|
50
|
-
// Mock Weaviate collection
|
|
51
|
-
function createMockCollection() {
|
|
52
|
-
const filterResult = { lessThanOrEqual: jest.fn().mockReturnValue('trust_filter') };
|
|
53
|
-
return {
|
|
54
|
-
filter: {
|
|
55
|
-
byProperty: jest.fn().mockReturnValue(filterResult),
|
|
56
|
-
},
|
|
57
|
-
_filterResult: filterResult,
|
|
58
|
-
};
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
// ─── buildTrustFilter ──────────────────────────────────────────────────────
|
|
62
|
-
|
|
63
|
-
describe('buildTrustFilter', () => {
|
|
64
|
-
it('creates a filter for trust_score <= accessorTrustLevel', () => {
|
|
65
|
-
const collection = createMockCollection();
|
|
66
|
-
const result = buildTrustFilter(collection, 0.5);
|
|
67
|
-
expect(collection.filter.byProperty).toHaveBeenCalledWith('trust_score');
|
|
68
|
-
expect(collection._filterResult.lessThanOrEqual).toHaveBeenCalledWith(0.5);
|
|
69
|
-
expect(result).toBe('trust_filter');
|
|
70
|
-
});
|
|
71
|
-
|
|
72
|
-
it('works with trust level 0', () => {
|
|
73
|
-
const collection = createMockCollection();
|
|
74
|
-
buildTrustFilter(collection, 0);
|
|
75
|
-
expect(collection._filterResult.lessThanOrEqual).toHaveBeenCalledWith(0);
|
|
76
|
-
});
|
|
77
|
-
|
|
78
|
-
it('works with trust level 1 (includes trust 1.0 memories for acknowledgment)', () => {
|
|
79
|
-
const collection = createMockCollection();
|
|
80
|
-
buildTrustFilter(collection, 1.0);
|
|
81
|
-
expect(collection._filterResult.lessThanOrEqual).toHaveBeenCalledWith(1.0);
|
|
82
|
-
});
|
|
83
|
-
});
|
|
84
|
-
|
|
85
|
-
// ─── formatMemoryForPrompt ─────────────────────────────────────────────────
|
|
86
|
-
|
|
87
|
-
describe('formatMemoryForPrompt', () => {
|
|
88
|
-
it('returns full content at trust 1.0', () => {
|
|
89
|
-
const result = formatMemoryForPrompt(mockMemory, 1.0);
|
|
90
|
-
expect(result.trust_tier).toBe('Full Access');
|
|
91
|
-
expect(result.content).toContain(mockMemory.content);
|
|
92
|
-
expect(result.content).toContain(mockMemory.title!);
|
|
93
|
-
expect(result.content).toContain(mockMemory.summary!);
|
|
94
|
-
expect(result.content).toContain('personal, reflection');
|
|
95
|
-
expect(result.content).toContain(mockMemory.created_at);
|
|
96
|
-
expect(result.memory_id).toBe('mem-1');
|
|
97
|
-
});
|
|
98
|
-
|
|
99
|
-
it('returns redacted content at trust 0.75', () => {
|
|
100
|
-
const result = formatMemoryForPrompt(mockMemory, 0.75);
|
|
101
|
-
expect(result.trust_tier).toBe('Partial Access');
|
|
102
|
-
expect(result.content).toContain(mockMemory.content);
|
|
103
|
-
expect(result.content).toContain('personal, reflection');
|
|
104
|
-
// Should not contain full summary (only in full access)
|
|
105
|
-
expect(result.content).not.toContain(mockMemory.summary!);
|
|
106
|
-
});
|
|
107
|
-
|
|
108
|
-
it('returns summary only at trust 0.5', () => {
|
|
109
|
-
const result = formatMemoryForPrompt(mockMemory, 0.5);
|
|
110
|
-
expect(result.trust_tier).toBe('Summary Only');
|
|
111
|
-
expect(result.content).toContain(mockMemory.title!);
|
|
112
|
-
expect(result.content).toContain(mockMemory.summary!);
|
|
113
|
-
expect(result.content).not.toContain(mockMemory.content);
|
|
114
|
-
});
|
|
115
|
-
|
|
116
|
-
it('returns (No summary available) when summary missing at trust 0.5', () => {
|
|
117
|
-
const noSummary = { ...mockMemory, summary: undefined };
|
|
118
|
-
const result = formatMemoryForPrompt(noSummary, 0.5);
|
|
119
|
-
expect(result.content).toContain('(No summary available)');
|
|
120
|
-
});
|
|
121
|
-
|
|
122
|
-
it('returns metadata only at trust 0.25', () => {
|
|
123
|
-
const result = formatMemoryForPrompt(mockMemory, 0.25);
|
|
124
|
-
expect(result.trust_tier).toBe('Metadata Only');
|
|
125
|
-
expect(result.content).toContain('[journal]');
|
|
126
|
-
expect(result.content).toContain('personal, reflection');
|
|
127
|
-
expect(result.content).not.toContain(mockMemory.content);
|
|
128
|
-
expect(result.content).not.toContain(mockMemory.summary!);
|
|
129
|
-
});
|
|
130
|
-
|
|
131
|
-
it('returns existence only at trust 0', () => {
|
|
132
|
-
const result = formatMemoryForPrompt(mockMemory, 0);
|
|
133
|
-
expect(result.trust_tier).toBe('Existence Only');
|
|
134
|
-
expect(result.content).toBe('A memory exists about this topic.');
|
|
135
|
-
expect(result.content).not.toContain(mockMemory.content);
|
|
136
|
-
expect(result.content).not.toContain(mockMemory.title!);
|
|
137
|
-
});
|
|
138
|
-
|
|
139
|
-
it('handles memory without title gracefully', () => {
|
|
140
|
-
const noTitle = { ...mockMemory, title: undefined };
|
|
141
|
-
const result = formatMemoryForPrompt(noTitle, 1.0);
|
|
142
|
-
expect(result.content).toContain('Untitled');
|
|
143
|
-
});
|
|
144
|
-
|
|
145
|
-
it('handles memory with empty tags', () => {
|
|
146
|
-
const noTags = { ...mockMemory, tags: [] };
|
|
147
|
-
const result = formatMemoryForPrompt(noTags, 1.0);
|
|
148
|
-
expect(result.content).not.toContain('Tags:');
|
|
149
|
-
});
|
|
150
|
-
|
|
151
|
-
describe('trust 1.0 memories (existence-only for cross-users)', () => {
|
|
152
|
-
const trust1Memory = { ...mockMemory, trust: 1.0 };
|
|
153
|
-
|
|
154
|
-
it('returns existence-only for cross-user even at accessor trust 1.0', () => {
|
|
155
|
-
const result = formatMemoryForPrompt(trust1Memory, 1.0);
|
|
156
|
-
expect(result.trust_tier).toBe('Existence Only');
|
|
157
|
-
expect(result.content).toBe('A memory exists about this topic.');
|
|
158
|
-
});
|
|
159
|
-
|
|
160
|
-
it('returns existence-only for cross-user at any trust level', () => {
|
|
161
|
-
for (const trust of [0, 0.25, 0.5, 0.75, 1.0]) {
|
|
162
|
-
const result = formatMemoryForPrompt(trust1Memory, trust);
|
|
163
|
-
expect(result.trust_tier).toBe('Existence Only');
|
|
164
|
-
expect(result.content).not.toContain(trust1Memory.content);
|
|
165
|
-
}
|
|
166
|
-
});
|
|
167
|
-
|
|
168
|
-
it('returns full content for owner (isSelfAccess = true)', () => {
|
|
169
|
-
const result = formatMemoryForPrompt(trust1Memory, 1.0, true);
|
|
170
|
-
expect(result.trust_tier).toBe('Full Access');
|
|
171
|
-
expect(result.content).toContain(trust1Memory.content);
|
|
172
|
-
});
|
|
173
|
-
|
|
174
|
-
it('does not leak title, summary, or tags for cross-user', () => {
|
|
175
|
-
const result = formatMemoryForPrompt(trust1Memory, 1.0);
|
|
176
|
-
expect(result.content).not.toContain(trust1Memory.title!);
|
|
177
|
-
expect(result.content).not.toContain(trust1Memory.summary!);
|
|
178
|
-
expect(result.content).not.toContain('personal');
|
|
179
|
-
});
|
|
180
|
-
});
|
|
181
|
-
});
|
|
182
|
-
|
|
183
|
-
// ─── getTrustLevelLabel ────────────────────────────────────────────────────
|
|
184
|
-
|
|
185
|
-
describe('getTrustLevelLabel', () => {
|
|
186
|
-
it('returns Full Access at 1.0', () => {
|
|
187
|
-
expect(getTrustLevelLabel(1.0)).toBe('Full Access');
|
|
188
|
-
});
|
|
189
|
-
|
|
190
|
-
it('returns Partial Access at 0.75', () => {
|
|
191
|
-
expect(getTrustLevelLabel(0.75)).toBe('Partial Access');
|
|
192
|
-
});
|
|
193
|
-
|
|
194
|
-
it('returns Summary Only at 0.5', () => {
|
|
195
|
-
expect(getTrustLevelLabel(0.5)).toBe('Summary Only');
|
|
196
|
-
});
|
|
197
|
-
|
|
198
|
-
it('returns Metadata Only at 0.25', () => {
|
|
199
|
-
expect(getTrustLevelLabel(0.25)).toBe('Metadata Only');
|
|
200
|
-
});
|
|
201
|
-
|
|
202
|
-
it('returns Existence Only at 0', () => {
|
|
203
|
-
expect(getTrustLevelLabel(0)).toBe('Existence Only');
|
|
204
|
-
});
|
|
205
|
-
|
|
206
|
-
it('maps intermediate values to nearest lower threshold', () => {
|
|
207
|
-
expect(getTrustLevelLabel(0.9)).toBe('Partial Access');
|
|
208
|
-
expect(getTrustLevelLabel(0.6)).toBe('Summary Only');
|
|
209
|
-
expect(getTrustLevelLabel(0.3)).toBe('Metadata Only');
|
|
210
|
-
expect(getTrustLevelLabel(0.1)).toBe('Existence Only');
|
|
211
|
-
});
|
|
212
|
-
});
|
|
213
|
-
|
|
214
|
-
// ─── getTrustInstructions ──────────────────────────────────────────────────
|
|
215
|
-
|
|
216
|
-
describe('getTrustInstructions', () => {
|
|
217
|
-
it('returns appropriate instructions for each tier', () => {
|
|
218
|
-
expect(getTrustInstructions(1.0)).toContain('full access');
|
|
219
|
-
expect(getTrustInstructions(0.75)).toContain('partial access');
|
|
220
|
-
expect(getTrustInstructions(0.5)).toContain('summary');
|
|
221
|
-
expect(getTrustInstructions(0.25)).toContain('metadata');
|
|
222
|
-
expect(getTrustInstructions(0)).toContain('acknowledge');
|
|
223
|
-
});
|
|
224
|
-
});
|
|
225
|
-
|
|
226
|
-
// ─── redactSensitiveFields ─────────────────────────────────────────────────
|
|
227
|
-
|
|
228
|
-
describe('redactSensitiveFields', () => {
|
|
229
|
-
it('clears GPS and address from location', () => {
|
|
230
|
-
const redacted = redactSensitiveFields(mockMemory, 0.75);
|
|
231
|
-
expect(redacted.location.gps).toBeNull();
|
|
232
|
-
expect(redacted.location.address).toBeNull();
|
|
233
|
-
expect(redacted.location.source).toBe('unavailable');
|
|
234
|
-
});
|
|
235
|
-
|
|
236
|
-
it('clears context participants and environment', () => {
|
|
237
|
-
const redacted = redactSensitiveFields(mockMemory, 0.75);
|
|
238
|
-
expect(redacted.context.participants).toBeUndefined();
|
|
239
|
-
expect(redacted.context.environment).toBeUndefined();
|
|
240
|
-
expect(redacted.context.notes).toBeUndefined();
|
|
241
|
-
});
|
|
242
|
-
|
|
243
|
-
it('clears references', () => {
|
|
244
|
-
const redacted = redactSensitiveFields(mockMemory, 0.75);
|
|
245
|
-
expect(redacted.references).toBeUndefined();
|
|
246
|
-
});
|
|
247
|
-
|
|
248
|
-
it('preserves content and tags', () => {
|
|
249
|
-
const redacted = redactSensitiveFields(mockMemory, 0.75);
|
|
250
|
-
expect(redacted.content).toBe(mockMemory.content);
|
|
251
|
-
expect(redacted.tags).toEqual(mockMemory.tags);
|
|
252
|
-
expect(redacted.title).toBe(mockMemory.title);
|
|
253
|
-
});
|
|
254
|
-
|
|
255
|
-
it('preserves core context fields', () => {
|
|
256
|
-
const redacted = redactSensitiveFields(mockMemory, 0.75);
|
|
257
|
-
expect(redacted.context.timestamp).toBe(mockMemory.context.timestamp);
|
|
258
|
-
expect(redacted.context.source).toEqual(mockMemory.context.source);
|
|
259
|
-
});
|
|
260
|
-
|
|
261
|
-
it('does not mutate original memory', () => {
|
|
262
|
-
const originalLocation = mockMemory.location.gps;
|
|
263
|
-
redactSensitiveFields(mockMemory, 0.75);
|
|
264
|
-
expect(mockMemory.location.gps).toBe(originalLocation);
|
|
265
|
-
});
|
|
266
|
-
});
|
|
267
|
-
|
|
268
|
-
// ─── isTrustSufficient ─────────────────────────────────────────────────────
|
|
269
|
-
|
|
270
|
-
describe('isTrustSufficient', () => {
|
|
271
|
-
it('returns true when accessor trust >= memory trust', () => {
|
|
272
|
-
expect(isTrustSufficient(0.5, 0.75)).toBe(true);
|
|
273
|
-
expect(isTrustSufficient(0.5, 0.5)).toBe(true);
|
|
274
|
-
expect(isTrustSufficient(0, 0)).toBe(true);
|
|
275
|
-
expect(isTrustSufficient(1.0, 1.0)).toBe(true);
|
|
276
|
-
});
|
|
277
|
-
|
|
278
|
-
it('returns false when accessor trust < memory trust', () => {
|
|
279
|
-
expect(isTrustSufficient(0.75, 0.5)).toBe(false);
|
|
280
|
-
expect(isTrustSufficient(1.0, 0.99)).toBe(false);
|
|
281
|
-
expect(isTrustSufficient(0.25, 0)).toBe(false);
|
|
282
|
-
});
|
|
283
|
-
});
|
|
284
|
-
|
|
285
|
-
// ─── resolveEnforcementMode ────────────────────────────────────────────────
|
|
286
|
-
|
|
287
|
-
describe('resolveEnforcementMode', () => {
|
|
288
|
-
it('returns the provided mode', () => {
|
|
289
|
-
expect(resolveEnforcementMode('query')).toBe('query');
|
|
290
|
-
expect(resolveEnforcementMode('prompt')).toBe('prompt');
|
|
291
|
-
expect(resolveEnforcementMode('hybrid')).toBe('hybrid');
|
|
292
|
-
});
|
|
293
|
-
|
|
294
|
-
it('defaults to query when undefined', () => {
|
|
295
|
-
expect(resolveEnforcementMode(undefined)).toBe('query');
|
|
296
|
-
});
|
|
297
|
-
});
|
|
298
|
-
|
|
299
|
-
// ─── TRUST_THRESHOLDS ──────────────────────────────────────────────────────
|
|
300
|
-
|
|
301
|
-
describe('TRUST_THRESHOLDS', () => {
|
|
302
|
-
it('has correct values', () => {
|
|
303
|
-
expect(TRUST_THRESHOLDS.FULL_ACCESS).toBe(1.0);
|
|
304
|
-
expect(TRUST_THRESHOLDS.PARTIAL_ACCESS).toBe(0.75);
|
|
305
|
-
expect(TRUST_THRESHOLDS.SUMMARY_ONLY).toBe(0.5);
|
|
306
|
-
expect(TRUST_THRESHOLDS.METADATA_ONLY).toBe(0.25);
|
|
307
|
-
expect(TRUST_THRESHOLDS.EXISTENCE_ONLY).toBe(0.0);
|
|
308
|
-
});
|
|
309
|
-
});
|