feat(machine): seed + bunker-binding IPC bridge

get-atm-secrets now returns { spireSeed, bunkerBinding } instead of the raw
nsec (one-shot semantics kept). Adds IPC handlers + preload bindings for
saveBunkerBinding / clearBunkerBinding / resetBootstrapGate so the renderer
can persist a pairing and re-arm the cassette-state hello on re-pair (#56).
resetBootstrapGate added to state-store. Types mirrored in electron.d.ts.

Part of Phase C, aiolabs/bitspire#52.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Padreug 2026-06-19 00:15:31 +02:00
commit 209e4c3e20
4 changed files with 75 additions and 8 deletions

View file

@ -26,14 +26,19 @@ import {
getLastKnownConfigCreatedAt,
getBootstrapPublishedAt,
markBootstrapPublished,
resetBootstrapGate,
applyOperatorCassettesConfig,
getFeeConfig,
getLastKnownFeeConfigCreatedAt,
applyFeeConfig,
getBunkerBinding,
saveBunkerBinding,
clearBunkerBinding,
type OperatorCassettesPayload,
type FeeConfigPayload,
type FeeConfigRow,
type ApplyResult,
type StoredBunkerBinding,
} from './state-store.js'
import { initializeHal, type HalInstance } from './hal-service.js'
@ -323,14 +328,31 @@ let secretsConsumed = false
ipcMain.handle('get-atm-secrets', () => {
if (secretsConsumed) {
console.warn('[Electron] SECURITY: get-atm-secrets called after secrets already consumed')
return { atmPrivateKey: '' }
return { spireSeed: '', bunkerBinding: null }
}
secretsConsumed = true
// The spire pairing seed (one-shot connect token inside) + the persisted
// bunker binding (transport key). The renderer resolves these into a
// BunkerSigner; see services/signer-resolver.ts (aiolabs/bitspire#52).
return {
atmPrivateKey: process.env.VITE_ATM_PRIVATE_KEY || '',
spireSeed: process.env.VITE_SPIRE_SEED || '',
bunkerBinding: getBunkerBinding(),
}
})
// Bunker binding persistence — the renderer writes the binding after a
// successful pairing (connectNewSeed), and resets the bootstrap gate so the
// new operator receives the spire's hello-event (aiolabs/bitspire#52 / #56).
ipcMain.handle('state:save-bunker-binding', (_event, binding: StoredBunkerBinding): void => {
saveBunkerBinding(binding)
})
ipcMain.handle('state:clear-bunker-binding', (): void => {
clearBunkerBinding()
})
ipcMain.handle('state:reset-bootstrap-gate', (): void => {
resetBootstrapGate()
})
// State persistence IPC handlers
ipcMain.handle('state:load-cassettes', () => loadCassettes())
ipcMain.handle('state:set-cassettes', (_event, cassettes) => setCassettes(cassettes))