212 lines
6.4 KiB
TypeScript
212 lines
6.4 KiB
TypeScript
import fs from 'fs';
|
|
import path from 'path';
|
|
|
|
import pg from 'pg';
|
|
|
|
import {
|
|
MEMORY_DB_URL,
|
|
PROJECT_ROOT,
|
|
SKILLS_DB_URL,
|
|
} from './config.js';
|
|
import {
|
|
BUILTIN_KNOWLEDGE_ARTIFACT_SQL,
|
|
BUILTIN_KNOWLEDGE_METADATA_JSON,
|
|
readBuiltinKnowledgeMetadata,
|
|
} from './builtin-knowledge-artifact.js';
|
|
|
|
export type DbAvailability = 'available' | 'missing';
|
|
export type ArtifactPresence = 'present' | 'missing';
|
|
export type SkillsArtifactStatus =
|
|
| 'ready'
|
|
| 'missing'
|
|
| 'incomplete'
|
|
| 'unavailable'
|
|
| 'unknown';
|
|
|
|
export interface SplitBrainStatus {
|
|
skillsDb: DbAvailability;
|
|
memoryDb: DbAvailability;
|
|
skillsArtifactSql: ArtifactPresence;
|
|
skillsArtifactMetadata: ArtifactPresence;
|
|
skillsArtifact: SkillsArtifactStatus;
|
|
skillsArtifactVersion: string;
|
|
skillsArtifactDbVersion: string;
|
|
skillsRuntimeLookup: ArtifactPresence;
|
|
skillsArtifactRows: number;
|
|
skillsDocumentRows: number;
|
|
skillsChunkRows: number;
|
|
memoryRows: number;
|
|
memoryChunkRows: number;
|
|
memoryEmbeddingRows: number;
|
|
}
|
|
|
|
const { Client } = pg;
|
|
|
|
async function verifyDb(connectionString: string): Promise<DbAvailability> {
|
|
try {
|
|
const client = new Client({
|
|
connectionString,
|
|
connectionTimeoutMillis: 5000,
|
|
});
|
|
await client.connect();
|
|
try {
|
|
await client.query('SELECT 1');
|
|
return 'available';
|
|
} finally {
|
|
await client.end();
|
|
}
|
|
} catch {
|
|
return 'missing';
|
|
}
|
|
}
|
|
|
|
export async function collectSplitBrainStatus(): Promise<SplitBrainStatus> {
|
|
const metadata = readBuiltinKnowledgeMetadata();
|
|
const status: SplitBrainStatus = {
|
|
skillsDb: 'missing',
|
|
memoryDb: 'missing',
|
|
skillsArtifactSql: fs.existsSync(BUILTIN_KNOWLEDGE_ARTIFACT_SQL)
|
|
? 'present'
|
|
: 'missing',
|
|
skillsArtifactMetadata: fs.existsSync(BUILTIN_KNOWLEDGE_METADATA_JSON)
|
|
? 'present'
|
|
: 'missing',
|
|
skillsArtifact: 'unknown',
|
|
skillsArtifactVersion: String(metadata?.artifact_version ?? 'unknown'),
|
|
skillsArtifactDbVersion: 'unknown',
|
|
skillsRuntimeLookup: fs.existsSync(path.join(PROJECT_ROOT, 'src', 'skills-pg.ts'))
|
|
? 'present'
|
|
: 'missing',
|
|
skillsArtifactRows: 0,
|
|
skillsDocumentRows: 0,
|
|
skillsChunkRows: 0,
|
|
memoryRows: 0,
|
|
memoryChunkRows: 0,
|
|
memoryEmbeddingRows: 0,
|
|
};
|
|
|
|
const [skillsDb, memoryDb] = await Promise.all([
|
|
verifyDb(SKILLS_DB_URL),
|
|
verifyDb(MEMORY_DB_URL),
|
|
]);
|
|
status.skillsDb = skillsDb;
|
|
status.memoryDb = memoryDb;
|
|
|
|
if (status.skillsDb === 'available') {
|
|
try {
|
|
const client = new Client({
|
|
connectionString: SKILLS_DB_URL,
|
|
connectionTimeoutMillis: 5000,
|
|
});
|
|
await client.connect();
|
|
try {
|
|
const [artifactRows, documentRows, chunkRows, latestArtifact] =
|
|
await Promise.all([
|
|
client.query<{ count: string }>(
|
|
'SELECT count(*) FROM builtin_knowledge_artifacts',
|
|
),
|
|
client.query<{ count: string }>(
|
|
'SELECT count(*) FROM builtin_knowledge_documents',
|
|
),
|
|
client.query<{ count: string }>(
|
|
'SELECT count(*) FROM builtin_knowledge_chunks',
|
|
),
|
|
client.query<{ artifact_version: string }>(
|
|
'SELECT artifact_version FROM builtin_knowledge_artifacts ORDER BY created_at DESC LIMIT 1',
|
|
),
|
|
]);
|
|
|
|
status.skillsArtifactRows = Number(artifactRows.rows[0]?.count ?? 0);
|
|
status.skillsDocumentRows = Number(documentRows.rows[0]?.count ?? 0);
|
|
status.skillsChunkRows = Number(chunkRows.rows[0]?.count ?? 0);
|
|
status.skillsArtifactDbVersion =
|
|
latestArtifact.rows[0]?.artifact_version ?? 'unknown';
|
|
|
|
const expectedDocuments = Number(metadata?.document_count ?? 0);
|
|
const expectedChunks = Number(metadata?.chunk_count ?? 0);
|
|
|
|
if (
|
|
status.skillsArtifactSql === 'missing' ||
|
|
status.skillsArtifactMetadata === 'missing' ||
|
|
status.skillsArtifactRows < 1
|
|
) {
|
|
status.skillsArtifact = 'missing';
|
|
} else if (
|
|
status.skillsDocumentRows < Math.max(expectedDocuments, 1) ||
|
|
status.skillsChunkRows < Math.max(expectedChunks, 1) ||
|
|
(status.skillsArtifactVersion !== 'unknown' &&
|
|
status.skillsArtifactDbVersion !== 'unknown' &&
|
|
status.skillsArtifactVersion !== status.skillsArtifactDbVersion)
|
|
) {
|
|
status.skillsArtifact = 'incomplete';
|
|
} else {
|
|
status.skillsArtifact = 'ready';
|
|
}
|
|
} finally {
|
|
await client.end();
|
|
}
|
|
} catch {
|
|
status.skillsArtifact = 'unknown';
|
|
}
|
|
} else {
|
|
status.skillsArtifact = 'unavailable';
|
|
}
|
|
|
|
if (status.memoryDb === 'available') {
|
|
try {
|
|
const client = new Client({
|
|
connectionString: MEMORY_DB_URL,
|
|
connectionTimeoutMillis: 5000,
|
|
});
|
|
await client.connect();
|
|
try {
|
|
const [memoryRows, memoryChunkRows, memoryEmbeddingRows] =
|
|
await Promise.all([
|
|
client.query<{ count: string }>('SELECT count(*) FROM memories'),
|
|
client.query<{ count: string }>('SELECT count(*) FROM memory_chunks'),
|
|
client.query<{ count: string }>('SELECT count(*) FROM memory_embeddings'),
|
|
]);
|
|
|
|
status.memoryRows = Number(memoryRows.rows[0]?.count ?? 0);
|
|
status.memoryChunkRows = Number(memoryChunkRows.rows[0]?.count ?? 0);
|
|
status.memoryEmbeddingRows = Number(memoryEmbeddingRows.rows[0]?.count ?? 0);
|
|
} finally {
|
|
await client.end();
|
|
}
|
|
} catch {
|
|
// Leave counts at zero; availability is already tracked above.
|
|
}
|
|
}
|
|
|
|
return status;
|
|
}
|
|
|
|
export function deriveSplitBrainReadiness(
|
|
status: SplitBrainStatus,
|
|
): 'ready' | 'degraded' {
|
|
return status.skillsDb === 'available' &&
|
|
status.memoryDb === 'available' &&
|
|
status.skillsArtifact === 'ready' &&
|
|
status.skillsRuntimeLookup === 'present'
|
|
? 'ready'
|
|
: 'degraded';
|
|
}
|
|
|
|
export function collectSplitBrainIssues(status: SplitBrainStatus): string[] {
|
|
const issues: string[] = [];
|
|
|
|
if (status.skillsDb !== 'available') {
|
|
issues.push('skills db is unavailable');
|
|
}
|
|
if (status.memoryDb !== 'available') {
|
|
issues.push('memory db is unavailable');
|
|
}
|
|
if (status.skillsArtifact !== 'ready') {
|
|
issues.push(`built-in knowledge artifact is ${status.skillsArtifact}`);
|
|
}
|
|
if (status.skillsRuntimeLookup !== 'present') {
|
|
issues.push('skills runtime lookup is missing');
|
|
}
|
|
|
|
return issues;
|
|
}
|