OverviewFilesCommitsIssuesDocsReleasesPeers

P2P-Hiverelay / packages / blind-daemon / test / wal-segments.test.js

584 lines · javascript ref:
import test from 'brittle'
import fs from 'node:fs/promises'
import path from 'node:path'
import b4a from 'b4a'
import {
  BlindTransactionStore,
  BlindWalIntegrityError
} from '../transaction-store.js'
import {
  BlindStoreSession,
  acquireBlindStoreSessionTransactionLease
} from '../store-session.js'
import {
  scanBlindWalV2ForAnchoredRecovery,
  verifyBlindWalRecoveryScanResult
} from '../wal-recovery-scan.js'
import {
  createBlindBoundaryScratch,
  removeBlindBoundaryScratch
} from '../../../test/blind-boundary-scratch.js'

const PARTITION_KEY = b4a.alloc(32, 0x81)
const OWNER_FENCE = b4a.alloc(32, 0x91)
const CONTINUITY = b4a.alloc(32, 0x94)
const SEGMENT_MAX_BYTES = 64 * 1024
const FRAME_PAYLOAD_BYTES = 8000
const FRAME_BYTES = 224 + FRAME_PAYLOAD_BYTES // 8224 → seven frames per 64 KiB segment

// Captured from the pre-segmentation store (single growing wal.v2/HRWL, the
// format this phase segments): five single-appender frames over the fixed
// keys above. The cutover must import this exact file as the live segment,
// replay it byte-for-byte, and seal it without breaking the hash chain.
const GOLDEN_WAL_HEX = '4852574c02c9000000f200000000000000015151515151515151515151515151515151515151515151515151515151515151000100000000000000019191919191919191919191919191919191919191919191919191919191919191000000120000000000000000000000000000000000000000000000000000000000000000949494949494949494949494949494949494949494949494949494949494949471c376c15bf7b8ac7dcc12041d195971f02d9814450bf72a4b7bce1ba29c56006d6967726174696f6e2d676f6c64656e2d319a3330d5e3be15f13536d442e9f2d04d723e7e41cbb02a53ee1fcc7adf0a92674852574c02ca000000f20000000000000002525252525252525252525252525252525252525252525252525252525252525200020000000000000001919191919191919191919191919191919191919191919191919191919191919100000012ce8f5edeae3943cb18c5d903572ea5feccf69ef5dca1e1aa0b2820f605c9d4db9494949494949494949494949494949494949494949494949494949494949494b0ed88d52d9b8ba7c5662ec4f85d20f2151d028da3ce266f1a39af03a418755f6d6967726174696f6e2d676f6c64656e2d32b53203059e4ec81b522c6e4303bd9f2d33506ef5e75615d85e9ce922baa672554852574c02cb000000f2000000000000000353535353535353535353535353535353535353535353535353535353535353530003000000000000000191919191919191919191919191919191919191919191919191919191919191910000001219b507a1a7e52b3ba66e91607e94b70e1695a980cf681bcf0aec743da12a31919494949494949494949494949494949494949494949494949494949494949494de5bc44ddcb297bb2a8b4023ee72b7ab61c66abea00ae2d362cb0eb61fbddab36d6967726174696f6e2d676f6c64656e2d333a476ecef536cbfea5095eb57ad53d788ea9c306d847718f1adf35a8c0687fd94852574c02cc000000f200000000000000045454545454545454545454545454545454545454545454545454545454545454000400000000000000019191919191919191919191919191919191919191919191919191919191919191000000122e61431a1c3508aa68127027926fa39bcfce923b7462bf71dff7fc40b0019b239494949494949494949494949494949494949494949494949494949494949494198cd8f6be9ef6f9cb4ca2e340548d991a1d0a6270819aaec64f1e285fd55c026d6967726174696f6e2d676f6c64656e2d34084381465b261ea9e870b4f650ba75da6d73a18a218e6a727ab82787c93aa05d4852574c02cd000000f20000000000000005555555555555555555555555555555555555555555555555555555555555555500050000000000000001919191919191919191919191919191919191919191919191919191919191919100000012b37b16a02c0ee5c6b26cb9850affc6a30224a2e944be12b922cfd55ebf8d43f89494949494949494949494949494949494949494949494949494949494949494db4d446450ad140bc3b51ab6e32ae0e784e378bf3456b49f27506b1fb8afc7ad6d6967726174696f6e2d676f6c64656e2d3589c4f31ee65ecd14bced204acc960b88d4c6053f136ab4f16cb64615f53948de'
const GOLDEN_WAL_BYTES = 1210
const GOLDEN_FRAME_HASHES = [
  'ce8f5edeae3943cb18c5d903572ea5feccf69ef5dca1e1aa0b2820f605c9d4db',
  '19b507a1a7e52b3ba66e91607e94b70e1695a980cf681bcf0aec743da12a3191',
  '2e61431a1c3508aa68127027926fa39bcfce923b7462bf71dff7fc40b0019b23',
  'b37b16a02c0ee5c6b26cb9850affc6a30224a2e944be12b922cfd55ebf8d43f8',
  'e06698399f7885a1be70390a9a78690e9c23ce13286f6ed4255e2fb7f0f4c676'
]

async function temporaryRoot (t, name = 'hiverelay-wal-segments-') {
  const root = await createBlindBoundaryScratch(name)
  t.teardown(async () => removeBlindBoundaryScratch(root))
  return root
}

function storeOptions (root, overrides = {}) {
  return {
    root,
    partitionKey: PARTITION_KEY,
    ownerFenceTokenHash: OWNER_FENCE,
    durabilityContinuityHash: CONTINUITY,
    walSegmentMaxBytes: SEGMENT_MAX_BYTES,
    ...overrides
  }
}

function frameValue (index) {
  return {
    type: 200 + (index % 50),
    transactionId: b4a.alloc(32, index),
    virtualBucket: index % 8,
    payload: b4a.alloc(FRAME_PAYLOAD_BYTES, index)
  }
}

function segmentName (first, last) {
  const hex = value => value.toString(16).padStart(16, '0')
  return `wal-${hex(first)}-${hex(last)}.v2`
}

async function appendFrames (store, from, to) {
  const frames = []
  for (let index = from; index <= to; index++) frames.push(await store.append(frameValue(index)))
  return frames
}

async function sealedSegmentNames (root) {
  const names = await fs.readdir(path.join(root, 'control'))
  return names.filter(name => name.startsWith('wal-')).sort()
}

async function rejectsIntegrity (t, promise, pattern) {
  try {
    await promise
    t.fail('expected the WAL to fail closed')
  } catch (error) {
    t.is(error.name, 'BlindWalIntegrityError')
    if (pattern) t.ok(pattern.test(error.message), `"${error.message}" matches ${pattern}`)
  }
}

async function rejectsType (t, promise, pattern) {
  try {
    await promise
    t.fail('expected a TypeError')
  } catch (error) {
    t.ok(error instanceof TypeError, `${error} is a TypeError`)
    if (pattern) t.ok(pattern.test(error.message), `"${error.message}" matches ${pattern}`)
  }
}

// Starts count appenders behind one gate so the drain admits them as exactly
// one commit group, exactly like the group-commit admission rule.
function gatedAppenders (store, from, count, applied) {
  let release
  const gate = new Promise(resolve => { release = resolve })
  const commits = []
  for (let index = from; index < from + count; index++) {
    commits.push((async () => {
      await gate
      return store.appendAndApply(frameValue(index), frame => {
        if (applied) applied.push(Number(frame.sequence))
      })
    })())
  }
  return { commits, release: () => release() }
}

test('segment rollover seals the live segment at a group boundary and keeps the chain continuous', async t => {
  const root = await temporaryRoot(t)
  const faults = []
  const store = new BlindTransactionStore(storeOptions(root, {
    faultInjector: async point => {
      if (point.startsWith('wal:after-segment')) faults.push(point)
    }
  }))
  await store.open(() => {})
  const frames = await appendFrames(store, 1, 16)
  t.is(store.walSequence, 16n)
  t.alike(store.sealedWalSegments.map(segment => segment.name), [segmentName(1, 7), segmentName(8, 14)])
  t.is(store.liveSegmentFirstSequence, 15n)
  t.is(store.walOffset, 2 * FRAME_BYTES)
  t.alike(faults, [
    'wal:after-segment-seal',
    'wal:after-segment-rollover',
    'wal:after-segment-seal',
    'wal:after-segment-rollover'
  ])
  t.alike(await sealedSegmentNames(root), [segmentName(1, 7), segmentName(8, 14)])
  const sealedBytes = await fs.stat(path.join(root, 'control', segmentName(1, 7)))
  t.is(sealedBytes.size, 7 * FRAME_BYTES, 'sealed segment ends exactly on the group boundary')
  await store.close()

  const reopened = new BlindTransactionStore(storeOptions(root))
  const replayed = []
  await reopened.open(frame => replayed.push(frame))
  t.is(replayed.length, 16, 'every frame replays across the segment boundary')
  for (let index = 0; index < 16; index++) {
    t.is(replayed[index].sequence, BigInt(index + 1))
    t.ok(b4a.equals(replayed[index].walHash, frames[index].walHash))
    if (index > 0) t.ok(b4a.equals(replayed[index].previousWalHash, frames[index - 1].walHash))
  }
  t.alike(reopened.sealedWalSegments.map(segment => segment.name), [segmentName(1, 7), segmentName(8, 14)])
  t.is(reopened.liveSegmentFirstSequence, 15n)
  await reopened.close()
})

test('a commit group never straddles a segment boundary', async t => {
  const root = await temporaryRoot(t)
  const store = new BlindTransactionStore(storeOptions(root))
  await store.open(() => {})
  const frames = await appendFrames(store, 1, 14)
  t.alike(await sealedSegmentNames(root), [segmentName(1, 7)])
  t.is(store.walOffset, 7 * FRAME_BYTES)

  const applied = []
  const { commits, release } = gatedAppenders(store, 15, 3, applied)
  release()
  const groupFrames = await Promise.all(commits)
  t.alike(groupFrames.map(frame => Number(frame.sequence)), [15, 16, 17])
  t.ok(b4a.equals(groupFrames[0].previousWalHash, frames[13].walHash), 'hash chain crosses the rollover')
  t.alike(applied, [15, 16, 17], 'group applies in admission order')
  t.alike(await sealedSegmentNames(root), [segmentName(1, 7), segmentName(8, 14)])
  const liveStat = await fs.stat(path.join(root, 'control', 'wal.v2'))
  t.is(liveStat.size, 3 * FRAME_BYTES, 'the whole group landed together in the fresh live segment')
  await store.close()

  const reopened = new BlindTransactionStore(storeOptions(root))
  const replayed = []
  await reopened.open(frame => replayed.push(Number(frame.sequence)))
  t.alike(replayed, Array.from({ length: 17 }, (_, index) => index + 1))
  await reopened.close()
})

test('checkpoint-anchored recovery replays only post-anchor frames', async t => {
  const root = await temporaryRoot(t)
  const store = new BlindTransactionStore(storeOptions(root))
  await store.open(() => {})
  const frames = await appendFrames(store, 1, 16)
  await store.close()
  const anchor = { sequence: frames[9].sequence, hash: frames[9].walHash }

  const anchored = new BlindTransactionStore(storeOptions(root))
  const replayed = []
  await anchored.open(frame => replayed.push(Number(frame.sequence)), { checkpointAnchor: anchor })
  t.alike(replayed, [11, 12, 13, 14, 15, 16], 'covered frames are verified but not applied')
  t.is(anchored.walSequence, 16n)
  t.ok(b4a.equals(anchored.walHash, frames[15].walHash))
  await anchored.close()

  const covered = new BlindTransactionStore(storeOptions(root))
  const coveredReplay = []
  await covered.open(frame => coveredReplay.push(Number(frame.sequence)), {
    checkpointAnchor: anchor,
    replayCoveredFrames: true
  })
  t.alike(coveredReplay, Array.from({ length: 16 }, (_, index) => index + 1),
    'prune-tolerant full replay applies every retained frame')
  await covered.close()

  const wrongHash = new BlindTransactionStore(storeOptions(root))
  await rejectsIntegrity(t, wrongHash.open(() => {}, {
    checkpointAnchor: { sequence: frames[9].sequence, hash: frames[10].walHash }
  }), /anchor hash mismatch/)

  const missing = new BlindTransactionStore(storeOptions(root))
  await rejectsIntegrity(t, missing.open(() => {}, {
    checkpointAnchor: { sequence: 20n, hash: frames[15].walHash }
  }), /does not contain the exact checkpoint anchor/)

  await rejectsType(t,
    new BlindTransactionStore(storeOptions(root)).open(() => {}, { replayCoveredFrames: true }),
    /replayCoveredFrames requires a checkpoint anchor/)
  await rejectsType(t,
    new BlindTransactionStore(storeOptions(root)).open(() => {}, { anchor: null }),
    /unknown field anchor/)
  await rejectsType(t,
    new BlindTransactionStore(storeOptions(root)).open(() => {}, {
      checkpointAnchor: { sequence: 0n, hash: frames[0].walHash }
    }),
    /sequence must be nonzero/)
})

test('boundary anchor after pruning replays post-anchor frames only', async t => {
  const root = await temporaryRoot(t)
  const store = new BlindTransactionStore(storeOptions(root))
  await store.open(() => {})
  const frames = await appendFrames(store, 1, 16)
  await store.close()
  const anchor = { sequence: frames[13].sequence, hash: frames[13].walHash }

  const writer = new BlindTransactionStore(storeOptions(root))
  await writer.open(() => {})
  const result = await writer.pruneWalSegments({ checkpointAnchor: anchor, retainFromSequence: 15n })
  t.alike(result.prunedSegments.map(segment => segment.name), [segmentName(1, 7), segmentName(8, 14)])
  t.alike(await sealedSegmentNames(root), [])
  await writer.close()

  await rejectsIntegrity(t,
    new BlindTransactionStore(storeOptions(root)).open(() => {}),
    /begins after genesis without a checkpoint anchor/)

  const anchored = new BlindTransactionStore(storeOptions(root))
  const replayed = []
  await anchored.open(frame => replayed.push(Number(frame.sequence)), { checkpointAnchor: anchor })
  t.alike(replayed, [15, 16], 'the first retained frame must chain to the anchor hash')
  t.is(anchored.walSequence, 16n)
  await anchored.close()

  const wrongHash = new BlindTransactionStore(storeOptions(root))
  await rejectsIntegrity(t, wrongHash.open(() => {}, {
    checkpointAnchor: { sequence: frames[13].sequence, hash: frames[12].walHash }
  }))
})

