feat: provider access to the fact log + shared stamp verifier via internals

Three additive surfaces for native index providers, which hold only
`storage` and must never construct their own fact-log reader (the log's
open path is writer-side — it reconciles by truncating/rewriting):

- Fact-scan capability on the storage adapter (the getBinaryBlobPath
  pattern): the host brain wires a closure over its LIVE fact log at
  init; providers call storage.scanFacts()/factLogHeadGeneration()/
  factSegmentPaths() and fall back to the enumeration walk on null.
  Restore/reopen swaps the underlying log transparently.
- The family-stamp trio (readFamilyStamp/writeFamilyStamp/
  verifyFamilyStamp + types) exported from @soulcraft/brainy/internals,
  so 'one verifier' is literally one function shared with the native
  side, never two synchronized copies.
- Rollup invariants widened to number|string: content fingerprints
  (e.g. a per-tree SHA-256) are valid invariant values; strict equality
  either way, and a type mismatch reads as incoherence, never a pass.
This commit is contained in:
David Snelling 2026-07-15 12:36:27 -07:00
parent 70886da548
commit 352e356fd5
7 changed files with 153 additions and 3 deletions

View file

@ -108,6 +108,25 @@ describe('fact log dual-write (memory adapter)', () => {
}
})
it('the storage fact-scan capability serves a provider holding only `storage`', async () => {
// An index provider receives `storage` — never the brain — and reaches the
// fact log through the host-wired capability (it must never construct its
// own fact-log reader: the log's open path is writer-side).
const id = await brain.add({ data: 'via storage', type: 'document', metadata: { s: 1 } })
await brain.remove(id)
const storage = brain.storage
expect(typeof storage.scanFacts).toBe('function')
expect(storage.factLogHeadGeneration()).toBe(brain.scanFacts()!.headGeneration)
const viaStorage: CommitFact[] = []
for await (const b of storage.scanFacts()!.batches()) viaStorage.push(...b.facts)
const viaBrain: CommitFact[] = []
for await (const b of brain.scanFacts()!.batches()) viaBrain.push(...b.facts)
expect(viaStorage.map((f) => f.generation)).toEqual(viaBrain.map((f) => f.generation))
expect(storage.factSegmentPaths()).toEqual(brain.factSegmentPaths())
})
it('scan telemetry carries the frozen shape end-to-end', async () => {
for (let i = 0; i < 5; i++) await brain.add({ data: `t${i}`, type: 'document', metadata: { i } })