fix(state-machine): add 2-minute safety timeout to dispensingCash state

If the dispenseCash promise hangs (hardware jam, serial port freeze,
waitForBillsRemoved stuck), the machine was trapped in dispensingCash
forever with no way to recover. Now it transitions to dispenseError
after 2 minutes, which then auto-returns to idle after 30 seconds.

This ensures the ATM always recovers to a usable state, even when
hardware fails mid-dispense.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Patrick Mulligan 2026-03-07 10:25:51 -05:00
commit e48073397f

View file

@ -392,6 +392,7 @@ export function createATMMachine(
delays: { delays: {
TIMEOUT_MS: 300000, // 5 minutes TIMEOUT_MS: 300000, // 5 minutes
COMPLETE_DELAY: 60000, COMPLETE_DELAY: 60000,
DISPENSE_TIMEOUT: 120000, // 2 min max for hardware to respond
DISPENSE_ERROR_TIMEOUT: 30000, // 30s like brain.js _timedState DISPENSE_ERROR_TIMEOUT: 30000, // 30s like brain.js _timedState
}, },
}).createMachine({ }).createMachine({
@ -654,6 +655,16 @@ export function createATMMachine(
}, },
}, },
dispensingCash: { dispensingCash: {
// Safety timeout: if dispenseCash promise hangs (hardware jam,
// serial port freeze), don't stay here forever.
after: {
DISPENSE_TIMEOUT: {
target: 'dispenseError',
actions: assign({
error: () => 'Dispense timed out — hardware may be jammed',
}),
},
},
invoke: { invoke: {
src: 'dispenseCash', src: 'dispenseCash',
input: ({ context }) => context.dispenseAmounts, input: ({ context }) => context.dispenseAmounts,