test('pruning enforces the checkpoint anchor and the spent-marker retention floor', async t => {
  const root = await temporaryRoot(t)
  const store = new BlindTransactionStore(storeOptions(root))
  await store.open(() => {})
  const frames = await appendFrames(store, 1, 16)
  await store.close()

  const writer = new BlindTransactionStore(storeOptions(root))
  await writer.open(() => {})
  const anchor = { sequence: frames[13].sequence, hash: frames[13].walHash }

  const floorInsideSecond = await writer.pruneWalSegments({
    checkpointAnchor: anchor,
    retainFromSequence: 10n
  })
  t.alike(floorInsideSecond.prunedSegments.map(segment => segment.name), [segmentName(1, 7)],
    'a segment at or above the floor survives even when checkpoint-covered')
  t.alike(await sealedSegmentNames(root), [segmentName(8, 14)])

  const retainEverything = await writer.pruneWalSegments({ checkpointAnchor: anchor, retainFromSequence: 1n })
  t.alike(retainEverything.prunedSegments, [], 'floor 1 retains every segment')
  t.alike(await sealedSegmentNames(root), [segmentName(8, 14)])

  await rejectsIntegrity(t, writer.pruneWalSegments({
    checkpointAnchor: { sequence: 20n, hash: frames[15].walHash },
    retainFromSequence: 15n
  }), /ahead of the WAL head/)
  await rejectsIntegrity(t, writer.pruneWalSegments({
    checkpointAnchor: { sequence: frames[13].sequence, hash: frames[12].walHash },
    retainFromSequence: 15n
  }), /anchor hash mismatch/)
  await rejectsIntegrity(t, writer.pruneWalSegments({
    checkpointAnchor: { sequence: frames[4].sequence, hash: frames[4].walHash },
    retainFromSequence: 15n
  }), /anchor frame is not retained/)
  await rejectsType(t,
    writer.pruneWalSegments({ checkpointAnchor: anchor, retainFromSequence: 0n }),
    /retainFromSequence must be nonzero/)
  await rejectsType(t,
    writer.pruneWalSegments({ checkpointAnchor: anchor, retainFromSequence: 15n, extra: true }),
    /unknown field extra/)
  await rejectsType(t, writer.pruneWalSegments({ retainFromSequence: 15n }), /checkpointAnchor must be an object/)

  const headAnchor = { sequence: frames[15].sequence, hash: frames[15].walHash }
  const headPrune = await writer.pruneWalSegments({ checkpointAnchor: headAnchor, retainFromSequence: 15n })
  t.alike(headPrune.prunedSegments.map(segment => segment.name), [segmentName(8, 14)],
    'the O(1) head anchor path verifies against the in-memory head')
  t.alike(await sealedSegmentNames(root), [])
  await writer.close()

  const anchored = new BlindTransactionStore(storeOptions(root))
  const replayed = []
  await anchored.open(frame => replayed.push(Number(frame.sequence)), { checkpointAnchor: headAnchor })
  t.alike(replayed, [], 'the anchor frame itself survived inside the live segment')
  t.is(anchored.walSequence, 16n)
  await anchored.close()
})

test('crash mid-prune leaves a contiguous retained prefix and an unpoisoned writer', async t => {
  const root = await temporaryRoot(t)
  const store = new BlindTransactionStore(storeOptions(root))
  await store.open(() => {})
  await appendFrames(store, 1, 16)
  await store.close()

  let pruneFaults = 0
  const writer = new BlindTransactionStore(storeOptions(root, {
    faultInjector: async point => {
      if (point === 'wal:after-segment-prune' && pruneFaults === 0) {
        pruneFaults++
        throw new Error('simulated crash mid-prune')
      }
    }
  }))
  const recovered = []
  await writer.open(frame => recovered.push(frame))
  const anchor = { sequence: recovered[13].sequence, hash: recovered[13].walHash }
  await t.exception(
    writer.pruneWalSegments({ checkpointAnchor: anchor, retainFromSequence: 15n }),
    /simulated crash mid-prune/
  )
  t.is(pruneFaults, 1)
  t.alike(await sealedSegmentNames(root), [segmentName(8, 14)],
    'the oldest segment is deleted, the rest of the prefix is untouched')

  const extra = await writer.append(frameValue(17))
  t.is(extra.sequence, 17n, 'a prune fault does not poison the writer')
  const retry = await writer.pruneWalSegments({ checkpointAnchor: anchor, retainFromSequence: 15n })
  t.alike(retry.prunedSegments.map(segment => segment.name), [segmentName(1, 7), segmentName(8, 14)],
    'a retried prune tolerates the already-deleted segment')
  t.alike(await sealedSegmentNames(root), [])
  await writer.close()

  const anchored = new BlindTransactionStore(storeOptions(root))
  const replayed = []
  await anchored.open(frame => replayed.push(Number(frame.sequence)), { checkpointAnchor: anchor })
  t.alike(replayed, [15, 16, 17])
  await anchored.close()
})

