/**
* Repair — fix corrupted Autobase instances.
*
* Two repair operations:
* 1. truncateWriterCore — remove poisoned ops from the writer log
* 2. resetSystemState — truncate the internal system + view batches
* so Autobase re-bootstraps from the clean writer log
*
* Both are needed because:
* - The writer core has the poisoned op in the append-only log
* - The system core has persisted state built from the poisoned op
* - Truncating only the writer core leaves stale system state
* - Resetting only the system core causes it to replay the poison
*/
import Autobase from 'autobase'
import Hyperbee from 'hyperbee'
/**
* Truncate a writer core at the poisoned op index.
* All ops before the poison are preserved.
*
* @param {Corestore} ns - Namespaced corestore
* @param {number} poisonIndex - Index of the poisoned op
* @returns {{ before: number, after: number }}
*/
export async function truncateWriterCore (ns, poisonIndex) {
const local = ns.get({ name: 'local' })
await local.ready()
const before = local.length
await local.truncate(poisonIndex)
const after = local.length
await local.close()
return { before, after }
}
/**
* Reset Autobase system state so it re-bootstraps from the writer log.
*
* Opens the Autobase, accesses the internal ViewCore batch sessions
* (which are writable even when the read-only session is not), and
* truncates them to 0. Also clears the autobase/boot userData.
*
* Why the batch, not the core directly:
* - base.core is a read-only session (Autobase creates it with writable: false)
* - Opening the core by key doesn't provide the manifest, so corestore
* treats it as remote (not writable)
* - The ViewCore's batch session IS writable — it was created with
* { name: 'batch', writable: true } by AutoStore
*
* @param {Corestore} ns - Namespaced corestore
* @param {Object} opts
* @param {Function} opts.open - Autobase open handler (creates the view)
* @param {Function} opts.apply - Autobase apply handler
* @returns {{ systemTruncated: number, viewTruncated: number, bootCleared: boolean }}
*/
export async function resetSystemState (ns, opts = {}) {
const {
open = defaultOpen,
apply = defaultApply
} = opts
const base = new Autobase(ns, null, {
open,
apply,
valueEncoding: 'binary'
})
await base.ready()
const result = { systemTruncated: 0, viewTruncated: 0, bootCleared: false }
// Truncate system batch
const sysView = base._viewStore.getSystemView()
if (sysView.batch && sysView.batch.writable && sysView.batch.length > 0) {
result.systemTruncated = sysView.batch.length
await sysView.batch.truncate(0)
}
// Truncate view batch
const viewView = base._viewStore.byName.get('view')
if (viewView && viewView.batch && viewView.batch.writable && viewView.batch.length > 0) {
result.viewTruncated = viewView.batch.length
await viewView.batch.truncate(0)
}
// Clear boot record
const boot = await base.local.getUserData('autobase/boot')
if (boot) {
await base.local.setUserData('autobase/boot', null)
result.bootCleared = true
}
await base.close()
return result
}
/**
* Check if an Autobase has poisoned system state.
* Signs: writable=false with members > 0 on a self-bootstrapped Autobase.
*/
export async function hasPoisonedSystemState (ns, opts = {}) {
const {
open = defaultOpen,
apply = defaultApply
} = opts
const base = new Autobase(ns, null, {
open,
apply,
valueEncoding: 'binary'
})
await base.ready()
const poisoned = !base.writable && base.system.members > 0
const info = {
poisoned,
writable: base.writable,
members: base.system.members,
indexers: base.system.indexers.length,
localLength: base.local.length,
systemBatchLength: 0
}
const sysView = base._viewStore.getSystemView()
if (sysView.batch) {
info.systemBatchLength = sysView.batch.length
}
await base.close()
return info
}
function defaultOpen (store) {
return new Hyperbee(store.get('view', { valueEncoding: 'json' }), {
keyEncoding: 'utf-8',
valueEncoding: 'json'
})
}
async function defaultApply (nodes, view, host) {
await view.batch().flush()
}