brainy/tests/unit/storage/getVerbs-totalCount.test.ts
David Snelling 3a3aa43b3a fix(8.0): VFS path-cache instance-scoping + verb totalCount page-cap
Two independent restart/multi-instance correctness bugs.

VFS path cache (A.3): PathResolver keyed the PROCESS-GLOBAL path cache on
`vfs:path:<path>` with no instance scope. Multiple Brainys per process (a
supported pattern) over DIFFERENT storage collided — instance A's `/x → id_A`
satisfied instance B's `stat('/x')` against unrelated storage → stale id →
"Entity not found" (surfaced by metadata-only-comprehensive's VFS stat() once
another VFS test had seeded the cache). Scope every `vfs:path:` key by a
monotonic per-process instance token (the VFS root id is a fixed sentinel, so it
can't disambiguate; the global cache is itself process-scoped so the token
suffices). Scoped the invalidate/prefix-delete paths too, so a branch-switch /
clear / fork on one brain no longer wipes another brain's cache. Cross-instance
sharing was only an optimization and was the collision itself; each instance
keeps its own local pathCache.

Verb totalCount (verb mirror of b2005ff): getVerbsWithPagination returned
`collectedVerbs.length` (the peeked page size, bounded by offset+limit+1) as
totalCount, so getVerbs({limit:1}).totalCount read 1/2 for any non-empty brain
on the unfiltered path. Now returns the authoritative O(1) totalVerbCount
(isNew-gated, visibility-filtered, rehydrated on init); filtered scans keep the
collected length. Regression: tests/unit/storage/getVerbs-totalCount.test.ts
(warm + cold reopen, mirrors the noun test).

metadata-only-comprehensive + vfs-api-wiring integration green; 1471 unit green.
2026-06-19 11:45:13 -07:00

94 lines
3.4 KiB
TypeScript

/**
* @module tests/unit/storage/getVerbs-totalCount
* @description Verb-side mirror of getNouns-totalCount (the b2005ff fix). The
* verb type-scan pagination (`BaseStorage.getVerbsWithPagination`) early-terminates
* at `offset + limit + 1` (it peeks one extra item to compute `hasMore`), then
* returned `collectedVerbs.length` as `totalCount` — i.e. the PAGE size, never the
* dataset total. So `getVerbs({ pagination: { limit: 1 } }).totalCount` reported 1
* (or 2) for any non-empty brain on the unfiltered path. `totalCount` must be the
* authoritative O(1) verb counter (`totalVerbCount`, isNew-gated +
* visibility-filtered, rehydrated from `counts.json` on init).
*/
import { describe, it, expect } from 'vitest'
import { mkdtempSync, rmSync } from 'fs'
import { tmpdir } from 'os'
import { join } from 'path'
import { FileSystemStorage } from '../../../src/storage/adapters/fileSystemStorage.js'
import type { VerbMetadata, HNSWVerb } from '../../../src/coreTypes.js'
import { VerbType } from '../../../src/types/graphTypes.js'
const DIM = 8
/** A deterministic non-zero vector so the verb record is well-formed. */
function vec(seed: number): number[] {
return Array.from({ length: DIM }, (_, i) => ((seed + i) % 7) / 7 - 0.5)
}
/** Storage shards by UUID, so ids must be 32 hex chars. */
function uuid(i: number, salt = 0): string {
return (i + salt).toString(16).padStart(32, '0')
}
/**
* Seed `n` unique verbs. Metadata is saved BEFORE the verb record: it is where
* the O(1) verb counter is incremented (isNew-gated), mirroring the real
* relate() path.
*/
async function seed(storage: FileSystemStorage, n: number): Promise<void> {
for (let i = 0; i < n; i++) {
const id = uuid(i, 0x1000)
const sourceId = uuid(i, 0x4000)
const targetId = uuid(i, 0x8000)
await storage.saveVerbMetadata(id, {
verb: VerbType.RelatedTo,
sourceId,
targetId,
createdAt: Date.now(),
updatedAt: Date.now()
} as VerbMetadata)
await storage.saveVerb({
id,
vector: vec(i),
connections: new Map(),
verb: VerbType.RelatedTo,
sourceId,
targetId
} as HNSWVerb)
}
}
describe('FileSystemStorage.getVerbs totalCount (verb mirror of b2005ff)', () => {
it('reports the TRUE total, not the page size, for a 1-item page', async () => {
const dir = mkdtempSync(join(tmpdir(), 'brainy-verbtotal-'))
try {
const storage = new FileSystemStorage(dir)
await storage.init()
await seed(storage, 25)
const onePage = await storage.getVerbs({ pagination: { limit: 1 } })
expect(onePage.items.length).toBe(1)
// Before the fix this was the peeked page size (limit + 1 == 2).
expect(onePage.totalCount).toBe(25)
expect(onePage.hasMore).toBe(true)
} finally {
rmSync(dir, { recursive: true, force: true })
}
})
it('still reports the true total after a cold reopen (counts rehydrate)', async () => {
const dir = mkdtempSync(join(tmpdir(), 'brainy-verbtotal-reopen-'))
try {
const first = new FileSystemStorage(dir)
await first.init()
await seed(first, 25)
await (first as unknown as { persistCounts(): Promise<void> }).persistCounts()
const reopened = new FileSystemStorage(dir)
await reopened.init()
const page = await reopened.getVerbs({ pagination: { limit: 1 } })
expect(page.totalCount).toBe(25)
} finally {
rmSync(dir, { recursive: true, force: true })
}
})
})