fix(cassettes): publish state on every change, and make the stamps monotonic
Three linked failures in one mechanism, so one commit. The state publish was gated on a one-shot 'have we said hello' flag. It fired once on first boot and then only after a dispense or an applied operator config, so any change to the layout itself — a reseed, an atm-tui edit, direct SQL — was never announced. The operator kept validating against a bay set the machine no longer had, and a publish from the dashboard could overwrite a fresh seed (#94). State is now published on every start. A publish is one fire-and-forget event with no retry. If the relay was unreachable at the moment of a dispense, that update was gone until the next customer bought cash. A five-minute heartbeat makes the channel self-healing and is also the only way an out-of-band edit to the table ever reaches the operator. Addressable events are ordered by created_at at second granularity with ties broken by lowest event id, and a relay acknowledges an event it then discards. Two publishes inside one second therefore left the winner decided by a hash, permanently, and a clock stepping backwards would have made every report from this machine vanish silently. Each publish now takes a stamp strictly above the last, recorded in the meta row that used to hold the gate — same key, no migration, honest name. Closes #94 Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
parent
db68e6e244
commit
54c59fadcc
6 changed files with 98 additions and 77 deletions
|
|
@ -24,9 +24,9 @@ import {
|
|||
markCommandExecuting,
|
||||
completeCommand,
|
||||
getLastKnownConfigCreatedAt,
|
||||
getBootstrapPublishedAt,
|
||||
markBootstrapPublished,
|
||||
resetBootstrapGate,
|
||||
getLastStatePublishedAt,
|
||||
markStatePublished,
|
||||
resetStatePublishWatermark,
|
||||
resetForRepair,
|
||||
applyOperatorCassettesConfig,
|
||||
getFeeConfig,
|
||||
|
|
@ -410,7 +410,7 @@ ipcMain.handle('get-atm-secrets', () => {
|
|||
})
|
||||
|
||||
// Bunker binding persistence — the renderer writes the binding after a
|
||||
// successful pairing (connectNewSeed), and resets the bootstrap gate so the
|
||||
// successful pairing (connectNewSeed), and resets the publish watermark 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)
|
||||
|
|
@ -418,8 +418,8 @@ ipcMain.handle('state:save-bunker-binding', (_event, binding: StoredBunkerBindin
|
|||
ipcMain.handle('state:clear-bunker-binding', (): void => {
|
||||
clearBunkerBinding()
|
||||
})
|
||||
ipcMain.handle('state:reset-bootstrap-gate', (): void => {
|
||||
resetBootstrapGate()
|
||||
ipcMain.handle('state:reset-state-publish-watermark', (): void => {
|
||||
resetStatePublishWatermark()
|
||||
})
|
||||
ipcMain.handle('state:reset-for-repair', (): void => {
|
||||
resetForRepair()
|
||||
|
|
@ -555,9 +555,9 @@ ipcMain.handle('state:remediate-transaction', (_event, txid: string, remediatedB
|
|||
ipcMain.handle('state:get-last-known-config-created-at', (): number =>
|
||||
getLastKnownConfigCreatedAt()
|
||||
)
|
||||
ipcMain.handle('state:get-bootstrap-published-at', (): number | null => getBootstrapPublishedAt())
|
||||
ipcMain.handle('state:mark-bootstrap-published', (_event, unixTimestamp: number): void => {
|
||||
markBootstrapPublished(unixTimestamp)
|
||||
ipcMain.handle('state:get-last-state-published-at', (): number | null => getLastStatePublishedAt())
|
||||
ipcMain.handle('state:mark-state-published', (_event, unixTimestamp: number): void => {
|
||||
markStatePublished(unixTimestamp)
|
||||
})
|
||||
ipcMain.handle(
|
||||
'state:apply-operator-cassettes-config',
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue