fix(anpr): abort the poll loop if the subscriber transacts by card/QR mid-poll
The poll-until-confident loop (prev commit) opened a race: during its ~8s window a subscriber could scan their card/QR at the reader and exit immediately — but the ANPR loop kept polling and would ALSO emit a confident read a moment later, exiting the NEXT open occurrence (a phantom double-exit, worst for a fleet sub with several open). Guard it with the subscriber's open-occurrence count: the bridge identifies the subscription as soon as a frame reads the bound plate (identity needs no confidence), baselines openOccurrenceCount, then each tick AND before emit checks if it moved. If a credential closed/opened an occurrence mid-poll, the subscriber already transacted → abort, don't emit. New public SubscriptionFlow.openOccurrenceCount(). Bounded loop is unchanged (ANPR_POLL_WINDOW_MS=8000 cap; never infinite). +1 test (credential transacts mid-poll → no double-act); 170 server tests green. Claude-Session: https://claude.ai/code/session_01Xcm6ikLgGoCxxHrxtjkk5V
This commit is contained in:
@@ -310,6 +310,15 @@ export class SubscriptionFlow {
|
||||
* subscription, (b) pick which occurrence a read closes, and (c) enforce
|
||||
* `maxConcurrent`. The on-chain field is `permitId`, so we match against that.
|
||||
*/
|
||||
/** How many occurrences this subscription currently has OPEN (entries not yet exited).
|
||||
* Public so the ANPR bridge can detect a credential (card/QR) exit landing mid-poll — if
|
||||
* the count drops while it's polling, the subscriber already transacted and the bridge must
|
||||
* NOT also emit (which would exit the NEXT open occurrence — a phantom double-exit, esp. for
|
||||
* a fleet sub). See anpr-entry.ts. */
|
||||
openOccurrenceCount(subscriptionId: string): number {
|
||||
return this.#openOccurrences(subscriptionId).length;
|
||||
}
|
||||
|
||||
#openOccurrences(subscriptionId: string): { identity: string; index: number }[] {
|
||||
const rows = this.#db.select().from(ledgerEvents).orderBy(ledgerEvents.index).all();
|
||||
// Net entries−exits per occurrence identity, keeping the entry order (oldest first).
|
||||
|
||||
Reference in New Issue
Block a user