fix: surface segment/entity read faults loudly instead of masking as absent

ColumnStore silently skipped a manifest-listed segment it could not load: a
corrupt or missing segment dropped every one of its entities out of
filter/rangeQuery/sortTopK with no error, so an inconsistent index read as a
merely short result. It now throws a typed ColumnSegmentLoadError when a listed
segment yields undecodable or no bytes, and lets a genuine storage IO fault
propagate verbatim. Only a field with no manifest at all stays benign (nothing
was ever written for it).

baseStorage.getNoun_internal / getVerb_internal likewise caught every error and
returned null, reporting a present-but-unreadable entity as "not found". They
now return null only for genuine ENOENT-class absence (via isAbsentError) and
rethrow real faults and deserialize errors.

Pattern-B (blind catch) hardening: absence -> null, fault -> loud.
This commit is contained in:
David Snelling 2026-07-13 09:14:52 -07:00
parent 119087a75c
commit af5d2f389b
3 changed files with 216 additions and 33 deletions

View file

@ -29,6 +29,7 @@ import { getShardId } from './sharding.js'
import { BlobStorage, type BlobStoreAdapter } from './blobStorage.js'
import { unwrapBinaryData } from './binaryDataCodec.js'
import { prodLog } from '../utils/logger.js'
import { isAbsentError } from '../utils/errorClassification.js'
import { BrainyError } from '../errors/brainyError.js'
import { MetadataWriteBuffer } from '../utils/metadataWriteBuffer.js'
import {
@ -4064,8 +4065,12 @@ export abstract class BaseStorage extends BaseStorageAdapter {
return this.deserializeNoun(noun)
}
} catch (error) {
// Entity not found
return null
// A real storage/deserialize fault is NOT "entity not found":
// readCanonicalObject returns null for genuine absence and only throws on
// a real fault, so masking it here reports a present-but-unreadable entity
// as missing. Absence → null, fault → propagate loudly.
if (isAbsentError(error)) return null
throw error
}
return null
@ -4175,8 +4180,12 @@ export abstract class BaseStorage extends BaseStorageAdapter {
return this.deserializeVerb(verb)
}
} catch (error) {
// Entity not found
return null
// A real storage/deserialize fault is NOT "relationship not found":
// readCanonicalObject returns null for genuine absence and only throws on
// a real fault, so masking it here reports a present-but-unreadable edge
// as missing. Absence → null, fault → propagate loudly.
if (isAbsentError(error)) return null
throw error
}
return null