Files
memind/memory-v2-health.mjs
T
2026-07-03 09:46:03 +08:00

153 lines
4.3 KiB
JavaScript

const REQUIRED_STATUS_KEYS = [
'enabled',
'backend',
'selectedBackend',
'profileEnabled',
'eventLogEnabled',
'vectorEnabled',
'failOpen',
'backends',
];
function pass(name, details = {}) {
return { name, ok: true, ...details };
}
function fail(name, message, details = {}) {
return { name, ok: false, message, ...details };
}
function findBackend(status, name) {
return Array.isArray(status?.backends)
? status.backends.find((backend) => backend?.name === name)
: null;
}
function missingStatusKeys(status) {
return REQUIRED_STATUS_KEYS.filter((key) => !(key in Object(status)));
}
export function evaluateMemoryV2Health({
status,
writeResult = null,
compactResult = null,
expectedBackend = null,
requireEnabled = false,
requireFailOpen = true,
} = {}) {
const checks = [];
const missing = missingStatusKeys(status);
checks.push(
missing.length === 0
? pass('status_contract')
: fail('status_contract', `Missing status keys: ${missing.join(', ')}`, { missing }),
);
if (requireEnabled) {
checks.push(
status?.enabled === true
? pass('memory_enabled')
: fail('memory_enabled', 'MEMORY_ENABLED is not enabled', { enabled: status?.enabled }),
);
} else {
checks.push(pass('memory_enabled_optional', { enabled: Boolean(status?.enabled) }));
}
if (requireFailOpen) {
checks.push(
status?.failOpen === true
? pass('fail_open')
: fail('fail_open', 'MEMORY_FAIL_OPEN must remain enabled for release safety', {
failOpen: status?.failOpen,
}),
);
}
if (expectedBackend) {
checks.push(
status?.backend === expectedBackend
? pass('expected_backend', { backend: status?.backend })
: fail('expected_backend', `Expected MEMORY_BACKEND=${expectedBackend}`, {
backend: status?.backend,
}),
);
}
const legacy = findBackend(status, 'legacy-conversation-memory');
checks.push(
legacy?.available === true
? pass('legacy_available')
: fail('legacy_available', 'Legacy conversation-memory backend must be available', {
legacy,
}),
);
checks.push(
legacy?.supports?.write === true && legacy?.supports?.compact === true
? pass('legacy_write_compact_supported')
: fail('legacy_write_compact_supported', 'Legacy backend must support write and compact', {
supports: legacy?.supports ?? null,
}),
);
const selectedBackend = status?.selectedBackend ?? null;
checks.push(
selectedBackend
? pass('selected_backend_present', { selectedBackend })
: fail('selected_backend_present', 'No selected Memory V2 backend for resolve'),
);
const selected = findBackend(status, selectedBackend);
checks.push(
!selected || selected.available === true
? pass('selected_backend_available', { selectedBackend })
: fail('selected_backend_available', 'Selected backend is not available', { selected }),
);
const pgvector = findBackend(status, 'pgvector');
if (status?.backend === 'pgvector') {
checks.push(
pgvector?.available === true
? pass('pgvector_canary_available')
: pass('pgvector_canary_fallback', {
reason: pgvector?.reason ?? 'unavailable',
selectedBackend,
}),
);
}
if (writeResult) {
checks.push(
writeResult.source === 'legacy-conversation-memory'
? pass('write_uses_legacy', { source: writeResult.source })
: fail('write_uses_legacy', 'Memory V2 write must use legacy backend during semantic canary', {
source: writeResult.source ?? null,
}),
);
}
if (compactResult) {
checks.push(
compactResult.source === 'legacy-conversation-memory'
? pass('compact_uses_legacy', { source: compactResult.source })
: fail('compact_uses_legacy', 'Memory V2 compact must use legacy backend during semantic canary', {
source: compactResult.source ?? null,
}),
);
}
return {
ok: checks.every((check) => check.ok),
checkedAt: new Date().toISOString(),
summary: {
enabled: Boolean(status?.enabled),
backend: status?.backend ?? null,
selectedBackend,
failOpen: Boolean(status?.failOpen),
backendCount: Array.isArray(status?.backends) ? status.backends.length : 0,
},
checks,
};
}