fix(cassettes): say when the counts are unverified instead of reporting a guess
When the dispenser throws or the dispense times out there is no per-bay report, so nothing is debited — not the cassette rows, not HAL's bays. Bills may well have reached the customer, and both counters then read high with nothing to indicate it. The machine went on treating a number it had reason to doubt as measurement. A dispense that ends with no report now latches a countsUncertainSince flag, which rides along in the state document as counts_uncertain_since so the operator can see the numbers need a recount. The field is additive: a consumer reading positions ignores it, so this needs no coordinated release. An operator config apply clears the flag inside the same transaction, since asserting authoritative counts is precisely what a recount is. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
parent
54c59fadcc
commit
474903c38b
7 changed files with 130 additions and 2 deletions
|
|
@ -270,7 +270,14 @@ async function publishCassettesState(
|
|||
for (const c of cassettes) {
|
||||
positions[String(c.position)] = { denomination: c.denomination, count: c.count }
|
||||
}
|
||||
const ciphertext = await cfg.signer.nip44Encrypt(operatorPubkey, JSON.stringify({ positions }))
|
||||
// Additive field: an operator on the old consumer reads `positions` and
|
||||
// ignores this, so it needs no coordinated release. When set, the counts
|
||||
// above are the machine's best guess, not a measurement.
|
||||
const countsUncertainSince = await api.getCountsUncertainSince()
|
||||
const payload = countsUncertainSince
|
||||
? { positions, counts_uncertain_since: countsUncertainSince }
|
||||
: { positions }
|
||||
const ciphertext = await cfg.signer.nip44Encrypt(operatorPubkey, JSON.stringify(payload))
|
||||
|
||||
// Force the stamp strictly above our last one. Addressable events are ordered
|
||||
// by `created_at` at second granularity, ties broken by lowest event id, and
|
||||
|
|
|
|||
|
|
@ -633,6 +633,19 @@ export const useAtmStore = defineStore('atm', () => {
|
|||
bills = dr.bills
|
||||
.filter((b) => b.dispensed > 0)
|
||||
.map((b) => ({ denomination: b.denomination, count: b.dispensed }))
|
||||
} else {
|
||||
// The dispenser threw, or the dispense timed out, so there is no
|
||||
// per-bay report. Bills may well have reached the customer, and
|
||||
// nothing knows how many: neither the cassette rows nor HAL's bays
|
||||
// were debited, so both now read high. Record that the counts are
|
||||
// unverified instead of letting a number we know may be wrong go
|
||||
// on being treated as fact.
|
||||
console.error(
|
||||
`[ATM] Dispense ended with no report — bay counts are unverified (txid=${ctx.txid})`
|
||||
)
|
||||
void window.electronAPI
|
||||
?.markCountsUncertain(Math.floor(Date.now() / 1000))
|
||||
.catch((e) => console.warn('[ATM] Could not flag counts unverified:', e))
|
||||
}
|
||||
|
||||
persistTransaction({
|
||||
|
|
|
|||
3
apps/machine/src/types/electron.d.ts
vendored
3
apps/machine/src/types/electron.d.ts
vendored
|
|
@ -147,6 +147,9 @@ declare global {
|
|||
remediateTransaction: (txid: string, remediatedByTxid: string) => Promise<boolean>
|
||||
getLastKnownConfigCreatedAt: () => Promise<number>
|
||||
getLastStatePublishedAt: () => Promise<number | null>
|
||||
/** When the bay counts became unverified (a dispense that reported nothing), or null. */
|
||||
getCountsUncertainSince: () => Promise<number | null>
|
||||
markCountsUncertain: (unixTimestamp: number) => Promise<void>
|
||||
markStatePublished: (unixTimestamp: number) => Promise<void>
|
||||
saveBunkerBinding: (binding: BunkerBindingRecord) => Promise<void>
|
||||
clearBunkerBinding: () => Promise<void>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue