@prmichaelsen/remember-mcp 2.8.0 → 3.12.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENT.md +296 -250
- package/CHANGELOG.md +468 -0
- package/README.md +163 -46
- package/agent/commands/acp.clarification-create.md +382 -0
- package/agent/commands/acp.command-create.md +0 -1
- package/agent/commands/acp.design-create.md +0 -1
- package/agent/commands/acp.init.md +0 -1
- package/agent/commands/acp.package-create.md +0 -1
- package/agent/commands/acp.package-info.md +0 -1
- package/agent/commands/acp.package-install.md +0 -1
- package/agent/commands/acp.package-list.md +0 -1
- package/agent/commands/acp.package-publish.md +0 -1
- package/agent/commands/acp.package-remove.md +0 -1
- package/agent/commands/acp.package-search.md +0 -1
- package/agent/commands/acp.package-update.md +0 -1
- package/agent/commands/acp.package-validate.md +0 -1
- package/agent/commands/acp.pattern-create.md +0 -1
- package/agent/commands/acp.plan.md +0 -1
- package/agent/commands/acp.proceed.md +0 -1
- package/agent/commands/acp.project-create.md +0 -1
- package/agent/commands/acp.project-info.md +309 -0
- package/agent/commands/acp.project-list.md +0 -1
- package/agent/commands/acp.project-remove.md +379 -0
- package/agent/commands/acp.project-set.md +0 -1
- package/agent/commands/acp.project-update.md +296 -0
- package/agent/commands/acp.report.md +0 -1
- package/agent/commands/acp.resume.md +0 -1
- package/agent/commands/acp.status.md +0 -1
- package/agent/commands/acp.sync.md +0 -1
- package/agent/commands/acp.task-create.md +17 -10
- package/agent/commands/acp.update.md +0 -1
- package/agent/commands/acp.validate.md +0 -1
- package/agent/commands/acp.version-check-for-updates.md +0 -1
- package/agent/commands/acp.version-check.md +0 -1
- package/agent/commands/acp.version-update.md +0 -1
- package/agent/commands/command.template.md +0 -5
- package/agent/commands/git.commit.md +13 -2
- package/agent/commands/git.init.md +0 -1
- package/agent/design/comment-memory-type.md +2 -2
- package/agent/design/local.collaborative-memory-sync.md +265 -0
- package/agent/design/local.content-flags.md +210 -0
- package/agent/design/local.ghost-persona-system.md +273 -0
- package/agent/design/local.group-acl-integration.md +338 -0
- package/agent/design/local.memory-acl-schema.md +352 -0
- package/agent/design/local.memory-collection-pattern-v2.md +348 -0
- package/agent/design/local.moderation-and-space-config.md +257 -0
- package/agent/design/local.v2-api-reference.md +621 -0
- package/agent/design/local.v2-migration-guide.md +191 -0
- package/agent/design/local.v2-usage-examples.md +265 -0
- package/agent/design/permissions-storage-architecture.md +11 -3
- package/agent/design/soft-delete-system.md +291 -0
- package/agent/design/trust-escalation-prevention.md +9 -2
- package/agent/design/trust-system-implementation.md +12 -3
- package/agent/milestones/milestone-13-soft-delete-system.md +306 -0
- package/agent/milestones/milestone-14-memory-collection-v2.md +182 -0
- package/agent/milestones/milestone-15-moderation-space-config.md +126 -0
- package/agent/package.template.yaml +0 -17
- package/agent/progress.yaml +762 -49
- package/agent/scripts/acp.common.sh +2 -0
- package/agent/scripts/acp.install.sh +15 -85
- package/agent/scripts/acp.package-install-optimized.sh +454 -0
- package/agent/scripts/acp.package-install.sh +248 -380
- package/agent/scripts/acp.package-validate.sh +0 -99
- package/agent/scripts/acp.project-info.sh +218 -0
- package/agent/scripts/acp.project-remove.sh +302 -0
- package/agent/scripts/acp.project-update.sh +296 -0
- package/agent/scripts/acp.yaml-parser.sh +128 -10
- package/agent/tasks/milestone-14-memory-collection-v2/task-165-core-infrastructure-setup.md +171 -0
- package/agent/tasks/milestone-14-memory-collection-v2/task-166-update-remember-publish.md +191 -0
- package/agent/tasks/milestone-14-memory-collection-v2/task-167-update-remember-retract.md +186 -0
- package/agent/tasks/milestone-14-memory-collection-v2/task-168-implement-remember-revise.md +184 -0
- package/agent/tasks/milestone-14-memory-collection-v2/task-169-update-remember-search-space.md +179 -0
- package/agent/tasks/milestone-14-memory-collection-v2/task-170-update-remember-create-update.md +139 -0
- package/agent/tasks/milestone-14-memory-collection-v2/task-172-performance-testing-optimization.md +161 -0
- package/agent/tasks/milestone-14-memory-collection-v2/task-173-documentation-examples.md +258 -0
- package/agent/tasks/milestone-15-moderation-space-config/task-174-add-moderation-schema-fields.md +57 -0
- package/agent/tasks/milestone-15-moderation-space-config/task-175-create-space-config-service.md +64 -0
- package/agent/tasks/milestone-15-moderation-space-config/task-176-wire-moderation-publish-flow.md +45 -0
- package/agent/tasks/milestone-15-moderation-space-config/task-177-add-moderation-search-filters.md +70 -0
- package/agent/tasks/milestone-15-moderation-space-config/task-178-create-remember-moderate-tool.md +69 -0
- package/agent/tasks/milestone-15-moderation-space-config/task-179-documentation-integration-tests.md +58 -0
- package/agent/tasks/milestone-16-ghost-system/task-187-ghost-config-firestore.md +41 -0
- package/agent/tasks/milestone-16-ghost-system/task-188-trust-filter-integration.md +44 -0
- package/agent/tasks/milestone-16-ghost-system/task-189-ghost-memory-filtering.md +43 -0
- package/agent/tasks/milestone-16-ghost-system/task-190-ghost-config-tools.md +45 -0
- package/agent/tasks/milestone-16-ghost-system/task-191-escalation-firestore.md +38 -0
- package/agent/tasks/milestone-16-ghost-system/task-192-documentation-verification.md +39 -0
- package/agent/tasks/milestone-7-trust-permissions/task-180-access-result-permission-types.md +69 -0
- package/agent/tasks/milestone-7-trust-permissions/task-181-firestore-permissions-access-logs.md +56 -0
- package/agent/tasks/milestone-7-trust-permissions/task-182-trust-enforcement-service.md +68 -0
- package/agent/tasks/milestone-7-trust-permissions/task-183-access-control-service.md +70 -0
- package/agent/tasks/milestone-7-trust-permissions/task-184-permission-tools.md +79 -0
- package/agent/tasks/milestone-7-trust-permissions/task-185-wire-trust-into-search-query.md +55 -0
- package/agent/tasks/milestone-7-trust-permissions/task-186-documentation-verification.md +56 -0
- package/agent/tasks/task-70-add-soft-delete-schema-fields.md +165 -0
- package/agent/tasks/task-71-implement-delete-confirmation-flow.md +257 -0
- package/agent/tasks/task-72-add-deleted-filter-to-search-tools.md +18 -0
- package/agent/tasks/task-73-update-relationship-handling.md +18 -0
- package/agent/tasks/task-74-add-unit-tests-soft-delete.md +18 -0
- package/agent/tasks/task-75-update-documentation-changelog.md +26 -0
- package/agent/tasks/task-76-fix-indexnullstate-schema-bug.md +197 -0
- package/dist/collections/composite-ids.d.ts +106 -0
- package/dist/collections/core-infrastructure.spec.d.ts +11 -0
- package/dist/collections/dot-notation.d.ts +106 -0
- package/dist/collections/tracking-arrays.d.ts +176 -0
- package/dist/constants/content-types.d.ts +1 -0
- package/dist/schema/v2-collections-comments.spec.d.ts +8 -0
- package/dist/schema/v2-collections.d.ts +210 -0
- package/dist/server-factory.d.ts +15 -0
- package/dist/server-factory.js +3261 -1316
- package/dist/server.js +2926 -1236
- package/dist/services/access-control.d.ts +103 -0
- package/dist/services/access-control.spec.d.ts +2 -0
- package/dist/services/credentials-provider.d.ts +24 -0
- package/dist/services/credentials-provider.spec.d.ts +2 -0
- package/dist/services/escalation.service.d.ts +22 -0
- package/dist/services/escalation.service.spec.d.ts +2 -0
- package/dist/services/ghost-config.service.d.ts +55 -0
- package/dist/services/ghost-config.service.spec.d.ts +2 -0
- package/dist/services/space-config.service.d.ts +23 -0
- package/dist/services/space-config.service.spec.d.ts +2 -0
- package/dist/services/trust-enforcement.d.ts +83 -0
- package/dist/services/trust-enforcement.spec.d.ts +2 -0
- package/dist/services/trust-validator.d.ts +43 -0
- package/dist/services/trust-validator.spec.d.ts +2 -0
- package/dist/tools/confirm-publish-moderation.spec.d.ts +8 -0
- package/dist/tools/confirm.d.ts +8 -1
- package/dist/tools/create-memory.d.ts +2 -1
- package/dist/tools/create-memory.spec.d.ts +10 -0
- package/dist/tools/create-relationship.d.ts +2 -1
- package/dist/tools/delete-memory.d.ts +7 -31
- package/dist/tools/delete-relationship.d.ts +2 -1
- package/dist/tools/deny.d.ts +2 -1
- package/dist/tools/find-similar.d.ts +10 -2
- package/dist/tools/get-preferences.d.ts +2 -1
- package/dist/tools/ghost-config.d.ts +27 -0
- package/dist/tools/ghost-config.spec.d.ts +2 -0
- package/dist/tools/moderate.d.ts +20 -0
- package/dist/tools/moderate.spec.d.ts +5 -0
- package/dist/tools/publish.d.ts +11 -3
- package/dist/tools/query-memory.d.ts +11 -2
- package/dist/tools/query-space.d.ts +4 -1
- package/dist/tools/retract.d.ts +29 -0
- package/dist/tools/revise.d.ts +45 -0
- package/dist/tools/revise.spec.d.ts +8 -0
- package/dist/tools/search-memory.d.ts +8 -1
- package/dist/tools/search-relationship.d.ts +10 -2
- package/dist/tools/search-space.d.ts +25 -5
- package/dist/tools/search-space.spec.d.ts +9 -0
- package/dist/tools/set-preference.d.ts +2 -1
- package/dist/tools/update-memory.d.ts +2 -1
- package/dist/tools/update-relationship.d.ts +2 -1
- package/dist/types/access-result.d.ts +48 -0
- package/dist/types/access-result.spec.d.ts +2 -0
- package/dist/types/auth.d.ts +46 -0
- package/dist/types/ghost-config.d.ts +36 -0
- package/dist/types/memory.d.ts +11 -1
- package/dist/types/preferences.d.ts +1 -1
- package/dist/types/space-memory.d.ts +3 -0
- package/dist/utils/auth-helpers.d.ts +14 -0
- package/dist/utils/auth-helpers.spec.d.ts +2 -0
- package/dist/utils/test-data-generator.d.ts +124 -0
- package/dist/utils/test-data-generator.spec.d.ts +12 -0
- package/dist/utils/weaviate-filters.d.ts +19 -0
- package/dist/v2-performance.e2e.d.ts +17 -0
- package/dist/v2-smoke.e2e.d.ts +14 -0
- package/dist/weaviate/client.d.ts +5 -8
- package/dist/weaviate/space-schema.d.ts +2 -2
- package/docs/performance/v2-benchmarks.md +80 -0
- package/jest.e2e.config.js +14 -3
- package/package.json +1 -1
- package/scripts/.collection-recreation-state.yaml +16 -0
- package/scripts/.gitkeep +5 -0
- package/scripts/README-collection-recreation.md +224 -0
- package/scripts/README.md +51 -0
- package/scripts/backup-collections.ts +543 -0
- package/scripts/delete-collection.ts +137 -0
- package/scripts/migrate-recreate-collections.ts +578 -0
- package/scripts/migrate-v1-to-v2.ts +1094 -0
- package/scripts/package-lock.json +1113 -0
- package/scripts/package.json +27 -0
- package/src/collections/composite-ids.ts +193 -0
- package/src/collections/core-infrastructure.spec.ts +353 -0
- package/src/collections/dot-notation.ts +212 -0
- package/src/collections/tracking-arrays.ts +298 -0
- package/src/constants/content-types.ts +20 -0
- package/src/schema/v2-collections-comments.spec.ts +141 -0
- package/src/schema/v2-collections.ts +433 -0
- package/src/server-factory.ts +89 -20
- package/src/server.ts +45 -17
- package/src/services/access-control.spec.ts +383 -0
- package/src/services/access-control.ts +291 -0
- package/src/services/credentials-provider.spec.ts +22 -0
- package/src/services/credentials-provider.ts +34 -0
- package/src/services/escalation.service.spec.ts +183 -0
- package/src/services/escalation.service.ts +150 -0
- package/src/services/ghost-config.service.spec.ts +339 -0
- package/src/services/ghost-config.service.ts +219 -0
- package/src/services/space-config.service.spec.ts +102 -0
- package/src/services/space-config.service.ts +79 -0
- package/src/services/trust-enforcement.spec.ts +309 -0
- package/src/services/trust-enforcement.ts +197 -0
- package/src/services/trust-validator.spec.ts +108 -0
- package/src/services/trust-validator.ts +105 -0
- package/src/tools/confirm-publish-moderation.spec.ts +240 -0
- package/src/tools/confirm.ts +914 -116
- package/src/tools/create-memory.spec.ts +126 -0
- package/src/tools/create-memory.ts +20 -27
- package/src/tools/create-relationship.ts +30 -8
- package/src/tools/delete-memory.ts +99 -64
- package/src/tools/delete-relationship.ts +15 -6
- package/src/tools/deny.ts +8 -1
- package/src/tools/find-similar.ts +44 -6
- package/src/tools/get-preferences.ts +10 -1
- package/src/tools/ghost-config.spec.ts +180 -0
- package/src/tools/ghost-config.ts +230 -0
- package/src/tools/moderate.spec.ts +277 -0
- package/src/tools/moderate.ts +219 -0
- package/src/tools/publish.ts +99 -41
- package/src/tools/query-memory.ts +44 -9
- package/src/tools/query-space.ts +39 -4
- package/src/tools/retract.ts +292 -0
- package/src/tools/revise.spec.ts +146 -0
- package/src/tools/revise.ts +283 -0
- package/src/tools/search-memory.ts +46 -10
- package/src/tools/search-relationship.ts +30 -7
- package/src/tools/search-space.spec.ts +341 -0
- package/src/tools/search-space.ts +323 -99
- package/src/tools/set-preference.ts +10 -1
- package/src/tools/update-memory.ts +24 -5
- package/src/tools/update-relationship.ts +10 -1
- package/src/types/access-result.spec.ts +193 -0
- package/src/types/access-result.ts +62 -0
- package/src/types/auth.ts +52 -0
- package/src/types/ghost-config.ts +46 -0
- package/src/types/memory.ts +20 -1
- package/src/types/preferences.ts +2 -2
- package/src/types/space-memory.ts +5 -0
- package/src/utils/auth-helpers.spec.ts +75 -0
- package/src/utils/auth-helpers.ts +25 -0
- package/src/utils/test-data-generator.spec.ts +317 -0
- package/src/utils/test-data-generator.ts +292 -0
- package/src/utils/weaviate-filters.ts +32 -5
- package/src/v2-performance.e2e.ts +173 -0
- package/src/v2-smoke.e2e.ts +401 -0
- package/src/weaviate/client.spec.ts +5 -5
- package/src/weaviate/client.ts +55 -35
- package/src/weaviate/schema.ts +11 -239
- package/src/weaviate/space-schema.spec.ts +28 -25
- package/src/weaviate/space-schema.ts +35 -11
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Space/Group Configuration Service
|
|
3
|
+
*
|
|
4
|
+
* Per-space and per-group behavioral configuration stored in Firestore.
|
|
5
|
+
* Controls moderation requirements, write modes, and other behavioral rules.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { getDocument, setDocument } from '../firestore/init.js';
|
|
9
|
+
import { logger } from '../utils/logger.js';
|
|
10
|
+
import type { WriteMode } from '../types/auth.js';
|
|
11
|
+
|
|
12
|
+
export interface SpaceConfig {
|
|
13
|
+
require_moderation: boolean;
|
|
14
|
+
default_write_mode: WriteMode;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export const DEFAULT_SPACE_CONFIG: SpaceConfig = {
|
|
18
|
+
require_moderation: false,
|
|
19
|
+
default_write_mode: 'owner_only',
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Get the Firestore path for a space or group config document.
|
|
24
|
+
*/
|
|
25
|
+
function getConfigPath(id: string, type: 'space' | 'group'): { collectionPath: string; docId: string } {
|
|
26
|
+
const prefix = type === 'space' ? 'spaces' : 'groups';
|
|
27
|
+
return {
|
|
28
|
+
collectionPath: `${prefix}/${id}/config`,
|
|
29
|
+
docId: 'settings',
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Get configuration for a space or group.
|
|
35
|
+
* Returns defaults merged with any stored config.
|
|
36
|
+
*/
|
|
37
|
+
export async function getSpaceConfig(
|
|
38
|
+
id: string,
|
|
39
|
+
type: 'space' | 'group'
|
|
40
|
+
): Promise<SpaceConfig> {
|
|
41
|
+
try {
|
|
42
|
+
const { collectionPath, docId } = getConfigPath(id, type);
|
|
43
|
+
const doc = await getDocument(collectionPath, docId);
|
|
44
|
+
|
|
45
|
+
if (!doc) {
|
|
46
|
+
return { ...DEFAULT_SPACE_CONFIG };
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
return { ...DEFAULT_SPACE_CONFIG, ...doc };
|
|
50
|
+
} catch (error) {
|
|
51
|
+
logger.error('Failed to get space config', {
|
|
52
|
+
service: 'SpaceConfigService',
|
|
53
|
+
id,
|
|
54
|
+
type,
|
|
55
|
+
error: error instanceof Error ? error.message : String(error),
|
|
56
|
+
});
|
|
57
|
+
return { ...DEFAULT_SPACE_CONFIG };
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Set configuration for a space or group.
|
|
63
|
+
* Merges partial config with existing values.
|
|
64
|
+
*/
|
|
65
|
+
export async function setSpaceConfig(
|
|
66
|
+
id: string,
|
|
67
|
+
type: 'space' | 'group',
|
|
68
|
+
config: Partial<SpaceConfig>
|
|
69
|
+
): Promise<void> {
|
|
70
|
+
const { collectionPath, docId } = getConfigPath(id, type);
|
|
71
|
+
await setDocument(collectionPath, docId, config, { merge: true });
|
|
72
|
+
|
|
73
|
+
logger.info('Space config updated', {
|
|
74
|
+
service: 'SpaceConfigService',
|
|
75
|
+
id,
|
|
76
|
+
type,
|
|
77
|
+
updatedKeys: Object.keys(config),
|
|
78
|
+
});
|
|
79
|
+
}
|
|
@@ -0,0 +1,309 @@
|
|
|
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
|
+
});
|
|
@@ -0,0 +1,197 @@
|
|
|
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
|
+
}
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import { validateTrustAssignment, suggestTrustLevel } from './trust-validator.js';
|
|
2
|
+
|
|
3
|
+
describe('validateTrustAssignment', () => {
|
|
4
|
+
it('accepts trust levels in valid range', () => {
|
|
5
|
+
expect(validateTrustAssignment(0).valid).toBe(true);
|
|
6
|
+
expect(validateTrustAssignment(0.5).valid).toBe(true);
|
|
7
|
+
expect(validateTrustAssignment(1.0).valid).toBe(true);
|
|
8
|
+
});
|
|
9
|
+
|
|
10
|
+
it('rejects trust levels below 0', () => {
|
|
11
|
+
const result = validateTrustAssignment(-0.1);
|
|
12
|
+
expect(result.valid).toBe(false);
|
|
13
|
+
expect(result.warning).toContain('-0.1');
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
it('rejects trust levels above 1', () => {
|
|
17
|
+
const result = validateTrustAssignment(1.5);
|
|
18
|
+
expect(result.valid).toBe(false);
|
|
19
|
+
expect(result.warning).toContain('1.5');
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
it('warns for trust < 0.25', () => {
|
|
23
|
+
const result = validateTrustAssignment(0.1);
|
|
24
|
+
expect(result.valid).toBe(true);
|
|
25
|
+
expect(result.warning).toContain('very restrictive');
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
it('warns for trust 0', () => {
|
|
29
|
+
const result = validateTrustAssignment(0);
|
|
30
|
+
expect(result.valid).toBe(true);
|
|
31
|
+
expect(result.warning).toContain('very restrictive');
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
it('does not warn for trust >= 0.25', () => {
|
|
35
|
+
expect(validateTrustAssignment(0.25).warning).toBeUndefined();
|
|
36
|
+
expect(validateTrustAssignment(0.5).warning).toBeUndefined();
|
|
37
|
+
expect(validateTrustAssignment(1.0).warning).toBeUndefined();
|
|
38
|
+
});
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
describe('suggestTrustLevel', () => {
|
|
42
|
+
describe('tag overrides', () => {
|
|
43
|
+
it('returns 0.1 for private tag', () => {
|
|
44
|
+
expect(suggestTrustLevel('note', ['private'])).toBe(0.1);
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
it('returns 0.1 for secret tag', () => {
|
|
48
|
+
expect(suggestTrustLevel('note', ['Secret'])).toBe(0.1);
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
it('returns 1.0 for public tag', () => {
|
|
52
|
+
expect(suggestTrustLevel('journal', ['public'])).toBe(1.0);
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
it('tag override takes priority over content type', () => {
|
|
56
|
+
// journal normally suggests 0.75, but 'private' overrides to 0.1
|
|
57
|
+
expect(suggestTrustLevel('journal', ['private'])).toBe(0.1);
|
|
58
|
+
});
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
describe('content type suggestions', () => {
|
|
62
|
+
it('suggests 0.75 for personal types', () => {
|
|
63
|
+
expect(suggestTrustLevel('journal')).toBe(0.75);
|
|
64
|
+
expect(suggestTrustLevel('memory')).toBe(0.75);
|
|
65
|
+
expect(suggestTrustLevel('event')).toBe(0.75);
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
it('suggests 0.5 for system types', () => {
|
|
69
|
+
expect(suggestTrustLevel('system')).toBe(0.5);
|
|
70
|
+
expect(suggestTrustLevel('audit')).toBe(0.5);
|
|
71
|
+
expect(suggestTrustLevel('action')).toBe(0.5);
|
|
72
|
+
expect(suggestTrustLevel('history')).toBe(0.5);
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
it('suggests 0.5 for business types', () => {
|
|
76
|
+
expect(suggestTrustLevel('invoice')).toBe(0.5);
|
|
77
|
+
expect(suggestTrustLevel('contract')).toBe(0.5);
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
it('suggests 0.5 for communication types', () => {
|
|
81
|
+
expect(suggestTrustLevel('email')).toBe(0.5);
|
|
82
|
+
expect(suggestTrustLevel('conversation')).toBe(0.5);
|
|
83
|
+
expect(suggestTrustLevel('meeting')).toBe(0.5);
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
it('suggests 0.75 for ghost type', () => {
|
|
87
|
+
expect(suggestTrustLevel('ghost')).toBe(0.75);
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
it('suggests 0.25 for general/creative types', () => {
|
|
91
|
+
expect(suggestTrustLevel('note')).toBe(0.25);
|
|
92
|
+
expect(suggestTrustLevel('code')).toBe(0.25);
|
|
93
|
+
expect(suggestTrustLevel('article')).toBe(0.25);
|
|
94
|
+
expect(suggestTrustLevel('recipe')).toBe(0.25);
|
|
95
|
+
expect(suggestTrustLevel('bookmark')).toBe(0.25);
|
|
96
|
+
});
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
describe('edge cases', () => {
|
|
100
|
+
it('handles empty tags array', () => {
|
|
101
|
+
expect(suggestTrustLevel('note', [])).toBe(0.25);
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
it('handles undefined tags', () => {
|
|
105
|
+
expect(suggestTrustLevel('note')).toBe(0.25);
|
|
106
|
+
});
|
|
107
|
+
});
|
|
108
|
+
});
|