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:
parent
79f58cb87b
commit
70263113ad
9 changed files with 895 additions and 144 deletions
|
|
@ -302,6 +302,20 @@ export class Brainy<T = any> implements BrainyInterface<T> {
|
|||
return !this.operationalMode.canWrite
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether the active storage adapter implements a given optional method.
|
||||
*
|
||||
* Brainy ships new storage-adapter capabilities over time (multi-process
|
||||
* locking, cross-process flush RPC, etc.). Older plugins (notably
|
||||
* `@soulcraft/cortex` ≤2.2.0) bundle their own `BaseStorage` from an
|
||||
* earlier Brainy version that doesn't have those methods. Rather than crash
|
||||
* at runtime, every call site that uses an optional storage method goes
|
||||
* through this helper and degrades gracefully when the method is absent.
|
||||
*/
|
||||
private hasStorageMethod(name: string): boolean {
|
||||
return !!this.storage && typeof (this.storage as unknown as Record<string, unknown>)[name] === 'function'
|
||||
}
|
||||
|
||||
/**
|
||||
* Throw if this instance cannot perform writes. Called at the top of every
|
||||
* mutation method. The check is cheap (a property lookup + boolean test) and
|
||||
|
|
@ -385,24 +399,41 @@ export class Brainy<T = any> implements BrainyInterface<T> {
|
|||
// Acquire the writer lock for filesystem (and other locking-capable) backends.
|
||||
// Skipped in reader mode and on backends that don't support multi-process locking.
|
||||
// Throws if another live writer holds the directory (unless force: true).
|
||||
if (this.config.mode !== 'reader' && this.storage.supportsMultiProcessLocking()) {
|
||||
await this.storage.acquireWriterLock({ force: this.config.force })
|
||||
// Watch for cross-process flush requests so inspectors can see fresh
|
||||
// state on demand. Reader instances don't run a watcher (nothing to flush).
|
||||
this.storage.startFlushRequestWatcher(async () => {
|
||||
if (this.initialized) {
|
||||
await this.flush()
|
||||
//
|
||||
// Defensive call: older storage adapters (e.g. `@soulcraft/cortex@2.2.0`
|
||||
// and earlier) bundle a pre-7.21 `BaseStorage` that doesn't define these
|
||||
// methods. We feature-detect each one rather than fail boot.
|
||||
if (this.config.mode !== 'reader') {
|
||||
const canLock = this.hasStorageMethod('supportsMultiProcessLocking') &&
|
||||
(this.storage as any).supportsMultiProcessLocking()
|
||||
if (canLock && this.hasStorageMethod('acquireWriterLock')) {
|
||||
await (this.storage as any).acquireWriterLock({ force: this.config.force })
|
||||
if (this.hasStorageMethod('startFlushRequestWatcher')) {
|
||||
(this.storage as any).startFlushRequestWatcher(async () => {
|
||||
if (this.initialized) {
|
||||
await this.flush()
|
||||
}
|
||||
})
|
||||
}
|
||||
} else if (!this.config.silent) {
|
||||
// Older adapter OR a backend that doesn't enforce locking (cloud / memory).
|
||||
// Surface this so operators know the multi-process protections aren't active.
|
||||
const backendName = (this.storage.constructor as any).name || 'storage'
|
||||
if (backendName === 'MemoryStorage') {
|
||||
// Memory is single-process by construction — no warning needed.
|
||||
} else if (!this.hasStorageMethod('supportsMultiProcessLocking')) {
|
||||
console.warn(
|
||||
`[brainy] Storage adapter \`${backendName}\` predates the 7.21 ` +
|
||||
`multi-process methods. Writer locking and the flush-request RPC ` +
|
||||
`are disabled for this directory. Upgrade the plugin (e.g. ` +
|
||||
`\`@soulcraft/cortex\` ≥2.3.0) for full enforcement.`
|
||||
)
|
||||
} else {
|
||||
console.warn(
|
||||
`[brainy] Multi-process writer protection is not enforced on ${backendName}. ` +
|
||||
`See docs/concepts/multi-process.md for the model.`
|
||||
)
|
||||
}
|
||||
})
|
||||
} else if (this.config.mode !== 'reader' && !this.config.silent) {
|
||||
// Cloud / memory backends: multi-process safety not enforced. One-time
|
||||
// warning so operators know what they're getting.
|
||||
const backendName = (this.storage.constructor as any).name || 'storage'
|
||||
if (backendName !== 'MemoryStorage') {
|
||||
console.warn(
|
||||
`[brainy] Multi-process writer protection is not enforced on ${backendName}. ` +
|
||||
`See docs/concepts/multi-process.md for the model.`
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue