fix(8.0): real bugs surfaced by integration hardening — where-intersect, related() offset, relate() updatedAt

Three of the five bugs the rot pass surfaced (correct inputs → wrong output), fixed:

1. where-filter dropped all but the last operator on a field. getIdsForFilter()'s
   per-operator loop overwrote fieldResults each iteration, so
   { year: { greaterThan: 2009, lessThan: 2020 } } kept only lessThan. Now each
   operator's match set is AND-intersected (multi-operator-per-field works).
   (metadataIndex.ts)
2. related({ offset }) ignored the offset — getVerbs() zeroed it and smuggled it via
   an unimplemented cursor, so every page returned items [0, limit). Now the real
   offset is passed through to getVerbsWithPagination (which slices [offset,+limit)).
   (baseStorage.ts) — verified: get-relations-fix pagination test passes.
3. relate() never persisted updatedAt, so reads fabricated a fresh Date.now() each
   call (non-idempotent). Now createdAt and updatedAt share one timestamp at create.
   (brainy.ts) — verified: get-relations-fix equivalence test passes.

Follow-ups (precisely diagnosed, not papered over): exclusive range bounds —
ColumnStore.rangeQuery(field,min,max) is inclusive-only, so getIdsForRange() drops
includeMin/includeMax (metadataIndex.ts:996) and lessThan/greaterThan behave as
lte/gte when the column store is active (the dual-bound test's popularity:95 boundary
stays red); counts not rehydrating after restart; unscoped VFS path-cache.
This commit is contained in:
David Snelling 2026-06-17 13:19:54 -07:00
parent e5997a1516
commit 5eaf579937
3 changed files with 26 additions and 13 deletions

View file

@ -2043,15 +2043,11 @@ export abstract class BaseStorage extends BaseStorageAdapter {
// Check if the adapter has a paginated method for getting verbs
if (typeof this.getVerbsWithPagination === 'function') {
// Use the adapter's paginated method
// Convert offset to cursor if no cursor provided (adapters use cursor for offset)
const effectiveCursor = cursor || (offset > 0 ? offset.toString() : undefined)
// offset is carried via effectiveCursor for adapters with cursor-based
// pagination; 0 here matches the long-standing destructure default in
// getVerbsWithPagination (this call never passed offset). The
// annotation widens totalCount to optional: adapter overrides follow
// BaseStorageAdapter's contract, where totalCount may be absent.
// getVerbsWithPagination honors `offset` directly (it slices the
// [offset, offset+limit) window; `cursor` is not yet implemented there).
// Pass the real offset through. Previously offset was zeroed and smuggled
// via an ignored `cursor`, so every page returned items [0, limit) and
// offset was silently dropped (related({ offset }) paginated incorrectly).
const result: {
items: HNSWVerbWithMetadata[]
totalCount?: number
@ -2059,12 +2055,11 @@ export abstract class BaseStorage extends BaseStorageAdapter {
nextCursor?: string
} = await this.getVerbsWithPagination({
limit,
offset: 0,
cursor: effectiveCursor,
offset,
cursor,
filter: options?.filter
})
// Items are already offset by the adapter via cursor, no need to slice
const items = result.items
// CRITICAL SAFETY CHECK: Prevent infinite loops