mirror of
https://github.com/ruvnet/ruflo.git
synced 2026-09-14 14:01:28 +08:00
fix: Windows CI build, dead agentdb controller exports, memory driver doctor check
- #2992: pin windows-latest -> windows-2022 for the Build V3 matrix job. windows-latest moved to VS2026, which node-gyp can't detect, breaking native builds for hnswlib-node and better-sqlite3. - #2977: remove dead agentdb import attempts in controller-registry.ts for 8 exports no installable agentdb range provides. Promote the working tieredMemoryFallback/createConsolidationStub implementations to first-class for hierarchicalMemory/memoryConsolidation; the other 6 controllers (semanticRouter, mutationGuard, attestationLog, gnnService, rvfOptimizer, guardedVectorBackend) have no fallback and now return null directly instead of attempting an import that never resolves. The consolidation stub now reports source:'stub' and an explicit note so agentdb_consolidate reads as "did not run" rather than "nothing to do". - #2968: add a read-only doctor check (checkMemoryPersistenceDriver) that reports whether the active SQLite driver is native better-sqlite3 (durable, WAL-capable) or the sql.js fallback (silently drops wal_checkpoint writes), using the table-count signal from the issue (~47 native vs ~10 fallback). Warns, never fails; does not touch install behavior. #2990 (MCP HTTP transport protocolVersion/tools-list/IPv6 bind) required no code change — all three fixes are already present on main; the bug in published ruflo/@claude-flow/cli only persists because no release has been cut since they landed. #3002 (ruvocal hidden-tab stream freeze) also required no code change — already fixed on main. Co-Authored-By: RuFlo <ruv@ruv.net> Claude-Session: https://claude.ai/code/session_018ZkgSrVRWNMcXmaFiMZ7Z8
This commit is contained in:
@@ -433,7 +433,11 @@ jobs:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
os: [ubuntu-latest, macos-latest, windows-latest]
|
||||
# windows-latest pinned to windows-2022 (#2992): the windows-latest image
|
||||
# moved to VS2026, which node-gyp's VS detector can't identify, breaking
|
||||
# native builds for hnswlib-node and better-sqlite3. Unpin once node-gyp
|
||||
# supports VS2026 detection.
|
||||
os: [ubuntu-latest, macos-latest, windows-2022]
|
||||
|
||||
steps:
|
||||
- name: Checkout code
|
||||
|
||||
@@ -537,6 +537,132 @@ async function checkMemoryStructuralIntegrity(): Promise<HealthCheck> {
|
||||
}
|
||||
}
|
||||
|
||||
// #2968 option 1 — read-only doctor check for the active SQLite driver.
|
||||
//
|
||||
// Native better-sqlite3 (durable, WAL-capable) can silently degrade to the
|
||||
// sql.js WASM fallback (non-durable — `wal_checkpoint` calls are rejected
|
||||
// and the write is lost) when a postinstall script is skipped. `memory
|
||||
// store` still printed "Data stored successfully" before the persistWarning
|
||||
// fix in #2983/3.38.1 (see memory-store-persist-warning-2968.test.ts); this
|
||||
// check gives a standing, read-only signal so the driver split is visible
|
||||
// any time doctor runs, independent of any single store call.
|
||||
//
|
||||
// Table count in the on-disk memory.db is the cheap, reliable signal from
|
||||
// the issue report: the native driver's schema produces 47 tables, the
|
||||
// sql.js fallback's produces only 10. Deliberately does NOT change install
|
||||
// behavior (that's option 2 from #2968, explicitly out of scope here) —
|
||||
// this only reports, it never repairs.
|
||||
const MEMORY_DRIVER_NATIVE_TABLE_FLOOR = 20; // roughly midpoint of sql.js's ~10 and native's ~47
|
||||
|
||||
async function checkMemoryPersistenceDriver(): Promise<HealthCheck> {
|
||||
const NAME = 'Memory Persistence Driver';
|
||||
const dbPath = await resolveMemoryDbPath();
|
||||
if (!dbPath) {
|
||||
return {
|
||||
name: NAME,
|
||||
status: 'warn',
|
||||
message: 'no memory.db found (see Memory Database Presence above) — driver check skipped',
|
||||
};
|
||||
}
|
||||
|
||||
if (isMemoryDbEncryptedAtRest(dbPath)) {
|
||||
return {
|
||||
name: NAME,
|
||||
status: 'warn',
|
||||
message: `${dbPath} — RFE1-encrypted at rest; driver check can't run without decrypting (expected, not corruption)`,
|
||||
};
|
||||
}
|
||||
|
||||
let Database: any;
|
||||
try {
|
||||
Database = ((await import('better-sqlite3')) as any).default;
|
||||
} catch {
|
||||
Database = null;
|
||||
}
|
||||
|
||||
let tableCount: number | null = null;
|
||||
let nativeUnavailableReason: string | null = null;
|
||||
let nativeOpenOtherError: string | null = null;
|
||||
|
||||
if (Database) {
|
||||
let db: any;
|
||||
try {
|
||||
db = new Database(dbPath, { readonly: true, fileMustExist: true });
|
||||
} catch (e) {
|
||||
if (isNativeSqliteBindingUnavailable(e)) {
|
||||
nativeUnavailableReason = ((e as Error).message || String(e)).split(/\r?\n/, 1)[0];
|
||||
} else {
|
||||
nativeOpenOtherError = (e as Error).message || String(e);
|
||||
}
|
||||
db = null;
|
||||
}
|
||||
if (db) {
|
||||
try {
|
||||
const row = db.prepare("SELECT count(*) AS c FROM sqlite_master WHERE type='table'").get() as { c: number };
|
||||
tableCount = Number(row?.c ?? 0);
|
||||
} catch {
|
||||
// leave null — Memory Integrity above already reports open/query failures
|
||||
} finally {
|
||||
try { db.close(); } catch { /* best-effort */ }
|
||||
}
|
||||
}
|
||||
} else {
|
||||
nativeUnavailableReason = 'better-sqlite3 not installed';
|
||||
}
|
||||
|
||||
// Fall back to sql.js purely to report table count when the native path
|
||||
// couldn't — informational only, never used to claim durability.
|
||||
if (tableCount === null && (nativeUnavailableReason || nativeOpenOtherError)) {
|
||||
const sdb = await tryOpenSqlJs(dbPath);
|
||||
if (sdb) {
|
||||
try {
|
||||
const res = sdb.exec("SELECT count(*) FROM sqlite_master WHERE type='table'");
|
||||
tableCount = Number(res[0]?.values?.[0]?.[0] ?? 0);
|
||||
} catch {
|
||||
// leave null
|
||||
} finally {
|
||||
try { sdb.close(); } catch { /* best-effort */ }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const tableSummary = tableCount === null
|
||||
? 'table count unavailable'
|
||||
: `${tableCount} tables (native schema ~47, sql.js-fallback schema ~10 — #2968)`;
|
||||
|
||||
if (nativeUnavailableReason) {
|
||||
return {
|
||||
name: NAME,
|
||||
status: 'warn',
|
||||
message: `${dbPath} — active driver: sql.js (WASM fallback, non-durable) — native better-sqlite3 binding unavailable: ${nativeUnavailableReason} — wal_checkpoint calls silently no-op, writes may not persist across processes (#2968/#2867/#2219) [${tableSummary}]`,
|
||||
fix: 'reinstall with npm install scripts enabled, or run `npm rebuild better-sqlite3`; then rerun this check',
|
||||
};
|
||||
}
|
||||
|
||||
if (nativeOpenOtherError) {
|
||||
return {
|
||||
name: NAME,
|
||||
status: 'warn',
|
||||
message: `${dbPath} — native better-sqlite3 module loadable, but could not open this database (${nativeOpenOtherError}) — see Memory Integrity above for the corruption/encryption diagnosis [${tableSummary}]`,
|
||||
};
|
||||
}
|
||||
|
||||
if (tableCount !== null && tableCount < MEMORY_DRIVER_NATIVE_TABLE_FLOOR) {
|
||||
return {
|
||||
name: NAME,
|
||||
status: 'warn',
|
||||
message: `${dbPath} — active driver: native better-sqlite3, but this database has only ${tableCount} tables — that matches the sql.js-fallback schema shape (~10), not the native schema (~47); it was likely created before the native binding became available, and durable writes made before then may be missing`,
|
||||
fix: 'back up .swarm/memory.db then `claude-flow memory init --force` to rebuild under the native driver',
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
name: NAME,
|
||||
status: 'pass',
|
||||
message: `${dbPath} — active driver: native better-sqlite3 (durable, WAL-capable) [${tableSummary}]`,
|
||||
};
|
||||
}
|
||||
|
||||
// Check 1 (--component memory, deep path) — #2737 part 2 strengthens this:
|
||||
// prefer native better-sqlite3 PRAGMA integrity_check (adds index↔table
|
||||
// cross-checking + UNIQUE verification that quick_check above deliberately
|
||||
@@ -2274,6 +2400,7 @@ export const doctorCommand: Command = {
|
||||
checkDaemonStatus,
|
||||
checkMemoryDatabase,
|
||||
checkMemoryStructuralIntegrity, // #2737 — bounded, native quick_check on every default run
|
||||
checkMemoryPersistenceDriver, // #2968 — native better-sqlite3 vs sql.js fallback, read-only
|
||||
checkLearningBridge, // #2545 — can the auto-memory hook actually load @claude-flow/memory?
|
||||
checkApiKeys,
|
||||
checkMcpServers,
|
||||
@@ -2312,6 +2439,7 @@ export const doctorCommand: Command = {
|
||||
'memory': [
|
||||
checkMemoryDatabase, // existing: exists + statable (unchanged)
|
||||
checkMemoryIntegrity, // #2677 check 1: sql.js open + PRAGMA integrity_check
|
||||
checkMemoryPersistenceDriver, // #2968: native better-sqlite3 vs sql.js fallback
|
||||
checkMemoryContent, // #2677 check 2: memory_entries content coverage
|
||||
checkMemoryEmbeddingCoverage, // #2677 check 3: vector coverage on populated rows
|
||||
checkMemoryReflexionCoverage, // #2677 check 6: episodes are retrievable
|
||||
|
||||
@@ -894,18 +894,13 @@ export class ControllerRegistry extends EventEmitter {
|
||||
// Agent memory scope — placeholder, activated when explicitly enabled
|
||||
return null;
|
||||
|
||||
case 'semanticRouter': {
|
||||
// SemanticRouter exported from agentdb 3.0.0-alpha.10 (ADR-062)
|
||||
// Constructor: () — requires initialize() after construction
|
||||
try {
|
||||
const agentdbModule: any = await import('agentdb');
|
||||
const SR = agentdbModule.SemanticRouter;
|
||||
if (!SR) return null;
|
||||
const router = new SR();
|
||||
await router.initialize();
|
||||
return router;
|
||||
} catch { return null; }
|
||||
}
|
||||
case 'semanticRouter':
|
||||
// SemanticRouter was exported by agentdb 3.0.0-alpha.10 (ADR-062) but
|
||||
// dropped from every installable range (`^3.0.0-alpha.17`) — issue
|
||||
// #2977. No fallback implementation exists for this controller;
|
||||
// returning null directly instead of attempting a dynamic import
|
||||
// that never resolves.
|
||||
return null;
|
||||
|
||||
case 'sonaTrajectory':
|
||||
// Delegate to AgentDB's SonaTrajectoryService if available
|
||||
@@ -920,47 +915,31 @@ export class ControllerRegistry extends EventEmitter {
|
||||
|
||||
case 'hierarchicalMemory': {
|
||||
// HierarchicalMemory was exported by agentdb 3.0.0-alpha.10 (ADR-066
|
||||
// Phase P2-3) and REMOVED again at alpha.17 — so the fallback below is
|
||||
// the live path, not an edge case. Every fallback records why, because
|
||||
// taking it silently is what made stores look successful while landing
|
||||
// nowhere (#2887).
|
||||
if (!this.agentdb) return this.tieredMemoryFallback('agentdb-unavailable');
|
||||
try {
|
||||
const agentdbModule: any = await import('agentdb');
|
||||
const HM = agentdbModule.HierarchicalMemory;
|
||||
if (typeof HM !== 'function') {
|
||||
return this.tieredMemoryFallback('agentdb-export-missing');
|
||||
}
|
||||
const embedder = this.createEmbeddingService();
|
||||
const hm = new HM(this.agentdb.database, embedder);
|
||||
await hm.initializeDatabase();
|
||||
return hm;
|
||||
} catch (error) {
|
||||
const msg = error instanceof Error ? error.message : String(error);
|
||||
return this.tieredMemoryFallback(`agentdb-init-failed: ${msg.substring(0, 120)}`);
|
||||
}
|
||||
// Phase P2-3) and REMOVED again at alpha.17 — every installable range
|
||||
// (`^3.0.0-alpha.17`) is missing it, and alpha.10 — the one version
|
||||
// that DOES export it — throws `RangeError: Too few parameter values
|
||||
// were provided` on the very first store() call (issue #2977), so
|
||||
// there is no version of agentdb where the native path actually
|
||||
// works. The dynamic-import/instantiate attempt is dead code on
|
||||
// every install; the tiered fallback is the only implementation that
|
||||
// has ever worked here, so it is promoted to first-class instead of
|
||||
// being attempted-and-discarded on every init. Still records *why*
|
||||
// it's in use, because taking a fallback silently is what made
|
||||
// stores look successful while landing nowhere (#2887).
|
||||
return this.tieredMemoryFallback(
|
||||
this.agentdb ? 'agentdb-export-missing' : 'agentdb-unavailable',
|
||||
);
|
||||
}
|
||||
|
||||
case 'memoryConsolidation': {
|
||||
// MemoryConsolidation exported from agentdb 3.0.0-alpha.10 (ADR-066 Phase P2-3)
|
||||
// Constructor: (db, hierarchicalMemory, embedder, vectorBackend?, graphBackend?, config?)
|
||||
if (!this.agentdb) return this.createConsolidationStub();
|
||||
try {
|
||||
const agentdbModule: any = await import('agentdb');
|
||||
const MC = agentdbModule.MemoryConsolidation;
|
||||
if (!MC) return this.createConsolidationStub();
|
||||
// Get the HierarchicalMemory instance (must be initialized at level 1 before us at level 3)
|
||||
const hm: any = this.get('hierarchicalMemory');
|
||||
if (!hm || typeof hm.recall !== 'function' || typeof hm.store !== 'function') {
|
||||
return this.createConsolidationStub();
|
||||
}
|
||||
const embedder = this.createEmbeddingService();
|
||||
const mc = new MC(this.agentdb.database, hm, embedder);
|
||||
await mc.initializeDatabase();
|
||||
return mc;
|
||||
} catch {
|
||||
return this.createConsolidationStub();
|
||||
}
|
||||
// MemoryConsolidation was exported by agentdb 3.0.0-alpha.10 (ADR-066
|
||||
// Phase P2-3) and removed at alpha.14 — no installable range exports
|
||||
// it, and it requires a working HierarchicalMemory instance to
|
||||
// construct, which is itself dead (see hierarchicalMemory above,
|
||||
// #2977). The no-op stub is therefore the only implementation that
|
||||
// has ever worked here; it is promoted to first-class instead of
|
||||
// attempting a dynamic import that never resolves.
|
||||
return this.createConsolidationStub();
|
||||
}
|
||||
|
||||
case 'federatedSession':
|
||||
@@ -1088,70 +1067,18 @@ export class ControllerRegistry extends EventEmitter {
|
||||
} catch { return null; }
|
||||
}
|
||||
|
||||
case 'mutationGuard': {
|
||||
// MutationGuard exported from agentdb 3.0.0-alpha.10 (ADR-060)
|
||||
// Constructor: (config?) where config.dimension, config.maxElements, config.enableWasmProofs
|
||||
if (!this.agentdb) return null;
|
||||
try {
|
||||
const agentdbModule: any = await import('agentdb');
|
||||
const MG = agentdbModule.MutationGuard;
|
||||
if (!MG) return null;
|
||||
return new MG({ dimension: this.config.dimension || 384 });
|
||||
} catch { return null; }
|
||||
}
|
||||
|
||||
case 'attestationLog': {
|
||||
// AttestationLog exported from agentdb 3.0.0-alpha.10 (ADR-060)
|
||||
// Constructor: (db) — uses database for append-only audit log
|
||||
if (!this.agentdb) return null;
|
||||
try {
|
||||
const agentdbModule: any = await import('agentdb');
|
||||
const AL = agentdbModule.AttestationLog;
|
||||
if (!AL) return null;
|
||||
return new AL(this.agentdb.database);
|
||||
} catch { return null; }
|
||||
}
|
||||
|
||||
case 'gnnService': {
|
||||
// GNNService exported from agentdb 3.0.0-alpha.10 (ADR-062)
|
||||
// Constructor: (config?) — requires initialize() after construction
|
||||
try {
|
||||
const agentdbModule: any = await import('agentdb');
|
||||
const GNN = agentdbModule.GNNService;
|
||||
if (!GNN) return null;
|
||||
const gnn = new GNN({ inputDim: this.config.dimension || 384 });
|
||||
await gnn.initialize();
|
||||
return gnn;
|
||||
} catch { return null; }
|
||||
}
|
||||
|
||||
case 'rvfOptimizer': {
|
||||
// RVFOptimizer exported from agentdb 3.0.0-alpha.10 (ADR-062/065)
|
||||
// Constructor: (config?) — no-arg for defaults
|
||||
try {
|
||||
const agentdbModule: any = await import('agentdb');
|
||||
const RVF = agentdbModule.RVFOptimizer;
|
||||
if (!RVF) return null;
|
||||
return new RVF();
|
||||
} catch { return null; }
|
||||
}
|
||||
|
||||
case 'guardedVectorBackend': {
|
||||
// GuardedVectorBackend exported from agentdb 3.0.0-alpha.10 (ADR-060)
|
||||
// Constructor: (innerBackend, mutationGuard, attestationLog?)
|
||||
// Requires vectorBackend and mutationGuard to be initialized first (level 2)
|
||||
if (!this.agentdb) return null;
|
||||
try {
|
||||
const vb = this.get('vectorBackend');
|
||||
const guard = this.get('mutationGuard');
|
||||
if (!vb || !guard) return null;
|
||||
const agentdbModule: any = await import('agentdb');
|
||||
const GVB = agentdbModule.GuardedVectorBackend;
|
||||
if (!GVB) return null;
|
||||
const log = this.get('attestationLog');
|
||||
return new GVB(vb, guard, log || undefined);
|
||||
} catch { return null; }
|
||||
}
|
||||
// MutationGuard, AttestationLog, GNNService, RVFOptimizer, and
|
||||
// GuardedVectorBackend were all exported by agentdb 3.0.0-alpha.10
|
||||
// (ADR-060/062/065) but dropped from every installable range
|
||||
// (`^3.0.0-alpha.17`) — issue #2977. No fallback implementation exists
|
||||
// for any of these five controllers; they return null directly instead
|
||||
// of attempting a dynamic import that never resolves.
|
||||
case 'mutationGuard':
|
||||
case 'attestationLog':
|
||||
case 'gnnService':
|
||||
case 'rvfOptimizer':
|
||||
case 'guardedVectorBackend':
|
||||
return null;
|
||||
|
||||
case 'vectorBackend':
|
||||
case 'graphAdapter': {
|
||||
@@ -1276,13 +1203,22 @@ export class ControllerRegistry extends EventEmitter {
|
||||
}
|
||||
|
||||
/**
|
||||
* No-op consolidation stub (fallback when MemoryConsolidation
|
||||
* cannot be initialized from agentdb).
|
||||
* No-op consolidation stub — the first-class implementation for
|
||||
* `memoryConsolidation` since agentdb has never shipped a working native
|
||||
* MemoryConsolidation on any installable range (#2977). `promoted: 0,
|
||||
* pruned: 0` on its own reads as "ran and found nothing to do"; `source`
|
||||
* and `note` make it explicit that consolidation never actually ran.
|
||||
*/
|
||||
private createConsolidationStub() {
|
||||
return {
|
||||
consolidate() {
|
||||
return { promoted: 0, pruned: 0, timestamp: Date.now() };
|
||||
return {
|
||||
promoted: 0,
|
||||
pruned: 0,
|
||||
timestamp: Date.now(),
|
||||
source: 'stub' as const,
|
||||
note: 'no-op: native MemoryConsolidation is unavailable (agentdb never exports a working implementation, #2977) — nothing was consolidated, this did not run',
|
||||
};
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user