test('interior segment loss or corruption fails closed', async t => {
  const root = await temporaryRoot(t)
  const store = new BlindTransactionStore(storeOptions(root))
  await store.open(() => {})
  await appendFrames(store, 1, 16)
  await store.close()
  const control = path.join(root, 'control')
  const middle = path.join(control, segmentName(8, 14))
  const middleBytes = await fs.readFile(middle)

  await fs.unlink(middle)
  await rejectsIntegrity(t, new BlindTransactionStore(storeOptions(root)).open(() => {}))
  await fs.writeFile(middle, middleBytes, { mode: 0o600 })

  const corrupted = b4a.from(middleBytes)
  corrupted[1000] ^= 0xff
  await fs.writeFile(middle, corrupted, { mode: 0o600 })
  await rejectsIntegrity(t, new BlindTransactionStore(storeOptions(root)).open(() => {}))
  await fs.writeFile(middle, middleBytes, { mode: 0o600 })

  await fs.truncate(middle, middleBytes.byteLength - 100)
  await rejectsIntegrity(t, new BlindTransactionStore(storeOptions(root)).open(() => {}), /ends inside a frame/)
  await fs.writeFile(middle, middleBytes, { mode: 0o600 })

  await fs.writeFile(path.join(control, 'wal-not-a-segment.v2'), b4a.alloc(16), { mode: 0o600 })
  await rejectsIntegrity(t, new BlindTransactionStore(storeOptions(root)).open(() => {}), /unexpected WAL segment entry/)
  await fs.unlink(path.join(control, 'wal-not-a-segment.v2'))

  const overlap = path.join(control, segmentName(7, 12))
  await fs.writeFile(overlap, b4a.alloc(16), { mode: 0o600 })
  await rejectsIntegrity(t, new BlindTransactionStore(storeOptions(root)).open(() => {}), /interior gap/)
  await fs.unlink(overlap)

  const restored = new BlindTransactionStore(storeOptions(root))
  const replayed = []
  await restored.open(frame => replayed.push(Number(frame.sequence)))
  t.alike(replayed, Array.from({ length: 16 }, (_, index) => index + 1), 'restored segments recover')
  await restored.close()
})

test('torn tail truncation stays legal only at the live segment tail', async t => {
  const root = await temporaryRoot(t)
  const store = new BlindTransactionStore(storeOptions(root))
  await store.open(() => {})
  await appendFrames(store, 1, 16)
  await store.close()
  const walPath = path.join(root, 'control', 'wal.v2')
  const completeSize = (await fs.stat(walPath)).size
  await fs.appendFile(walPath, b4a.alloc(17, 0xff))

  const reopened = new BlindTransactionStore(storeOptions(root))
  const replayed = []
  await reopened.open(frame => replayed.push(Number(frame.sequence)))
  t.alike(replayed, Array.from({ length: 16 }, (_, index) => index + 1))
  t.is((await fs.stat(walPath)).size, completeSize, 'the torn live tail was truncated')
  t.alike(await sealedSegmentNames(root), [segmentName(1, 7), segmentName(8, 14)])
  const extra = await reopened.append(frameValue(17))
  t.is(extra.sequence, 17n, 'the truncated live segment accepts new frames')
  await reopened.close()
})

