mirror of
https://github.com/ruvnet/ruflo.git
synced 2026-09-14 14:01:28 +08:00
0e957da3a4
* fix(memory): make a latched bridge-init failure diagnosable and recoverable
getRegistry() sets `bridgeAvailable = false` from a bare `catch {}`, and the
guard at the top of the function short-circuits every later call. One transient
init failure therefore routes every subsequent write to the sql.js whole-image
fallback for the remaining life of the process.
That fallback refuses whenever -wal/-shm sidecars are present, so the operator
sees only:
memory database has an active native WAL connection (found -wal/-shm sidecar
files) — refusing an unsafe sql.js whole-image write. Retry once the native
writer completes, or restore the native better-sqlite3 bridge.
which names a cause they cannot check. Observed in the wild: an MCP server
silently dropped every memory_store for hours while CLI writes to the same
database succeeded. better-sqlite3 was healthy throughout — it loaded from
every resolution context and opened the database in WAL mode. The sidecars that
trigger the refusal are created by the healthy native writers, so the better
the native path works elsewhere, the more reliably a latched process fails.
Three changes:
- Record the init error instead of discarding it, and expose it through
`getBridgeFailureReason()`. The WAL refusal now appends
"Bridge unavailable: <reason>" at all three sites, via one helper rather
than three copies of the literal.
- `shutdownBridge()` clears the cached state unconditionally. The reset used to
sit inside `if (registryInstance)`, which is null in exactly the case that
needs clearing — a failed init — so a latched process had no recovery short
of a restart.
- Stop swallowing degradation notices during init. The suppressor filtered any
message containing 'better-sqlite3' or '[AgentDB]', which matched AgentDB's
own "better-sqlite3 not available, using sql.js WASM" line. The one message
explaining why the native driver was not in use was being discarded as noise.
Extracted as `shouldSuppressInitLog()` so it is testable.
ADR-053 (Implemented, 2026-02-25) documents the sql.js fallback as intentional
and is unchanged by this: the fallback still exists and still degrades the same
way — it is now observable and clearable, so no ADR update is required.
@claude-flow/memory joins the externalized optional deps in vitest.config.ts;
memory-bridge dynamic-imports it inside try/catch and its dist is absent unless
the workspace was built.
Verification: new suite 9/9 green, 7/9 fail against pristine. WAL-guard,
provenance, upsert and durability suites report 11 failed / 14 passed both with
and without this change — identical, pre-existing, none in the touched files.
tsc --noEmit: 437 errors on this branch and 437 on pristine main.
Co-Authored-By: Ruflo & AQE
* fix(memory): migrate provenance_type on the bridge path (root cause of silent write loss)
The real defect behind the misleading WAL-sidecar refusal.
ADR-323 added `provenance_type` to bridgeStoreEntry()'s INSERT.
`ensureSchemaColumns()` backfills that column on the sql.js path, but the
bridge path only ran `CREATE TABLE IF NOT EXISTS memory_entries (...)` — a
no-op on a table that already exists. Any database created before ADR-323
therefore has no `provenance_type`, and every bridge write throws:
SqliteError: table memory_entries has no column named provenance_type
The catch at the end of bridgeStoreEntry() returns null on anything that is
not a UNIQUE violation, so the error is discarded and the caller demotes to
the sql.js whole-image path — which refuses whenever -wal/-shm sidecars are
present, reporting "active native WAL connection … restore the native
better-sqlite3 bridge". That names a cause with nothing to do with the
failure. better-sqlite3 was healthy throughout.
Measured on a real install: `.swarm/memory.db` HAS provenance_type (the
sql.js path migrated it); `.swarm/agentdb-memory.db`, which the bridge writes
per #2786, does NOT. Every MCP memory_store was silently lost for hours while
CLI writes to the other database succeeded. Adding the column alone — with no
code change — turned bridgeStoreEntry from null into
`{success: true, embedding: {dimensions: 384}}`.
Changes:
- `provenance_type` is in the CREATE TABLE DDL (fresh databases) AND applied
as an idempotent ALTER (pre-existing ones). SQLite has no ADD COLUMN IF NOT
EXISTS, so attempt-and-ignore is the idiom.
- The schema block is extracted as exported `ensureBridgeSchema()`. The bug
was untestable in place: the failure it causes is swallowed and resurfaces
as an unrelated message, so nothing end-to-end could have caught it.
- bridgeStoreEntry's catch now records the swallowed error in
`bridgeFailureReason`, so the WAL refusal carries the real cause.
`getBridgeFailureReason()` is no longer gated on `bridgeAvailable === false`
— a bridge that initialised fine can still fail every write, which is
precisely this bug, and that case is the one worth reporting.
Verification: 5 new migration tests green; 3 of the 5 fail with the exact
production error when the ALTER is removed. Combined suites report the same 11
pre-existing failures as pristine, with 28 passing (14 of them new).
tsc --noEmit: 437 on this branch, 437 on pristine main.
Co-Authored-By: Ruflo & AQE
45 lines
1.8 KiB
TypeScript
45 lines
1.8 KiB
TypeScript
import { defineConfig } from 'vitest/config';
|
|
import { fileURLToPath } from 'node:url';
|
|
|
|
export default defineConfig({
|
|
resolve: {
|
|
conditions: ['node'],
|
|
// Exercise the workspace security source rather than a stale installed
|
|
// package during cross-package policy integration tests (ADR-324).
|
|
alias: {
|
|
'@claude-flow/security': fileURLToPath(new URL('../security/src/index.ts', import.meta.url)),
|
|
},
|
|
},
|
|
plugins: [
|
|
{
|
|
name: 'externalize-optional-deps',
|
|
enforce: 'pre',
|
|
resolveId(source) {
|
|
// Don't let Vite resolve optional deps that may have missing subpath
|
|
// exports. These are imported via try/catch dynamic import in src/
|
|
// (sona-optimizer falls back to no-SONA when the package isn't
|
|
// installed). External-marking them keeps vitest from failing
|
|
// module resolution at transform time.
|
|
if (source.startsWith('agentic-flow')) return { id: source, external: true };
|
|
if (source.startsWith('agentdb')) return { id: source, external: true };
|
|
// memory-bridge dynamic-imports this inside try/catch and degrades to
|
|
// the sql.js path; its dist is absent unless the workspace was built.
|
|
if (source === '@claude-flow/memory') return { id: source, external: true };
|
|
if (source.startsWith('@ruvector/')) return { id: source, external: true };
|
|
if (source.startsWith('@huggingface/transformers')) return { id: source, external: true };
|
|
if (source.startsWith('@xenova/transformers')) return { id: source, external: true };
|
|
if (source.startsWith('@noble/ed25519')) return { id: source, external: true };
|
|
return null;
|
|
},
|
|
},
|
|
],
|
|
test: {
|
|
environment: 'node',
|
|
include: ['__tests__/**/*.test.ts'],
|
|
globals: true,
|
|
coverage: {
|
|
enabled: false,
|
|
},
|
|
},
|
|
});
|