Context
PR #10131 landed a 2-char correctness fix in ai/mcp/server/memory-core/services/DatabaseService.mjs — Memory_DatabaseService#exportGraph was writing JSONL record separators as the literal two characters \n (backslash + n) instead of \n (newline). Every historical graph-backup-*.jsonl artifact became single-line malformed JSONL unrestorable via the current import path.
The fix is live and verified empirically post-merge: the first npm run ai:backup run produced graph-backup-2026-04-20T18-59-41.643Z.jsonl with wc -l = 369 (one line per record, matching direct-SQL graph count).
But the fix has zero CI regression coverage. If a future refactor reverts to the escaped form, no test catches it. My self-review on #10131 scored EXECUTION_QUALITY at 78 primarily because of this gap.
The Problem
#exportGraph has no Playwright coverage. Sibling specs skip it:
DatabaseService.backupPath.spec.mjs — only memories + summaries
backup.mjs orchestrator spec — asserts graph/ subfolder exists but not populated (GraphService.db not wired under unitTestMode)
Adding coverage requires either a minimal .db.storage.db mock (fake prepare()/iterate()) or a real better-sqlite3 in-memory DB seeded with Nodes + Edges.
The Architectural Reality
ai/mcp/server/memory-core/services/DatabaseService.mjs — #exportGraph at lines ~139-210 (newline emission at lines 187 + 201)
ai/mcp/server/memory-core/services/GraphService.mjs — singleton whose .db.storage.db needs stubbing
- New spec:
test/playwright/unit/ai/mcp/server/memory-core/services/DatabaseService.exportGraph.spec.mjs
Pattern to mirror: DatabaseService.backupPath.spec.mjs stubs Memory_StorageRouter.get{Memory,Summary}Collection; new spec stubs Memory_GraphService.db.storage.db analogously.
The Fix
- Add spec, stub
Memory_GraphService.db.storage.db with fake prepare(sql).get() + prepare(sql).iterate()
- Seed with ≥2 nodes + ≥2 edges
- Call
Memory_DatabaseService.manageDatabaseBackup({action:'export', include:['graph'], backupPath: tmpDir})
- Assert:
wc -l equals seeded record count (newline emission)
- Each line parses as valid JSON independently (separator, not concatenation)
- Record content matches seed data
- Apply
test.describe.configure({mode: 'serial'}) per the sibling-spec convention
- Sanity check: revert the fix locally, confirm spec fails
Acceptance Criteria
Out of Scope
- Full SQLite round-trip testing (heavier setup)
#importGraph regression coverage — separate concern
- Generic parallel-worker race fix — one-off per-spec config is the established convention
Avoided Traps
- Rejected: rely on empirical evidence. The first live
ai:backup confirmed the fix today. But empirical evidence ≠ CI regression protection. If someone reverts in a refactor, no automated signal catches it. This is the rationalization I flagged in #10131's self-review.
Related
- Follow-up on #10131 — where the
\n fix landed
- Sibling spec:
DatabaseService.backupPath.spec.mjs
- Self-review: PR #10131 comment (2026-04-20) flagged this as the main EXECUTION_QUALITY gap
Origin Session ID: 5a521819-dc75-4549-888e-fcea818d0401
Context
PR #10131 landed a 2-char correctness fix in
ai/mcp/server/memory-core/services/DatabaseService.mjs—Memory_DatabaseService#exportGraphwas writing JSONL record separators as the literal two characters\n(backslash + n) instead of\n(newline). Every historicalgraph-backup-*.jsonlartifact became single-line malformed JSONL unrestorable via the current import path.The fix is live and verified empirically post-merge: the first
npm run ai:backuprun producedgraph-backup-2026-04-20T18-59-41.643Z.jsonlwithwc -l = 369(one line per record, matching direct-SQL graph count).But the fix has zero CI regression coverage. If a future refactor reverts to the escaped form, no test catches it. My self-review on #10131 scored EXECUTION_QUALITY at 78 primarily because of this gap.
The Problem
#exportGraphhas no Playwright coverage. Sibling specs skip it:DatabaseService.backupPath.spec.mjs— only memories + summariesbackup.mjsorchestrator spec — assertsgraph/subfolder exists but not populated (GraphService.dbnot wired underunitTestMode)Adding coverage requires either a minimal
.db.storage.dbmock (fakeprepare()/iterate()) or a realbetter-sqlite3in-memory DB seeded with Nodes + Edges.The Architectural Reality
ai/mcp/server/memory-core/services/DatabaseService.mjs—#exportGraphat lines ~139-210 (newline emission at lines 187 + 201)ai/mcp/server/memory-core/services/GraphService.mjs— singleton whose.db.storage.dbneeds stubbingtest/playwright/unit/ai/mcp/server/memory-core/services/DatabaseService.exportGraph.spec.mjsPattern to mirror:
DatabaseService.backupPath.spec.mjsstubsMemory_StorageRouter.get{Memory,Summary}Collection; new spec stubsMemory_GraphService.db.storage.dbanalogously.The Fix
Memory_GraphService.db.storage.dbwith fakeprepare(sql).get()+prepare(sql).iterate()Memory_DatabaseService.manageDatabaseBackup({action:'export', include:['graph'], backupPath: tmpDir})wc -lequals seeded record count (newline emission)test.describe.configure({mode: 'serial'})per the sibling-spec conventionAcceptance Criteria
DatabaseService.exportGraph.spec.mjsundertest/playwright/unit/ai/mcp/server/memory-core/services/test.describe.configure({mode: 'serial'})applied\nis reverted to\\nat either emission site — validated locally as part of PROut of Scope
#importGraphregression coverage — separate concernAvoided Traps
ai:backupconfirmed the fix today. But empirical evidence ≠ CI regression protection. If someone reverts in a refactor, no automated signal catches it. This is the rationalization I flagged in #10131's self-review.Related
\nfix landedDatabaseService.backupPath.spec.mjsOrigin Session ID: 5a521819-dc75-4549-888e-fcea818d0401