feat(machine): value-confirmed dispense, cash-out hold, fault screens, counts-uncertain on zero-with-error (ADR-005 §3–§5)
HAL glue (electron/hal-service.ts and the renderer-side services/hal.ts): dispenseConfirmed is Σ(denomination × dispensed) === Σ(denomination × requested), computed on value. The driver's tagged error is carried through as errorCode / rawCode / errorClass / human; pre-dispense inventory refusals are errorClass 'inventory' so they route to outOfCash rather than the fault screen. The manual-dispense command result keeps its wire key `dispensed` (spirekeeper's poller reads it) and gains the new fields alongside. Cash-out hold: state-store persists it in meta as one JSON value beside countsUncertainSince, idempotent on set (the first fault's `since` is kept); IPC get/set/clear through preload. The store persists the hold the moment the machine sets it and restores it into the machine on boot. A recount clears it in the store (same gesture that clears counts- uncertain); operator-config also honours a new resume_cash_out op — not a cassette op, split off before applyOperatorCassetteOps, and honoured only when stamped after the hold began so a re-delivered old resume cannot clear a fresh fault. Either release calls back into the store, which sends CASH_OUT_RELEASED. The cassettes-state document carries cash_out_held_since / _reason / _code (additive, like counts_uncertain_since); the availability beacon reports cash_out false while held; the idle Sell button is disabled with the reason. Store watcher: dispenseFault and outOfCash both record dispense_error / partial (the customer has paid either way). A report of zero dispensed WITH a hardware error now sets countsUncertainSince instead of being trusted as zero — a note stopped in the transport completes neither counter (sintra 2026-10-09: bay read 66, held 65, one in the transport). Fault screen: both terminal states show "your payment went through", amount paid, per-denomination dispensed, the txid as QR and text, the payment hash (threaded from the settlement watch through PAYMENT_RECEIVED) and the time, with "keep this reference" and an acknowledge button. The raw dispenser code is not shown; it travels in the report.
This commit is contained in:
parent
3ff86de4ed
commit
888870d01a
12 changed files with 469 additions and 72 deletions
|
|
@ -7,6 +7,14 @@
|
|||
|
||||
import { contextBridge, ipcRenderer } from 'electron'
|
||||
|
||||
/** Mirrors state-store.CashOutHold (ADR-005 §5) — preload can't import main-process modules. */
|
||||
interface CashOutHold {
|
||||
reason: string
|
||||
errorCode: string | null
|
||||
rawCode: string | null
|
||||
since: number
|
||||
}
|
||||
|
||||
/**
|
||||
* Runtime configuration interface (public info only)
|
||||
* These values are read from environment variables at runtime (not build time)
|
||||
|
|
@ -114,6 +122,11 @@ contextBridge.exposeInMainWorld('electronAPI', {
|
|||
ipcRenderer.invoke('state:get-counts-uncertain-since'),
|
||||
markCountsUncertain: (unixTimestamp: number): Promise<void> =>
|
||||
ipcRenderer.invoke('state:mark-counts-uncertain', unixTimestamp),
|
||||
// Cash-out hold (ADR-005 §5)
|
||||
getCashOutHold: (): Promise<CashOutHold | null> => ipcRenderer.invoke('state:get-cash-out-hold'),
|
||||
setCashOutHold: (hold: CashOutHold): Promise<CashOutHold> =>
|
||||
ipcRenderer.invoke('state:set-cash-out-hold', hold),
|
||||
clearCashOutHold: (): Promise<boolean> => ipcRenderer.invoke('state:clear-cash-out-hold'),
|
||||
markStatePublished: (unixTimestamp: number): Promise<void> =>
|
||||
ipcRenderer.invoke('state:mark-state-published', unixTimestamp),
|
||||
|
||||
|
|
@ -307,6 +320,9 @@ declare global {
|
|||
getLastStatePublishedAt: () => Promise<number | null>
|
||||
getCountsUncertainSince: () => Promise<number | null>
|
||||
markCountsUncertain: (unixTimestamp: number) => Promise<void>
|
||||
getCashOutHold: () => Promise<CashOutHold | null>
|
||||
setCashOutHold: (hold: CashOutHold) => Promise<CashOutHold>
|
||||
clearCashOutHold: () => Promise<boolean>
|
||||
markStatePublished: (unixTimestamp: number) => Promise<void>
|
||||
saveBunkerBinding: (binding: BunkerBindingRecord) => Promise<void>
|
||||
clearBunkerBinding: () => Promise<void>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue