fix(adoption): the baseline backfill cures hydration-law drift — existing brains reach the crash-safe default with zero operator steps
Some checks failed
CI / Node 22 (push) Successful in 12m17s
CI / Node 24 (push) Successful in 12m8s
CI / Bun (latest) (push) Failing after 5m41s

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:
David Snelling 2026-08-12 11:48:17 -07:00
parent 2abe8b3806
commit 25f0dd964e
3 changed files with 134 additions and 19 deletions

View file

@ -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()