/** * @module tests/unit/utils/metadataIndex-sort-callshape * @description THE ASYMPTOTIC CALL-SHAPE PIN for ordered reads * (BRAINY-PROD-LATENCY-TRIAD, David-approved plan Track A1). The defect it * keeps dead: `getSortedIdsForFilter`'s value resolution did a SERIAL * `storage.getNoun()` (the heavyweight VECTOR record) per filtered row — * 62–98ms × 3,224 rows = the measured 199–317 SECOND production sort, with * `topK` applied only after the full scan. These pins assert the SHAPE of * the storage traffic, not wall-clock (latency-blind, so they hold on any * machine): an ordered read performs ZERO per-row vector-record reads and * resolves sort values through BATCHED metadata-record calls only. */ import { describe, it, expect, beforeAll, afterAll, vi } from 'vitest' import { Brainy } from '../../../src/index.js' import { NounType } from '../../../src/types/graphTypes.js' const ROWS = 60 describe('ordered reads — the batched call-shape law (no per-row storage loops)', () => { let brain: Brainy let storage: { getNoun: (id: string) => Promise getNounMetadata: (id: string) => Promise getNounMetadataBatch: (ids: string[]) => Promise> } beforeAll(async () => { brain = new Brainy({ storage: { type: 'memory' }, requireSubtype: false }) await brain.init() for (let i = 0; i < ROWS; i++) { await brain.add({ data: `row ${i}`, type: NounType.Document, metadata: { rank: (i * 7) % ROWS, plain: `p${i}` } }) } storage = (brain as unknown as { storage: typeof storage }).storage }, 120000) afterAll(async () => { await brain.close().catch(() => {}) }) it('user-field orderBy: zero vector-record reads, zero serial metadata reads — batch calls only', async () => { const getNounSpy = vi.spyOn(storage, 'getNoun') const singleReadSpy = vi.spyOn(storage, 'getNounMetadata') const batchSpy = vi.spyOn(storage, 'getNounMetadataBatch') const rows = await brain.find({ type: NounType.Document, orderBy: 'rank', order: 'desc', limit: 10 }) expect(rows.length).toBe(10) expect((rows[0].metadata as Record).rank).toBe(ROWS - 1) // THE PIN: the sort's value resolution never opens a vector record and // never falls into a per-row metadata loop. (Result hydration after // pagination is allowed to read; the SORT itself must be batch-only — // hence the ceiling: strictly fewer single reads than sorted rows.) expect(getNounSpy.mock.calls.length, 'per-row vector-record reads in an ordered read').toBe(0) expect(batchSpy.mock.calls.length, 'the batch door was used').toBeGreaterThanOrEqual(1) expect( singleReadSpy.mock.calls.length, 'serial per-row metadata reads (the 199s shape)' ).toBeLessThan(ROWS / 2) vi.restoreAllMocks() }) it('system.createdAt orderBy: exact values from batched records — the bucketed index is never a per-row disk excuse', async () => { const getNounSpy = vi.spyOn(storage, 'getNoun') const batchSpy = vi.spyOn(storage, 'getNounMetadataBatch') const rows = await brain.find({ type: NounType.Document, orderBy: 'system.createdAt', order: 'asc', limit: 15 }) expect(rows.length).toBe(15) expect(getNounSpy.mock.calls.length, 'per-row vector-record reads').toBe(0) expect(batchSpy.mock.calls.length).toBeGreaterThanOrEqual(1) // Exactness: ascending createdAt must be non-decreasing with full // millisecond precision (the old path sorted minute-BUCKETED values or // paid a per-row disk read for exact ones — both are dead). Find results // carry the timestamps on the nested full entity. const stamps = rows.map( (r) => ((r as unknown as { entity?: { createdAt?: number } }).entity?.createdAt ?? (r as unknown as { createdAt?: number }).createdAt) as number ) for (let i = 1; i < stamps.length; i++) { expect(stamps[i]).toBeGreaterThanOrEqual(stamps[i - 1]) } vi.restoreAllMocks() }) it('the ordering contract survives the batch path: missing values LAST both directions, ties by id asc, rows never dropped', async () => { // Three rows lack `rank`? No — all carry it; add two rows WITHOUT it. const a = await brain.add({ data: 'no-rank a', type: NounType.Document, metadata: { plain: 'x' } }) const b = await brain.add({ data: 'no-rank b', type: NounType.Document, metadata: { plain: 'y' } }) for (const order of ['asc', 'desc'] as const) { const rows = await brain.find({ type: NounType.Document, orderBy: 'rank', order, limit: ROWS + 10 }) expect(rows.length, `complete result (${order})`).toBe(ROWS + 2) const lastTwo = rows.slice(-2).map((r) => r.id).sort() expect(lastTwo, `missing-value rows sort LAST (${order})`).toEqual([a, b].sort()) } }) })