import assert from 'node:assert/strict'; import test from 'node:test'; import { createMemoryV2AdminConfigService, memoryV2AdminConfigInternals, } from './memory-v2-admin-config.mjs'; function createPool(seedRow = null) { const state = { row: seedRow }; return { state, async query(sql, params) { if (sql.includes('CREATE TABLE')) return [[], []]; if (sql.includes('SELECT config_json')) { return [state.row ? [state.row] : [], []]; } if (sql.includes('INSERT INTO h5_memory_v2_admin_config')) { state.row = { config_json: params[1], secrets_ciphertext: params[2], secrets_iv: params[3], secrets_tag: params[4], updated_by: params[5], updated_at: params[6], }; return [[], []]; } throw new Error(`Unexpected query: ${sql}`); }, }; } test('memory v2 admin config service loads defaults from env and masks secrets', async () => { const service = createMemoryV2AdminConfigService(createPool(), { env: { MEMORY_ENABLED: '1', MEMORY_BACKEND: 'pgvector', MEMIND_CHAT_LLM_ROUTER_ENABLED: '1', MEMIND_CHAT_ROUTER_MODEL_PROVIDER_KEY_ID: 'key-router', MEMIND_CHAT_ROUTER_MODEL: 'deepseek-chat', MEMORY_PGVECTOR_DATABASE_URL: 'postgresql://user:pass@127.0.0.1:5432/memory', MEMORY_QDRANT_API_KEY: 'secret-qdrant', }, }); const result = await service.getAdminConfig(); assert.equal(result.config.global.enabled, true); assert.equal(result.config.global.backend, 'pgvector'); assert.equal(result.config.chatIntentRouter.enabled, true); assert.equal(result.config.chatIntentRouter.modelProviderKeyId, 'key-router'); assert.equal(result.config.chatIntentRouter.model, 'deepseek-chat'); assert.equal(result.config.pgvector.databaseUrlConfigured, true); assert.match(result.config.pgvector.databaseUrlMasked, /^post\*+/); assert.equal(result.config.qdrant.apiKeyConfigured, true); }); test('memory v2 admin config service persists non-secret and secret patches', async () => { const pool = createPool(); const service = createMemoryV2AdminConfigService(pool, { env: { MEMORY_ENABLED: '0', MEMORY_BACKEND: 'legacy', }, }); const updated = await service.updateAdminConfig({ global: { enabled: true, backend: 'qdrant' }, chatIntentRouter: { enabled: true, modelProviderKeyId: 'key-router', model: 'deepseek-chat', minConfidence: '0.7', memoryResolveEnabled: true, memoryResolveLimit: '8', timeoutMs: '1500', fallbackRoute: 'agent_orchestration', }, qdrant: { enabled: true, url: 'http://127.0.0.1:6333', apiKey: 'secret-qdrant', }, candidateMemory: { enabled: true, mode: 'shadow', minImportance: '0.75', minConfidence: '0.85', maxPending: '500', persistenceEnabled: true, }, runtimeControl: { agentResolveEnabled: true, agentInjectionMode: 'shadow', agentCanaryUserIds: 'user-1,user-2', agentResolveLimit: '3', agentResolveTimeoutMs: '1200', promotionEnabled: false, compactionV2Enabled: false, reflectionEnabled: false, }, policy: { enabled: true, saveExplicit: true, rejectSensitive: true, requireEvidence: true, retentionDays: '365', }, retriever: { enabled: true, episodicEnabled: true, episodicMode: 'canary', episodicCanaryUserIds: 'user-1,user-2', semanticEnabled: true, preferenceEnabled: true, goalEnabled: true, limit: '12', tokenBudget: '1800', timeoutMs: '1200', }, lifecycle: { enabled: true, dedupeEnabled: true, conflictReview: true }, persona: { enabled: true, provider: 'ai-mind', shadowMode: true }, graph: { enabled: true, provider: 'postgres', maxDepth: '2' }, userMemory: { enabled: true, reviewEnabled: true, forgetEnabled: true, deletePropagation: true }, pluginHealth: { enabled: true, intervalSeconds: '60', timeoutMs: '1500', failureThreshold: '3', autoFallback: true }, }, { updatedBy: 'admin-1' }); assert.equal(updated.config.global.enabled, true); assert.equal(updated.config.global.backend, 'qdrant'); assert.equal(updated.config.chatIntentRouter.enabled, true); assert.equal(updated.config.chatIntentRouter.modelProviderKeyId, 'key-router'); assert.equal(updated.config.chatIntentRouter.minConfidence, '0.7'); assert.equal(updated.config.qdrant.enabled, true); assert.equal(updated.config.qdrant.url, 'http://127.0.0.1:6333'); assert.equal(updated.config.qdrant.apiKeyConfigured, true); assert.equal(updated.config.candidateMemory.mode, 'shadow'); assert.equal(updated.config.candidateMemory.persistenceEnabled, true); assert.equal(updated.config.runtimeControl.agentResolveEnabled, true); assert.equal(updated.config.runtimeControl.agentInjectionMode, 'shadow'); assert.equal(updated.config.runtimeControl.agentCanaryUserIds, 'user-1,user-2'); assert.equal(updated.config.runtimeControl.agentResolveLimit, '3'); assert.equal(updated.config.policy.requireEvidence, true); assert.equal(updated.config.retriever.tokenBudget, '1800'); assert.equal(updated.config.retriever.episodicMode, 'canary'); assert.equal(updated.config.retriever.episodicCanaryUserIds, 'user-1,user-2'); assert.equal(updated.config.lifecycle.dedupeEnabled, true); assert.equal(updated.config.persona.provider, 'ai-mind'); assert.equal(updated.config.graph.provider, 'postgres'); assert.equal(updated.config.userMemory.deletePropagation, true); assert.equal(updated.config.pluginHealth.autoFallback, true); assert.equal(updated.updatedBy, 'admin-1'); const runtimeState = await service.getRuntimeState(); assert.equal(runtimeState.overrides.MEMORY_ENABLED, '1'); assert.equal(runtimeState.overrides.MEMORY_BACKEND, 'qdrant'); assert.equal(runtimeState.overrides.MEMIND_CHAT_LLM_ROUTER_ENABLED, '1'); assert.equal(runtimeState.overrides.MEMIND_CHAT_ROUTER_MODEL_PROVIDER_KEY_ID, 'key-router'); assert.equal(runtimeState.overrides.MEMIND_CHAT_ROUTER_MODEL, 'deepseek-chat'); assert.equal(runtimeState.overrides.MEMIND_CHAT_ROUTER_MIN_CONFIDENCE, '0.7'); assert.equal(runtimeState.overrides.MEMIND_CHAT_ROUTER_MEMORY_ENABLED, '1'); assert.equal(runtimeState.overrides.MEMIND_CHAT_ROUTER_MEMORY_LIMIT, '8'); assert.equal(runtimeState.overrides.MEMIND_CHAT_ROUTER_TIMEOUT_MS, '1500'); assert.equal(runtimeState.overrides.MEMIND_CHAT_ROUTER_FALLBACK_ROUTE, 'agent_orchestration'); assert.equal(runtimeState.overrides.MEMORY_QDRANT_ENABLED, '1'); assert.equal(runtimeState.overrides.MEMORY_QDRANT_URL, 'http://127.0.0.1:6333'); assert.equal(runtimeState.overrides.MEMORY_QDRANT_API_KEY, 'secret-qdrant'); assert.equal(runtimeState.overrides.MEMORY_CANDIDATE_ENABLED, '1'); assert.equal(runtimeState.overrides.MEMORY_CANDIDATE_MODE, 'shadow'); assert.equal(runtimeState.overrides.MEMORY_CANDIDATE_PERSISTENCE_ENABLED, '1'); assert.equal(runtimeState.overrides.MEMORY_AGENT_RESOLVE_ENABLED, '1'); assert.equal(runtimeState.overrides.MEMORY_AGENT_INJECTION_MODE, 'shadow'); assert.equal(runtimeState.overrides.MEMORY_AGENT_CANARY_USER_IDS, 'user-1,user-2'); assert.equal(runtimeState.overrides.MEMORY_AGENT_RESOLVE_LIMIT, '3'); assert.equal(runtimeState.overrides.MEMORY_AGENT_RESOLVE_TIMEOUT_MS, '1200'); assert.equal(runtimeState.overrides.MEMORY_PROMOTION_ENABLED, '0'); assert.equal(runtimeState.overrides.MEMORY_POLICY_REQUIRE_EVIDENCE, '1'); assert.equal(runtimeState.overrides.MEMORY_RETRIEVER_TOKEN_BUDGET, '1800'); assert.equal(runtimeState.overrides.MEMORY_RETRIEVER_EPISODIC_MODE, 'canary'); assert.equal(runtimeState.overrides.MEMORY_RETRIEVER_EPISODIC_CANARY_USER_IDS, 'user-1,user-2'); assert.equal(runtimeState.overrides.MEMORY_LIFECYCLE_DEDUPE_ENABLED, '1'); assert.equal(runtimeState.overrides.MEMORY_PERSONA_PROVIDER, 'ai-mind'); assert.equal(runtimeState.overrides.MEMORY_GRAPH_PROVIDER, 'postgres'); assert.equal(runtimeState.overrides.MEMORY_USER_DELETE_PROPAGATION, '1'); assert.equal(runtimeState.overrides.MEMORY_PLUGIN_HEALTH_AUTO_FALLBACK, '1'); }); test('memory v2 admin config internals flatten booleans and numbers consistently', () => { const env = memoryV2AdminConfigInternals.flattenConfig( { ...memoryV2AdminConfigInternals.defaultConfigShape(), global: { enabled: true, backend: 'pgvector', vectorEnabled: true }, chatIntentRouter: { enabled: true, minConfidence: '0.65', memoryResolveEnabled: true, memoryResolveLimit: '8', timeoutMs: '1500', }, pgvector: { enabled: true, poolMax: '5', limit: '8' }, }, { ...memoryV2AdminConfigInternals.defaultConfigShape(), pgvector: { databaseUrl: 'postgresql://local/memory' }, }, ); assert.equal(env.MEMORY_ENABLED, '1'); assert.equal(env.MEMORY_BACKEND, 'pgvector'); assert.equal(env.MEMIND_CHAT_LLM_ROUTER_ENABLED, '1'); assert.equal(env.MEMIND_CHAT_ROUTER_MIN_CONFIDENCE, '0.65'); assert.equal(env.MEMIND_CHAT_ROUTER_MEMORY_ENABLED, '1'); assert.equal(env.MEMIND_CHAT_ROUTER_MEMORY_LIMIT, '8'); assert.equal(env.MEMIND_CHAT_ROUTER_TIMEOUT_MS, '1500'); assert.equal(env.MEMORY_VECTOR_ENABLED, '1'); assert.equal(env.MEMORY_PGVECTOR_ENABLED, '1'); assert.equal(env.MEMORY_PGVECTOR_POOL_MAX, '5'); assert.equal(env.MEMORY_PGVECTOR_LIMIT, '8'); });