fix(machine): re-pair wipes the prior operator's config + watermarks (#70)
A new-seed re-pair UPSERTed the bunker binding but left fee_config, cassettes, and the created_at replay watermarks intact. The watermarks are the trap: a new backend whose first config event has a lower created_at than the old operator's last event is silently dropped as a replay, so re-pairing a long-lived install to a fresh backend appears to pair but never picks up new config. Add resetForRepair() (main-process state-store): in one transaction it clears fee_config and resets both replay watermarks to 0. Wired function → IPC (state:reset-for-repair) → preload → renderer, and called from the re-pair branch in signer-resolver, gated on an existing binding (re-pair only; a first pair has nothing to reset). Deliberately preserves cassettes/cashbox/transactions — those track PHYSICAL cash that survives an operator handover; a full wipe is the factory-reset path. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
parent
42c0d3e9ca
commit
78592d89f7
5 changed files with 42 additions and 0 deletions
|
|
@ -540,6 +540,32 @@ export function resetBootstrapGate(): void {
|
|||
db.prepare('UPDATE meta SET value = ? WHERE key = ?').run('', 'bootstrapPublishedAt')
|
||||
}
|
||||
|
||||
/**
|
||||
* Wipe operator-scoped CONFIG/TRUST state on a re-pair to a new operator/backend,
|
||||
* so stale policy from the previous pairing can't linger or silently reject the
|
||||
* new operator's config.
|
||||
*
|
||||
* Clears the fee config and resets BOTH replay watermarks to 0. The watermark
|
||||
* reset is the load-bearing part: without it, a new backend whose first config
|
||||
* event has a lower `created_at` than the old operator's last event is silently
|
||||
* dropped as a replay — the exact remnant trap where re-pairing a long-lived
|
||||
* install to a fresh backend appears to "work" but never picks up new config.
|
||||
*
|
||||
* Deliberately does NOT touch cassettes / cashbox / transactions: those track
|
||||
* PHYSICAL cash, which survives an operator handover. A full wipe (decommission
|
||||
* or a truly-fresh test) is the factory-reset path, not this.
|
||||
*/
|
||||
export function resetForRepair(): void {
|
||||
if (!db) throw new Error('Database not initialized')
|
||||
const database = db
|
||||
database.transaction(() => {
|
||||
database.prepare('DELETE FROM fee_config').run()
|
||||
const setWatermark = database.prepare('UPDATE meta SET value = ? WHERE key = ?')
|
||||
setWatermark.run('0', 'lastKnownFeeConfigCreatedAt')
|
||||
setWatermark.run('0', 'lastKnownConfigCreatedAt')
|
||||
})()
|
||||
}
|
||||
|
||||
export type OperatorCassettesPayload = {
|
||||
positions: Record<string, { denomination: number; count: number }>
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue