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
107
tests/integration/adopt-drift-cure.test.ts
Normal file
107
tests/integration/adopt-drift-cure.test.ts
Normal file
|
|
@ -0,0 +1,107 @@
|
|||
/**
|
||||
* @module tests/integration/adopt-drift-cure
|
||||
* @description THE DRIFT-CURING BACKFILL — the actual completion of the
|
||||
* default-flip ruling: existing brains whose canonical wrappers carry
|
||||
* pre-hydration-law drift (denormalized fields disagreeing with their own
|
||||
* metadata leg — the real depot-brain shape, uuid-v7 rows from the 9.0 era)
|
||||
* must ADOPT AUTOMATICALLY: the backfill rewrites canonical in the law
|
||||
* shape (metadata leg = the authority; floats preserved), the oracle then
|
||||
* verifies the rewrite before flipping. Same safety, zero operator chores.
|
||||
* Log-ahead divergences still refuse as before.
|
||||
*/
|
||||
import { describe, it, expect, afterEach } from 'vitest'
|
||||
import { mkdtempSync, rmSync } from 'node:fs'
|
||||
import { tmpdir } from 'node:os'
|
||||
import { join } from 'node:path'
|
||||
import { Brainy } from '../../src/index.js'
|
||||
import { NounType } from '../../src/types/graphTypes.js'
|
||||
|
||||
const dirs: string[] = []
|
||||
const brains: Brainy[] = []
|
||||
afterEach(async () => {
|
||||
for (const b of brains.splice(0)) await b.close().catch(() => {})
|
||||
for (const d of dirs.splice(0)) rmSync(d, { recursive: true, force: true })
|
||||
})
|
||||
|
||||
type RawBox = {
|
||||
storage: {
|
||||
readNounRaw(id: string): Promise<{ metadata: unknown; vector: unknown }>
|
||||
writeNounRaw(id: string, r: { metadata: unknown; vector: unknown }): Promise<void>
|
||||
}
|
||||
}
|
||||
|
||||
describe('adoption cures hydration-law drift automatically', () => {
|
||||
it('a drifted wrapper (stale denormalized fields) adopts green with floats preserved', async () => {
|
||||
const dir = mkdtempSync(join(tmpdir(), 'brainy-drift-cure-'))
|
||||
dirs.push(dir)
|
||||
const brain = new Brainy({
|
||||
storage: { type: 'filesystem', path: dir },
|
||||
requireSubtype: false,
|
||||
logAuthority: 'defer'
|
||||
})
|
||||
await brain.init()
|
||||
brains.push(brain)
|
||||
const id = await brain.add({
|
||||
data: 'early-era row with drift',
|
||||
type: NounType.Document,
|
||||
metadata: { k: 1 }
|
||||
})
|
||||
await brain.flush()
|
||||
const before = await brain.get(id, { includeVectors: true })
|
||||
const floats = [...(before!.vector as number[])]
|
||||
expect(floats.length).toBeGreaterThan(0)
|
||||
|
||||
// Manufacture the depot shape: the stored wrapper's denormalized fields
|
||||
// disagree with the metadata leg (pre-hydration-law drift) — an as-is
|
||||
// identity re-commit preserves this forever; the law-shape rewrite cures it.
|
||||
const storage = (brain as unknown as RawBox).storage
|
||||
const raw = await storage.readNounRaw(id)
|
||||
const wrapper = raw.vector as Record<string, unknown>
|
||||
await storage.writeNounRaw(id, {
|
||||
metadata: raw.metadata,
|
||||
vector: {
|
||||
...wrapper,
|
||||
noun: 'thing', // stale denormalized type (metadata leg says document)
|
||||
legacyField: 'pre-law residue',
|
||||
createdAt: '1999-01-01T00:00:00.000Z'
|
||||
}
|
||||
})
|
||||
// Confirm the drift is oracle-visible before the cure.
|
||||
expect((await brain.verifyLogAuthority()).verdict, 'drift detected').toBe('red')
|
||||
|
||||
// THE PIN: adoption cures it without any operator step.
|
||||
const report = await brain.adoptLogAuthority()
|
||||
expect(report.verdict).toBe('green')
|
||||
expect(brain.logAuthority().authority).toBe('log')
|
||||
|
||||
// Nothing degraded: floats byte-identical, metadata intact, row serves.
|
||||
const after = await brain.get(id, { includeVectors: true })
|
||||
expect(after!.vector as number[], 'floats preserved through the cure').toEqual(floats)
|
||||
expect((after!.metadata as { k: number }).k).toBe(1)
|
||||
expect((await brain.find({ where: { k: 1 }, limit: 5 })).map((r) => r.id)).toContain(id)
|
||||
}, 120000)
|
||||
|
||||
it('log-ahead divergences still refuse — the backfill never papers over a log the witness denies', async () => {
|
||||
const dir = mkdtempSync(join(tmpdir(), 'brainy-logahead-'))
|
||||
dirs.push(dir)
|
||||
const brain = new Brainy({
|
||||
storage: { type: 'filesystem', path: dir },
|
||||
requireSubtype: false,
|
||||
logAuthority: 'defer'
|
||||
})
|
||||
await brain.init()
|
||||
brains.push(brain)
|
||||
const id = await brain.add({ data: 'row', type: NounType.Document, metadata: { k: 1 } })
|
||||
await brain.flush()
|
||||
|
||||
// Log-ahead shape: canonical loses the record while the log still
|
||||
// claims it live (log-live-canonical-absent — NOT curable by baseline).
|
||||
const storage = (brain as unknown as RawBox).storage
|
||||
await storage.writeNounRaw(id, { metadata: null, vector: null })
|
||||
|
||||
await expect(brain.adoptLogAuthority()).rejects.toThrow(
|
||||
/log-ahead|witness denies|log claims/i
|
||||
)
|
||||
expect(brain.logAuthority().authority).toBe('tree')
|
||||
}, 120000)
|
||||
})
|
||||
Loading…
Add table
Add a link
Reference in a new issue