fix: find()/stats() correctness + Cortex compat (BR-FIND-WHERE-ZERO, BR-DEFENSIVE-INTERFACE)

Two production correctness defects fixed together so consumers can land
one upgrade.

BR-FIND-WHERE-ZERO — find() returned [] and stats() reported 0 entities
for any workspace whose data was written after the 7.20.0 column-store
refactor. Root cause: getStats() and the getIds() fallback still read
from the deleted sparse-index path. Separately, BaseStorage.getNounType()
was hardcoded to return 'thing', poisoning type-statistics.json with
every noun attributed to that bucket.

  - MetadataIndex.getStats() reads from ColumnStore + idMapper.
  - MetadataIndex.getIdsFromChunks() throws BrainyError(FIELD_NOT_INDEXED)
    when neither store has the field. getIdsForFilter() catches per
    clause, logs once, returns [].
  - BaseStorage.nounTypeByIdCache populated in saveNounMetadata_internal
    and consumed in saveNoun_internal. flushCounts() now persists the
    Uint32Array counters too, so readers see fresh per-type counts.
  - Self-heal at init: loadTypeStatistics() auto-rebuilds when the
    poisoned signature is detected. rebuildTypeCounts() is now public for
    use from `brainy inspect repair`.
  - Dead state removed: dirtyChunks, dirtySparseIndices,
    flushDirtyMetadata().

BR-DEFENSIVE-INTERFACE — 7.21.0 called supportsMultiProcessLocking()
unconditionally, crashing on older Cortex storage adapters that predate
the method. New hasStorageMethod(name) helper gates every new-method
call site. Older adapter triggers a one-line warning at init pointing
at the recommended plugin version.

Tests:
  - new tests/integration/find-where-zero.test.ts (7 cases)
  - new tests/integration/cortex-compat.test.ts (5 cases)
  - multi-process-safety.test.ts updated to enforce correct counts
  - 1329/1329 unit + 24/24 new integration tests passing
This commit is contained in:
David Snelling 2026-05-15 12:31:28 -07:00
parent 79f58cb87b
commit 70263113ad
9 changed files with 895 additions and 144 deletions

View file

@ -204,12 +204,13 @@ describe('Multi-process safety + read-only mode', () => {
})
const stats = await reader.stats()
expect(stats.mode).toBe('reader')
// NOTE: cross-instance count + type-classification drift between an
// in-process writer and a freshly-opened reader is a known issue with
// the reader-side index rebuild path. We verify shape + at-least-one-
// entity here; exact count/type fidelity is tracked as a separate fix.
expect(stats.entityCount).toBeGreaterThanOrEqual(1)
expect(Object.keys(stats.entitiesByType).length).toBeGreaterThan(0)
// Both Concept entities the writer added should be visible to the
// reader, classified correctly as 'concept' (not 'thing'). This is
// the BR-FIND-WHERE-ZERO regression test: stats() must read from the
// column store via idMapper.size, and getNounType() must use the
// write-time type cache instead of the old hardcoded 'thing'.
expect(stats.entityCount).toBeGreaterThanOrEqual(2)
expect(stats.entitiesByType.concept).toBeGreaterThanOrEqual(2)
expect(stats.writerLock).toBeDefined()
expect(stats.writerLock!.pid).toBe(process.pid)
expect(stats.version).toBeTruthy()