fix(adoption): the baseline backfill cures hydration-law drift — existing brains reach the crash-safe default with zero operator steps
The last rung of the default-flip ruling: with the sentinel exemption in, real production-shaped brains still refused adoption over state-differs mismatches the backfill could not cure — rows written before the hydration law carry denormalized wrapper fields that disagree with their own metadata leg, and the previous as-is identity re-commit PRESERVED that drift, so the oracle re-flagged it every pass and the flip never happened. In practice the crash-safe default reached zero existing brains: the exact outcome the hold ruling forbade. The cure: the backfill now rewrites canonical in the LAW SHAPE — exactly the wrapper the log's reconstruction produces (denormalized enumeration fields derived from the metadata leg, which is their authority under the field-addressing law; the embedding floats ride through byte-identical; adjacency residue keeps its own rebuild path). The oracle then verifies the rewrite before the flip — the same safety, no operator chore. Log-ahead divergence classes (a log the witness denies) still refuse loudly, exactly as before. Classification note for the record: the flagged uuid-v7 rows postdate the fact log's introduction, so they classify as state-differs (in-log, drift-shaped) rather than pre-log — both classes ride the same backfill. Pins: a manufactured depot-shape drifted wrapper adopts green with floats preserved and metadata intact; log-ahead still refuses typed. Gates: unit 2065/2065 · integration 832 · conformance 31/31.
This commit is contained in:
parent
2abe8b3806
commit
25f0dd964e
3 changed files with 134 additions and 19 deletions
|
|
@ -196,6 +196,7 @@ import { isDeterministicEmbedMode } from './embeddings/deterministicEmbedMode.js
|
|||
import { GenerationConflictError, StoreInconsistentError } from './db/errors.js'
|
||||
import { BrainyError, GraphIndexNotReadyError, MetadataIndexNotReadyError, MigrationInProgressError, VectorIndexNotReadyError } from './errors/brainyError.js'
|
||||
import { assessIndexReadiness } from './utils/indexReadiness.js'
|
||||
import { reconstructNounWrapper } from './db/factLog.js'
|
||||
import {
|
||||
readLogAuthority,
|
||||
runLogCompletenessOracle,
|
||||
|
|
@ -8274,15 +8275,17 @@ export class Brainy<T = any> implements BrainyInterface<T> {
|
|||
for (const m of curable) {
|
||||
const raw = await this.storage.readNounRaw(m.id)
|
||||
if (raw.metadata === null && raw.vector === null) continue // vanished since the scan
|
||||
// IDENTITY re-commit: preserve the stored vector-file wrapper AS-IS —
|
||||
// the denormalized enumeration fields and the embedding floats ride
|
||||
// through, because a backfill must never DEGRADE the row it cures
|
||||
// (a skeleton rewrite would drop the row's floats and its enumerable
|
||||
// fields, and a later log replay could only reproduce the metadata
|
||||
// leg's hydration). The wrapper's floats sit nested under `vector`
|
||||
// (canonical noun vector files hold the denormalized noun, not a
|
||||
// bare array); adjacency legs stay in SaveNounOperation's
|
||||
// placeholder shape (the vector index owns them).
|
||||
// LAW-SHAPE RE-COMMIT: rewrite canonical as EXACTLY the wrapper the
|
||||
// log's reconstruction produces (the hydration law: denormalized
|
||||
// enumeration fields derived from the metadata leg + the embedding
|
||||
// floats). This is what makes the backfill actually CURE
|
||||
// state-differs drift: rows written before the hydration law carry
|
||||
// denormalized copies that disagree with their own metadata leg, and
|
||||
// an as-is identity re-commit preserves that drift forever — the
|
||||
// oracle re-flags it every pass and existing brains never flip. The
|
||||
// metadata leg is the authority (denormalized fields are its
|
||||
// projections, per the field-addressing law); nothing degrades: the
|
||||
// floats ride through, adjacency residue has its own rebuild path.
|
||||
const wrapper =
|
||||
raw.vector !== null && typeof raw.vector === 'object' && !Array.isArray(raw.vector)
|
||||
? (raw.vector as Record<string, unknown>)
|
||||
|
|
@ -8292,16 +8295,21 @@ export class Brainy<T = any> implements BrainyInterface<T> {
|
|||
: Array.isArray(wrapper?.vector)
|
||||
? (wrapper!.vector as number[])
|
||||
: []
|
||||
const lawWrapper = reconstructNounWrapper(m.id, raw.metadata, vector)
|
||||
const priorRaw = { metadata: raw.metadata, vector: raw.vector }
|
||||
await this.persistSingleOp({ nouns: [m.id] }, async (tx) => {
|
||||
tx.addOperation(
|
||||
new SaveNounOperation(this.storage, {
|
||||
...(wrapper ?? {}),
|
||||
id: m.id,
|
||||
vector,
|
||||
connections: new Map(),
|
||||
level: typeof wrapper?.level === 'number' ? (wrapper.level as number) : 0
|
||||
} as HNSWNoun)
|
||||
)
|
||||
tx.addOperation({
|
||||
name: 'BaselineLawShapeRewrite',
|
||||
execute: async () => {
|
||||
await this.storage.writeNounRaw(m.id, {
|
||||
metadata: raw.metadata,
|
||||
vector: lawWrapper
|
||||
})
|
||||
return async () => {
|
||||
await this.storage.writeNounRaw(m.id, priorRaw)
|
||||
}
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
const next = await this.verifyLogAuthority()
|
||||
|
|
|
|||
Reference in a new issue