From 3e60aded36cdecd1fbb6389cbcdb394188b40bec Mon Sep 17 00:00:00 2001 From: David Snelling Date: Tue, 1 Sep 2026 12:48:38 -0700 Subject: [PATCH] perf(vfs): repairContainment's reconcile is one paged edge walk, not one graph call per file MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Pass 2 issued one awaited related({ to }) per VFS entity — O(entities) serialized graph calls, measured in whole minutes on large brains. Now a single paged walk over every Contains edge (type-only, 1,000 per page) feeds an in-memory group-by-target, and only actual defects mutate. The verdicts are unchanged: a stale parent's edge is removed, a missing edge is restored, duplicates cannot survive, and user knowledge edges are never touched. Pinned in tests/integration/vfs-containment-batched.test.ts: exact removed/restored counts on a seeded defect tree, tree correctness after the repair, user edges untouched, and the cost shape — related() call count independent of the entity count. --- src/vfs/VirtualFileSystem.ts | 27 +++- .../vfs-containment-batched.test.ts | 115 ++++++++++++++++++ 2 files changed, 141 insertions(+), 1 deletion(-) create mode 100644 tests/integration/vfs-containment-batched.test.ts diff --git a/src/vfs/VirtualFileSystem.ts b/src/vfs/VirtualFileSystem.ts index 46c6a12d..1a4b9fa5 100644 --- a/src/vfs/VirtualFileSystem.ts +++ b/src/vfs/VirtualFileSystem.ts @@ -2295,6 +2295,31 @@ export class VirtualFileSystem implements IVirtualFileSystem { cursor = page.nextCursor } + // Pass 2: ONE paged walk over every Contains edge, grouped by target in + // memory. The earlier shape issued one awaited related({ to }) per VFS + // entity — O(entities) serialized graph calls, measured in whole minutes + // on large brains. This shape is O(edges / page) calls regardless of how + // many entities exist; mutations alone stay per-defect. + const incomingByTarget = new Map[]>() + { + const pageSize = 1000 + let pageOffset = 0 + for (;;) { + const page = await this.brain.related({ + type: VerbType.Contains, + limit: pageSize, + offset: pageOffset + }) + for (const edge of page) { + const bucket = incomingByTarget.get(edge.to) + if (bucket) bucket.push(edge) + else incomingByTarget.set(edge.to, [edge]) + } + if (page.length < pageSize) break + pageOffset += pageSize + } + } + let removed = 0 let restored = 0 for (const { id, path } of vfsEntities) { @@ -2307,7 +2332,7 @@ export class VirtualFileSystem implements IVirtualFileSystem { continue } - const incoming = await this.brain.related({ to: id, type: VerbType.Contains }) + const incoming = incomingByTarget.get(id) ?? [] let expectedSeen = false for (const edge of incoming) { const isVfsEdge = edge.subtype === 'vfs-contains' || (edge.metadata as any)?.isVFS === true diff --git a/tests/integration/vfs-containment-batched.test.ts b/tests/integration/vfs-containment-batched.test.ts new file mode 100644 index 00000000..0a7919bf --- /dev/null +++ b/tests/integration/vfs-containment-batched.test.ts @@ -0,0 +1,115 @@ +/** + * @module tests/integration/vfs-containment-batched + * @description repairContainment costs O(edges/page) graph calls, not O(entities) (10.4.9 train). + * + * Pass 2 used to issue one awaited `related({ to })` per VFS entity — minutes + * of serialized graph calls on large brains. Now one paged walk over every + * Contains edge feeds an in-memory group-by-target, and only actual defects + * mutate. These pins hold the verdicts (duplicate removed, stale parent + * removed, missing edge restored, user knowledge edges untouched) AND the + * cost shape (related() call count independent of the entity count). + */ +import { describe, it, expect, beforeAll, afterAll, vi } from 'vitest' +import { Brainy } from '../../src/brainy' +import { NounType, VerbType } from '../../src/types/graphTypes' + +const FILES = 60 + +describe('repairContainment: batched pass 2', () => { + let brain: Brainy + let result: { removed: number; restored: number } + let relatedCalls = 0 + + beforeAll(async () => { + brain = new Brainy({ requireSubtype: false, storage: { type: 'memory' } }) + await brain.init() + const vfs = (brain as any).vfs ?? (brain as any)._vfs + expect(vfs).toBeTruthy() + await vfs.init() + + // A directory and FILES entries under it, wired as real VFS rows. + const mkNode = async (id: string, path: string, vfsType: string): Promise => { + await brain.add({ + id, + data: `vfs node ${path}`, + type: NounType.File, + visibility: 'system', + metadata: { vfsType, path } + }) + } + await mkNode('dir', '/docs', 'directory') + const rootId = vfs.rootEntityId ?? (await vfs.initializeRoot?.()) + if (rootId) { + await brain.relate({ + from: rootId, + to: 'dir', + type: VerbType.Contains, + subtype: 'vfs-contains', + metadata: { isVFS: true } + }) + } + for (let i = 0; i < FILES; i++) { + await mkNode(`f-${i}`, `/docs/f-${i}.md`, 'file') + if (i === 0) continue // f-0: MISSING edge — must be restored + await brain.relate({ + from: 'dir', + to: `f-${i}`, + type: VerbType.Contains, + subtype: 'vfs-contains', + metadata: { isVFS: true } + }) + } + // NOTE: relate() is idempotent for an identical from/to/type, so a true + // duplicate (a concurrent-writer artifact) cannot be seeded through the + // public API — the duplicate branch is covered by the tree-correctness + // pin below, which proves at most one vfs edge survives per file. + // f-2: STALE parent edge (from a sibling file) — must be removed. + await brain.relate({ + from: 'f-3', + to: 'f-2', + type: VerbType.Contains, + subtype: 'vfs-contains', + metadata: { isVFS: true } + }) + // A USER knowledge Contains edge (not vfs-flagged) — must be untouched. + await brain.relate({ from: 'f-4', to: 'f-5', type: VerbType.Contains }) + + const spy = vi.spyOn(brain, 'related') + result = await vfs.repairContainment() + relatedCalls = spy.mock.calls.length + spy.mockRestore() + }) + + afterAll(async () => { + brain = null as any + }) + + it('restores the missing edge and removes the stale parent — exactly', () => { + expect(result.restored).toBe(1) // f-0's missing edge + expect(result.removed).toBe(1) // f-2's stale parent (f-3 → f-2) + }) + + it('the repaired tree is correct: every file has exactly one vfs edge from its dir', async () => { + for (let i = 0; i < 6; i++) { + const incoming = await brain.related({ to: `f-${i}`, type: VerbType.Contains }) + const vfsEdges = incoming.filter( + (e) => e.subtype === 'vfs-contains' || (e.metadata as any)?.isVFS === true + ) + expect(vfsEdges, `f-${i}`).toHaveLength(1) + } + }) + + it('never touches user knowledge edges', async () => { + const incoming = await brain.related({ to: 'f-5', type: VerbType.Contains }) + const user = incoming.filter( + (e) => e.subtype !== 'vfs-contains' && (e.metadata as any)?.isVFS !== true + ) + expect(user).toHaveLength(1) + }) + + it('cost shape: related() calls do not scale with the entity count', () => { + // One paged type-only walk (~E/1000 pages) — with 60+ entities the old + // shape issued 60+ calls; the new one a handful. Bound generously. + expect(relatedCalls).toBeLessThanOrEqual(5) + }) +})