fix(8.0): getNouns().totalCount reports true total, not page size (port of 7.32.1)

getNounsWithPagination returned collectedNouns.length as totalCount, but the
type-first shard scan early-terminates at offset+limit+1 (peeks one for hasMore)
— so getNouns({ pagination: { limit: 1 } }).totalCount was the page size for any
non-empty brain. The index-rebuild gate calls exactly that, so cold starts
mis-logged "Small dataset (N items)". Now reports the authoritative O(1) noun
counter (rehydrated on init) as the unfiltered total; 8.0's peek-based hasMore is
already correct and unchanged. Filtered scans unchanged.

Regression: tests/unit/storage/getNouns-totalCount.test.ts. Full 8.0 unit suite green (1453).
This commit is contained in:
David Snelling 2026-06-17 14:10:06 -07:00
parent 5eaf579937
commit b2005ff22a
2 changed files with 103 additions and 1 deletions

View file

@ -1620,9 +1620,23 @@ export abstract class BaseStorage extends BaseStorageAdapter {
const paginatedNouns = collectedNouns.slice(offset, offset + limit)
const hasMore = collectedNouns.length > targetCount
// totalCount must be the TRUE dataset total, not the size of this (peeked)
// page. The shard scan early-terminates at `peekCount = offset + limit + 1`
// for memory efficiency, so `collectedNouns.length` only ever reaches the
// page size + 1 — returning it as `totalCount` made every non-empty brain
// look like it held ~`limit` items (e.g. `getNouns({ pagination: { limit: 1 }
// }).totalCount` was 2, tripping the index-rebuild gate's "Small dataset"
// path). For the unfiltered case the authoritative total is the O(1) counter
// maintained on every add/delete and rehydrated on init; `Math.max` guards a
// stale counter from under-reporting below what we collected. A filtered scan
// has no cheap exact total, so it keeps the collected length (a lower bound).
const totalCount = filter
? collectedNouns.length
: Math.max(this.totalNounCount, collectedNouns.length)
return {
items: paginatedNouns,
totalCount: collectedNouns.length,
totalCount,
hasMore,
nextCursor: hasMore && paginatedNouns.length > 0
? paginatedNouns[paginatedNouns.length - 1].id