2026-04-22. Two gaps in R1.3 closed in one cycle, both flagged in QA:
(1) PIN-collision: two cashiers sharing a random 4-digit PIN. Fix — two-step
Lock screen (cashier tile picker → PIN keypad), verify PIN against the explicitly
chosen identity only.
(2) Store Manager / Owner couldn't unlock registers (only PIN identities could). Fix —
a "Unlock as {web-user}" button on the Lock screen, visible only to users with
nix_cafe.pos.session_open permission. Spec §8.7 allows manager override via
"separate login" — the existing web session counts. No PIN needed.
verifyPinForIdentity by Alice's id resolves
to Alice (not Bob), and by Bob's id resolves to Bob. Deactivated cashier disappears from picker
and fails verify even with correct PIN. HTTP smoke on /cafe/pos/register/1 still 307.
cafe.sessions row
records opened_by_user_id (not opened_by_pin_id) when the manager path
is used.
lib/auth/active-cashier.ts — cookie now carries actorKind: 'pin' | 'user' + actorId. Legacy R1.3 {pinId} payloads still decode (upgraded in read).lib/db/pin_identities.ts — new listCashiersForRegister (shop-scoped + null-shop cashiers), new verifyPinForIdentity (verifies against one row, by id).lib/actions/register.ts — unlockRegisterAction now takes {posConfigId, cashierId, pin}. New unlockRegisterAsUserAction({posConfigId}) — gated on nix_cafe.pos.session_open, no PIN. openShift / closeShift write to opened_by_pin_id or opened_by_user_id based on cookie's actorKind.register-shell.tsx — Lock screen renders a green "Unlock as {manager-name}" button at the top (if web user has session_open), a divider ("Or pick a cashier"), then the cashier tile grid. Clicking the manager button bypasses the PIN keypad entirely.Loading…