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:
ruv
2026-08-22 12:14:24 -04:00
parent 5234333c34
commit 8a85001942
3 changed files with 186 additions and 118 deletions
+5 -1
View File
@@ -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
+128
View File
@@ -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
+53 -117
View File
@@ -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',
};
},
};
}