feat(FN-1227): add mesh state and system metrics support to CentralCore

- Add SystemMetrics, PeerNode, NodeMeshState, and MeshDiscovery types and export new mesh/metrics APIs from @fusion/core
- Introduce collectSystemMetrics with CPU, memory, storage, and uptime collection (including check-disk-space integration) plus dedicated unit tests
- Bump central DB schema to v3 with nodes.systemMetrics/knownPeers and peerNodes table, including migration coverage for v2->v3 upgrades
- Extend CentralCore with node metrics updates, peer register/unregister/list operations, mesh snapshot reporting, and event emissions backed by expanded test coverage
This commit is contained in:
gsxdsm
2026-04-08 11:21:18 -07:00
parent 0e200e2483
commit ff7f35c6c0
10 changed files with 851 additions and 2 deletions

View File

@@ -23,7 +23,7 @@ export { toJson, toJsonNullable, fromJson };
// ── Schema Definition ───────────────────────────────────────────────────
const CENTRAL_SCHEMA_VERSION = 2;
const CENTRAL_SCHEMA_VERSION = 3;
const CENTRAL_SCHEMA_SQL = `
-- Projects table (project registry)
@@ -96,6 +96,8 @@ CREATE TABLE IF NOT EXISTS nodes (
apiKey TEXT,
status TEXT NOT NULL DEFAULT 'offline',
capabilities TEXT,
systemMetrics TEXT,
knownPeers TEXT,
maxConcurrent INTEGER NOT NULL DEFAULT 2,
createdAt TEXT NOT NULL,
updatedAt TEXT NOT NULL
@@ -103,6 +105,21 @@ CREATE TABLE IF NOT EXISTS nodes (
CREATE INDEX IF NOT EXISTS idxNodesStatus ON nodes(status);
CREATE INDEX IF NOT EXISTS idxNodesType ON nodes(type);
-- Peer nodes table (mesh awareness graph per node)
CREATE TABLE IF NOT EXISTS peerNodes (
id TEXT PRIMARY KEY,
nodeId TEXT NOT NULL,
peerNodeId TEXT NOT NULL,
name TEXT NOT NULL,
url TEXT NOT NULL,
status TEXT NOT NULL DEFAULT 'unknown',
lastSeen TEXT NOT NULL,
connectedAt TEXT NOT NULL,
UNIQUE(nodeId, peerNodeId),
FOREIGN KEY (nodeId) REFERENCES nodes(id) ON DELETE CASCADE
);
CREATE INDEX IF NOT EXISTS idxPeerNodesNodeId ON peerNodes(nodeId);
-- Schema version tracking
CREATE TABLE IF NOT EXISTS __meta (
key TEXT PRIMARY KEY,
@@ -127,6 +144,29 @@ CREATE INDEX IF NOT EXISTS idxNodesStatus ON nodes(status);
CREATE INDEX IF NOT EXISTS idxNodesType ON nodes(type);
`;
const CENTRAL_SCHEMA_V3_MIGRATION_SQL = `
ALTER TABLE nodes ADD COLUMN systemMetrics TEXT;
ALTER TABLE nodes ADD COLUMN knownPeers TEXT;
CREATE TABLE IF NOT EXISTS peerNodes (
id TEXT PRIMARY KEY,
nodeId TEXT NOT NULL,
peerNodeId TEXT NOT NULL,
name TEXT NOT NULL,
url TEXT NOT NULL,
status TEXT NOT NULL DEFAULT 'unknown',
lastSeen TEXT NOT NULL,
connectedAt TEXT NOT NULL,
UNIQUE(nodeId, peerNodeId),
FOREIGN KEY (nodeId) REFERENCES nodes(id) ON DELETE CASCADE
);
CREATE INDEX IF NOT EXISTS idxPeerNodesNodeId ON peerNodes(nodeId);
`;
const CENTRAL_SCHEMA_V3_CREATE_PEERS_SQL = CENTRAL_SCHEMA_V3_MIGRATION_SQL
.split("\n")
.filter((line) => !line.trim().startsWith("ALTER TABLE nodes ADD COLUMN"))
.join("\n");
// ── Central Database Class ────────────────────────────────────────────────
export class CentralDatabase {
@@ -161,11 +201,28 @@ export class CentralDatabase {
this.db.exec(CENTRAL_SCHEMA_SQL);
const currentVersion = this.getSchemaVersion();
let migrated = false;
if (currentVersion < 2) {
this.db.exec(CENTRAL_SCHEMA_V2_MIGRATION_SQL);
if (!this.hasColumn("projects", "nodeId")) {
this.db.exec("ALTER TABLE projects ADD COLUMN nodeId TEXT");
}
migrated = true;
}
if (currentVersion < 3) {
if (!this.hasColumn("nodes", "systemMetrics")) {
this.db.exec("ALTER TABLE nodes ADD COLUMN systemMetrics TEXT");
}
if (!this.hasColumn("nodes", "knownPeers")) {
this.db.exec("ALTER TABLE nodes ADD COLUMN knownPeers TEXT");
}
this.db.exec(CENTRAL_SCHEMA_V3_CREATE_PEERS_SQL);
migrated = true;
}
if (migrated) {
this.db
.prepare("INSERT INTO __meta (key, value) VALUES ('schemaVersion', ?) ON CONFLICT(key) DO UPDATE SET value = excluded.value")
.run(String(CENTRAL_SCHEMA_VERSION));