test('migration: a pre-segmentation wal.v2 imports as the live segment and seals across the cutover', async t => {
  const root = await temporaryRoot(t, 'hiverelay-wal-migration-')
  const control = path.join(root, 'control')
  await fs.mkdir(control, { mode: 0o700 })
  await fs.writeFile(path.join(control, 'wal.v2'), b4a.from(GOLDEN_WAL_HEX, 'hex'), { mode: 0o600 })

  const store = new BlindTransactionStore(storeOptions(root))
  const replayed = []
  await store.open(frame => replayed.push(frame))
  t.is(replayed.length, 5, 'the imported single-file store replays completely')
  t.alike(replayed.map(frame => b4a.toString(frame.walHash, 'hex')), GOLDEN_FRAME_HASHES)
  t.is(store.walSequence, 5n)
  t.is(store.liveSegmentFirstSequence, 1n)
  t.alike(store.sealedWalSegments, [], 'import is zero-copy: the legacy file is the live segment')

  const extra = await appendFrames(store, 6, 13)
  t.ok(b4a.equals(extra[0].previousWalHash, replayed[4].walHash), 'the chain continues across the cutover')
  t.alike(await sealedSegmentNames(root), [segmentName(1, 12)],
    'the first rollover seals the imported frames with the new ones')
  await store.close()

  const sealedBytes = await fs.readFile(path.join(control, segmentName(1, 12)))
  t.is(b4a.toString(sealedBytes.subarray(0, GOLDEN_WAL_BYTES), 'hex'), GOLDEN_WAL_HEX,
    'the imported frames are byte-for-byte preserved inside the sealed segment')

  const reopened = new BlindTransactionStore(storeOptions(root))
  const replayedAfter = []
  await reopened.open(frame => replayedAfter.push(frame))
  t.is(replayedAfter.length, 13)
  t.alike(replayedAfter.slice(0, 5).map(frame => b4a.toString(frame.walHash, 'hex')), GOLDEN_FRAME_HASHES,
    'golden hashes validate continuously out of the sealed segment')
  t.is(reopened.walSequence, 13n)
  await reopened.close()
})

test('checkpoint-anchored scan replays only post-anchor frames across segments', async t => {
  const root = await temporaryRoot(t, 'hiverelay-wal-scan-segments-')
  const control = path.join(root, 'control')
  const store = new BlindTransactionStore(storeOptions(root))
  await store.open(() => {})
  const frames = await appendFrames(store, 1, 16)
  await store.close()
  await fs.writeFile(path.join(control, 'manifest-a.v1'), b4a.alloc(64, 0x74), { mode: 0o600 })

  const session = new BlindStoreSession({ root })
  await session.open()
  const lease = await acquireBlindStoreSessionTransactionLease(session.lockContext(), root)
  t.teardown(async () => {
    lease.release()
    await session.close()
  })

  const scanOptions = checkpoint => ({
    root,
    lease,
    checkpoint,
    mapGeneration: 1n,
    writerFenceTokenHash: OWNER_FENCE,
    durabilityContinuityHash: CONTINUITY,
    maximumWalBytes: 1024 * 1024,
    maximumWalFrames: 1024,
    maximumWalPayloadBytes: 1024 * 1024,
    applyShadowFrame: async frame => { replayed.push(Number(frame.sequence)) }
  })

  const replayed = []
  const result = await scanBlindWalV2ForAnchoredRecovery(
    scanOptions({ sequence: frames[9].sequence, hash: frames[9].walHash }))
  t.alike(replayed, [11, 12, 13, 14, 15, 16], 'shadow replay starts after the anchor')
  t.is(result.sealedSegmentCount, 2)
  t.is(result.completeFrameCount, 16)
  t.is(result.headSequence, 16n)
  t.ok(b4a.equals(result.headHash, frames[15].walHash))
  t.is(await verifyBlindWalRecoveryScanResult(result, root, lease), true)

  const sealedPath = path.join(control, segmentName(8, 14))
  const sealedBytes = await fs.readFile(sealedPath)
  const tampered = b4a.from(sealedBytes)
  tampered[100] ^= 0xff
  await fs.writeFile(sealedPath, tampered, { mode: 0o600 })
  await t.exception(verifyBlindWalRecoveryScanResult(result, root, lease),
    /sealed WAL segment changed/)
  await fs.writeFile(sealedPath, sealedBytes, { mode: 0o600 })

  replayed.length = 0
  const rescanned = await scanBlindWalV2ForAnchoredRecovery(
    scanOptions({ sequence: frames[9].sequence, hash: frames[9].walHash }))
  t.alike(replayed, [11, 12, 13, 14, 15, 16], 'restored bytes scan clean again')
  t.is(await verifyBlindWalRecoveryScanResult(rescanned, root, lease), true)

  await fs.unlink(path.join(control, segmentName(8, 14)))
  await rejectsIntegrity(t, scanBlindWalV2ForAnchoredRecovery(
    scanOptions({ sequence: frames[9].sequence, hash: frames[9].walHash })))
})

