153 lines
4.3 KiB
JavaScript
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,
|
|
};
|
|
}
|