2026-04-22. Four QA gaps in R1.3 closed in one cycle:
(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) Manager/Owner couldn't unlock: only PIN identities could open a register. Fix —
"Unlock as {web-user}" button visible to users with nix_cafe.pos.session_open.
(3) Unattended-terminal risk on manager override: first pass required no second factor.
Fix — new tenant_users.pos_pin_hash, inline "Set your POS PIN" dialog, and
mandatory keypad entry on the manager unlock path.
(4) Cashier-escalation via Set-PIN on unattended manager session: a cashier who
finds an open manager session could previously set a PIN of their choice and escalate. Fix —
setOwnPosPinAction now requires the caller's Commerce password (bcrypt-verified
against password_hash) before the PIN is written. Possession of the session cookie
alone is no longer enough.
tenant_users.pos_pin_hash column lands. (2) Manager POS PIN DAO round-trip —
two owners with the same PIN "1357" each verify only under their own user id; rotation
invalidates the old PIN; deactivated users fail verify even with correct PIN. (3) Cashier-side
collision test unchanged (11 assertions). (4) HTTP smoke on the fullscreen route still 307.
cafe.sessions records opened_by_user_id when manager path is used.
20260422100000_tenant_users_pos_pin_hash — nullable
tenant_users.pos_pin_hash (PBKDF2, same format as
commerce.pin_identities).lib/db/tenant_users.ts (new) — userHasPosPin,
setUserPosPin, verifyPosPinForUser. Hyperdrive-safe read.lib/actions/profile.ts (new) — setOwnPosPinAction({pin, confirmPin, currentPassword}).
bcrypt-compares the caller's password against password_hash before writing
the new PIN. Audit-logged.lib/auth/active-cashier.ts — cookie now carries
actorKind: 'pin' | 'user' + actorId. Legacy R1.3 payloads decode
upgrade-safe.lib/db/pin_identities.ts — listCashiersForRegister,
verifyPinForIdentity.lib/actions/register.ts — unlockRegisterAction({cashierId, pin});
unlockRegisterAsUserAction({pin}) now requires POS PIN.
openShift / closeShift write to opened_by_pin_id or
opened_by_user_id based on actor kind.register-shell.tsx — two-step Lock screen with three branches:
Manager override button (PIN set), Set-your-PIN CTA (PIN not set), and the cashier tile
grid. Keypad handles both cashier and manager targets.Loading…
Loading…
Loading…