test('checkpoint-anchored scan anchors on the segment boundary after pruning', async t => {
  const root = await temporaryRoot(t, 'hiverelay-wal-scan-boundary-')
  const control = path.join(root, 'control')
  const store = new BlindTransactionStore(storeOptions(root))
  await store.open(() => {})
  const frames = await appendFrames(store, 1, 16)
  const anchor = { sequence: frames[6].sequence, hash: frames[6].walHash }
  const result = await store.pruneWalSegments({ checkpointAnchor: anchor, retainFromSequence: 8n })
  t.alike(result.prunedSegments.map(segment => segment.name), [segmentName(1, 7)])
  await store.close()
  await fs.writeFile(path.join(control, 'manifest-a.v1'), b4a.alloc(64, 0x74), { mode: 0o600 })

  const session = new BlindStoreSession({ root })
  await session.open()
  const lease = await acquireBlindStoreSessionTransactionLease(session.lockContext(), root)
  t.teardown(async () => {
    lease.release()
    await session.close()
  })

  const replayed = []
  const scan = await scanBlindWalV2ForAnchoredRecovery({
    root,
    lease,
    checkpoint: anchor,
    mapGeneration: 1n,
    writerFenceTokenHash: OWNER_FENCE,
    durabilityContinuityHash: CONTINUITY,
    maximumWalBytes: 1024 * 1024,
    maximumWalFrames: 1024,
    maximumWalPayloadBytes: 1024 * 1024,
    applyShadowFrame: async frame => { replayed.push(Number(frame.sequence)) }
  })
  t.alike(replayed, [8, 9, 10, 11, 12, 13, 14, 15, 16],
    'the pruned prefix is pinned by the boundary link alone')
  t.is(scan.sealedSegmentCount, 1)
  t.is(scan.completeFrameCount, 9)
  t.is(scan.checkpointEndOffset, 0)
  t.is(await verifyBlindWalRecoveryScanResult(scan, root, lease), true)
})

test('a sealed segment whose name lies about its range fails closed', async t => {
  const root = await temporaryRoot(t)
  const store = new BlindTransactionStore(storeOptions(root))
  await store.open(() => {})
  await appendFrames(store, 1, 8)
  await store.close()
  // A forged seal: the name claims frames 8..9 but the file holds only
  // frame 8 — the seal is the name, so a range mismatch is an interior
  // break, not a torn tail.
  await fs.rename(
    path.join(root, 'control', 'wal.v2'),
    path.join(root, 'control', segmentName(8, 9))
  )
  await fs.writeFile(path.join(root, 'control', 'wal.v2'), b4a.alloc(0), { mode: 0o600 })
  const error = await new BlindTransactionStore(storeOptions(root)).open(() => {}).catch(error => error)
  t.ok(error instanceof BlindWalIntegrityError, 'a forged segment seal fails closed')
  t.ok(/does not contain its sealed sequence range/.test(error.message))
})