@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.js CHANGED
@@ -3815,118 +3815,6 @@ async function handleCreateMemory(args, userId, authContext, context) {
3815
3815
 
3816
3816
  // src/tools/search-memory.ts
3817
3817
  init_logger();
3818
-
3819
- // src/utils/weaviate-filters.ts
3820
- import { Filters as Filters6 } from "weaviate-client";
3821
- function buildCombinedSearchFilters2(collection, filters) {
3822
- const memoryFilters = buildDocTypeFilters2(collection, "memory", filters);
3823
- const relationshipFilters = buildDocTypeFilters2(collection, "relationship", filters);
3824
- const validFilters = [memoryFilters, relationshipFilters].filter((f) => f !== void 0 && f !== null);
3825
- if (validFilters.length === 0) {
3826
- return void 0;
3827
- } else if (validFilters.length === 1) {
3828
- return validFilters[0];
3829
- } else {
3830
- return combineFiltersWithOr2(validFilters);
3831
- }
3832
- }
3833
- function buildDocTypeFilters2(collection, docType, filters) {
3834
- const filterList = [];
3835
- filterList.push(
3836
- collection.filter.byProperty("doc_type").equal(docType)
3837
- );
3838
- if (docType === "memory" && filters?.types && filters.types.length > 0) {
3839
- if (filters.types.length === 1) {
3840
- filterList.push(
3841
- collection.filter.byProperty("content_type").equal(filters.types[0])
3842
- );
3843
- } else {
3844
- filterList.push(
3845
- collection.filter.byProperty("content_type").containsAny(filters.types)
3846
- );
3847
- }
3848
- }
3849
- if (filters?.weight_min !== void 0) {
3850
- filterList.push(
3851
- collection.filter.byProperty("weight").greaterThanOrEqual(filters.weight_min)
3852
- );
3853
- }
3854
- if (filters?.weight_max !== void 0) {
3855
- filterList.push(
3856
- collection.filter.byProperty("weight").lessThanOrEqual(filters.weight_max)
3857
- );
3858
- }
3859
- if (filters?.trust_min !== void 0) {
3860
- filterList.push(
3861
- collection.filter.byProperty("trust_score").greaterThanOrEqual(filters.trust_min)
3862
- );
3863
- }
3864
- if (filters?.trust_max !== void 0) {
3865
- filterList.push(
3866
- collection.filter.byProperty("trust_score").lessThanOrEqual(filters.trust_max)
3867
- );
3868
- }
3869
- if (filters?.date_from) {
3870
- filterList.push(
3871
- collection.filter.byProperty("created_at").greaterThanOrEqual(new Date(filters.date_from))
3872
- );
3873
- }
3874
- if (filters?.date_to) {
3875
- filterList.push(
3876
- collection.filter.byProperty("created_at").lessThanOrEqual(new Date(filters.date_to))
3877
- );
3878
- }
3879
- if (filters?.tags && filters.tags.length > 0) {
3880
- if (filters.tags.length === 1) {
3881
- filterList.push(
3882
- collection.filter.byProperty("tags").containsAny([filters.tags[0]])
3883
- );
3884
- } else {
3885
- filterList.push(
3886
- collection.filter.byProperty("tags").containsAny(filters.tags)
3887
- );
3888
- }
3889
- }
3890
- return combineFiltersWithAnd2(filterList);
3891
- }
3892
- function buildMemoryOnlyFilters2(collection, filters) {
3893
- return buildDocTypeFilters2(collection, "memory", filters);
3894
- }
3895
- function combineFiltersWithAnd2(filters) {
3896
- const validFilters = filters.filter((f) => f !== void 0 && f !== null);
3897
- if (validFilters.length === 0) {
3898
- return void 0;
3899
- }
3900
- if (validFilters.length === 1) {
3901
- return validFilters[0];
3902
- }
3903
- return Filters6.and(...validFilters);
3904
- }
3905
- function combineFiltersWithOr2(filters) {
3906
- const validFilters = filters.filter((f) => f !== void 0 && f !== null);
3907
- if (validFilters.length === 0) {
3908
- return void 0;
3909
- }
3910
- if (validFilters.length === 1) {
3911
- return validFilters[0];
3912
- }
3913
- return Filters6.or(...validFilters);
3914
- }
3915
- function buildDeletedFilter2(collection, deletedFilter = "exclude") {
3916
- if (deletedFilter === "exclude") {
3917
- return collection.filter.byProperty("deleted_at").isNull(true);
3918
- } else if (deletedFilter === "only") {
3919
- return collection.filter.byProperty("deleted_at").isNull(false);
3920
- }
3921
- return null;
3922
- }
3923
-
3924
- // src/services/trust-enforcement.ts
3925
- function buildTrustFilter2(collection, accessorTrustLevel) {
3926
- return collection.filter.byProperty("trust_score").lessThanOrEqual(accessorTrustLevel);
3927
- }
3928
-
3929
- // src/tools/search-memory.ts
3930
3818
  var searchMemoryTool = {
3931
3819
  name: "remember_search_memory",
3932
3820
  description: `Search memories AND relationships using hybrid semantic and keyword search.
@@ -4057,12 +3945,12 @@ async function handleSearchMemory(args, userId, authContext) {
4057
3945
  const alpha = args.alpha ?? 0.7;
4058
3946
  const limit = args.limit ?? 10;
4059
3947
  const offset = args.offset ?? 0;
4060
- const deletedFilter = buildDeletedFilter2(collection, args.deleted_filter || "exclude");
4061
- const trustFilter = ghostMode ? buildTrustFilter2(collection, ghostMode.accessor_trust_level) : null;
4062
- const searchFilters = includeRelationships ? buildCombinedSearchFilters2(collection, args.filters) : buildMemoryOnlyFilters2(collection, args.filters);
3948
+ const deletedFilter = buildDeletedFilter(collection, args.deleted_filter || "exclude");
3949
+ const trustFilter = ghostMode ? buildTrustFilter(collection, ghostMode.accessor_trust_level) : null;
3950
+ const searchFilters = includeRelationships ? buildCombinedSearchFilters(collection, args.filters) : buildMemoryOnlyFilters(collection, args.filters);
4063
3951
  const hasExplicitTypeFilter = args.filters?.types && args.filters.types.length > 0;
4064
3952
  const ghostExclusionFilter = !hasExplicitTypeFilter ? collection.filter.byProperty("content_type").notEqual("ghost") : null;
4065
- const combinedFilters = combineFiltersWithAnd2([deletedFilter, trustFilter, ghostExclusionFilter, searchFilters].filter((f) => f !== null));
3953
+ const combinedFilters = combineFiltersWithAnd([deletedFilter, trustFilter, ghostExclusionFilter, searchFilters].filter((f) => f !== null));
4066
3954
  const searchOptions = {
4067
3955
  alpha,
4068
3956
  limit: limit + offset
@@ -4524,12 +4412,12 @@ async function handleQueryMemory(args, userId, authContext) {
4524
4412
  const minRelevance = args.min_relevance ?? 0.6;
4525
4413
  const includeContext = args.include_context ?? true;
4526
4414
  const format = args.format ?? "detailed";
4527
- const deletedFilter = buildDeletedFilter2(collection, args.deleted_filter || "exclude");
4528
- const trustFilter = ghostMode ? buildTrustFilter2(collection, ghostMode.accessor_trust_level) : null;
4529
- const searchFilters = buildCombinedSearchFilters2(collection, args.filters);
4415
+ const deletedFilter = buildDeletedFilter(collection, args.deleted_filter || "exclude");
4416
+ const trustFilter = ghostMode ? buildTrustFilter(collection, ghostMode.accessor_trust_level) : null;
4417
+ const searchFilters = buildCombinedSearchFilters(collection, args.filters);
4530
4418
  const hasExplicitTypeFilter = args.filters?.types && args.filters.types.length > 0;
4531
4419
  const ghostExclusionFilter = !hasExplicitTypeFilter ? collection.filter.byProperty("content_type").notEqual("ghost") : null;
4532
- const combinedFilters = combineFiltersWithAnd2([deletedFilter, trustFilter, ghostExclusionFilter, searchFilters].filter((f) => f !== null));
4420
+ const combinedFilters = combineFiltersWithAnd([deletedFilter, trustFilter, ghostExclusionFilter, searchFilters].filter((f) => f !== null));
4533
4421
  const searchOptions = {
4534
4422
  limit,
4535
4423
  distance: 1 - minRelevance,
@@ -5641,7 +5529,7 @@ async function handleDeny(args, userId, authContext) {
5641
5529
  }
5642
5530
 
5643
5531
  // src/tools/search-space.ts
5644
- import { Filters as Filters7 } from "weaviate-client";
5532
+ import { Filters as Filters6 } from "weaviate-client";
5645
5533
  var searchSpaceTool = {
5646
5534
  name: "remember_search_space",
5647
5535
  description: `Search shared spaces and/or groups to discover memories from other users.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@prmichaelsen/remember-mcp",
3
- "version": "3.14.10",
3
+ "version": "3.14.12",
4
4
  "description": "Multi-tenant memory system MCP server with vector search and relationships",
5
5
  "main": "dist/server.js",
6
6
  "type": "module",
@@ -50,8 +50,7 @@
50
50
  "@modelcontextprotocol/sdk": "^1.0.4",
51
51
  "@prmichaelsen/firebase-admin-sdk-v8": "^2.2.0",
52
52
  "@prmichaelsen/mcp-auth": "^7.0.4",
53
- "@prmichaelsen/remember-core": "^0.22.3",
54
- "@prmichaelsen/remember-mcp": "^2.7.3",
53
+ "@prmichaelsen/remember-core": "^0.22.5",
55
54
  "dotenv": "^16.4.5",
56
55
  "uuid": "^13.0.0",
57
56
  "weaviate-client": "^3.2.0"
@@ -16,7 +16,7 @@ import type { Memory } from '../types/memory.js';
16
16
  import type { AccessResult } from '../types/access-result.js';
17
17
  import type { GhostConfig } from '../types/ghost-config.js';
18
18
  import { DEFAULT_GHOST_CONFIG } from '../types/ghost-config.js';
19
- import { isTrustSufficient } from './trust-enforcement.js';
19
+ import { isTrustSufficient } from '@prmichaelsen/remember-core';
20
20
 
21
21
  // ─── Types ─────────────────────────────────────────────────────────────────
22
22
 
@@ -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 '../utils/weaviate-filters.js';
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 '../utils/weaviate-filters.js';
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
 
@@ -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,2 +0,0 @@
1
- export {};
2
- //# sourceMappingURL=trust-enforcement.spec.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
- });