fix: restore loadBinaryBlob fault-propagation (native column-store lockstep)

The Pass-1 hardening that makes loadBinaryBlob distinguish genuine absence
(ENOENT -> null) from a real fault (EIO/EACCES/... -> throw) was held out of
8.2.6 because the native accelerator's two column-store read sites still relied
on null-on-error. That accelerator release has now hardened those sites to
handle the throw (a faulted segment read marks the field unavailable and throws
a named error), so the fault-propagating form is restored and ships lockstep.

A present-but-unreadable index blob no longer masquerades as absent (which drove
needless rebuilds / empty reads). Adds the loadBinaryBlob leg to the blob
durability suite (absent -> null; present -> bytes; real fault -> throws).
This commit is contained in:
David Snelling 2026-07-13 12:09:55 -07:00
parent 76843b782e
commit b6c7039769
3 changed files with 65 additions and 19 deletions

View file

@ -17,6 +17,7 @@ import {
WriterLockInfo
} from '../baseStorage.js'
import { getBrainyVersion } from '../../utils/index.js'
import { isAbsentError } from '../../utils/errorClassification.js'
// Node.js modules - dynamically imported to avoid issues in browser environments
let fs: any
@ -1228,25 +1229,17 @@ export class FileSystemStorage extends BaseStorage {
await this.ensureInitialized()
try {
return await fs.promises.readFile(this.blobPath(key))
} catch {
// COORDINATION HOLD — restore the fault-propagating form lockstep with
// cortex 3.0.13 (CORTEX-BILLION-SCALE-SPINE). The correct behavior
// distinguishes genuine absence from a real fault so a present-but-
// unreadable blob no longer masquerades as "absent" (which drove needless
// rebuilds / empty reads). Restore to:
// } catch (err) {
// // re-add: import { isAbsentError } from '../../utils/errorClassification.js'
// if (isAbsentError(err)) return null
// throw err
// }
// The throw is HELD because the native column-store still has 2 call sites
// that rely on null-on-error; cortex hardens them in 3.0.13, then this
// reverts to the throwing form and ships lockstep. Until then this
// preserves the shipped 8.2.5 swallow-on-fault behavior, so a consumer on
// new-brainy + old-cortex can never break. All other Pass-1 hardening
// (saveBinaryBlob honesty, clear(), pending-flush durability, count
// symmetry, degraded surfacing, aggregation) ships now, independent of this.
return null
} catch (err) {
// Absent blob → null (the documented contract). A real fault
// (EIO/EACCES/EMFILE/…) must NOT be masked as "absent": doing so makes a
// present-but-unreadable blob look missing and drives a needless rebuild
// or an empty read (the native provider consumes this). Mandate: loud
// errors, never quiet losses. Fault-propagation restored lockstep with
// cortex 3.0.13, whose two column-store call sites now handle the throw
// (they mark the field unavailable + throw a named error) instead of
// relying on null-on-error (CORTEX-BILLION-SCALE-SPINE).
if (isAbsentError(err)) return null
throw err
}
}