Skip to content

fix(lock): answer a refused finger from the resting view, and survive a wedged reader - #81

Merged
bhdai merged 1 commit into
mainfrom
feat-lock-fingerprint-recovery
Jul 29, 2026
Merged

fix(lock): answer a refused finger from the resting view, and survive a wedged reader#81
bhdai merged 1 commit into
mainfrom
feat-lock-fingerprint-recovery

Conversation

@bhdai

@bhdai bhdai commented Jul 29, 2026

Copy link
Copy Markdown
Owner

Two faults found running the locker on the real desktop after the cutover
(dotfiles#3). Both are in the fingerprint
factor; neither was reachable from the nested harness, because one needs a resting view a
real finger is presented to and the other needs the reader to physically misbehave.

A refused finger was invisible while resting

LockFingerprint is built inside the container gated by opacity: root.revealed ? 1 : 0
(modules/lock/LockSurface.qml:213), so from the resting view a rejection's shake and its
"Fingerprint not recognized. Try again." rendered at zero opacity. A win from resting
already worked — the comment at :210 says the reader "can win from the resting view
without any of this being on screen" — but a refusal was never given a resting answer, and
a wrong finger was indistinguishable from a dead sensor.

Result.MaxTries now carries Effect.RevealAuth alongside its re-arm, so a refusal opens
the surface exactly as typing does. That puts the shake and the copy where they are legible
and lands the user on the factor that still works, rather than reporting a dead end they
cannot act on.

It is the only outcome that reveals, and there is a test pinning that. A success tears
the screen down anyway, and every other result is the reader failing rather than the user
being told no — a reader that vanishes must not pop the password field open on a screen
nobody is standing at.

A wedged reader was given up on permanently

Measured on this machine rather than reasoned about. Repeated rejections drove enough
claim → verify → release cycles (one per attempt, under max-tries=1) that a release timed
out and the reader dropped off the USB bus:

21:13:31  quickshell: pam_fprintd: ReleaseDevice failed: transfer timed out
21:15:07  kernel:     usb 3-6: USB disconnect, device number 2
21:15:07  kernel:     usb 3-6: new full-speed USB device number 10
21:15:08  fprintd:    Ignoring device: No such device (may have been disconnected)

The reader was back about a second later. But the refusal that arrived during that window
latched fingerprintStopped on the first try, so the chip stayed Absent for the rest of a
lock the hardware was healthy for — recoverable only by locking again.

Stops now spend a finite backoff (2s, 5s, 10s) before latching. The budget is bounded
because an unslowed retry is the fork loop the original stop existed to prevent: a missing
quickshell-fprint policy refuses instantly and forever, and only exhausting a budget tells
that apart from hardware coming back. Any PAM message resets it, since the module speaks
only after claiming a working reader.

fingerprintAction's contract is unchanged — StopFingerprint still means "do not re-arm
now". Only the applier's reading of it changed, so the fork-loop reasoning in
LockLogic.js and its tests stand.

Tests

Written first, all five red before the change (ReferenceError: fingerprintRetryDelay is not defined, then the reveal assertions):

  • a non-matching finger reveals the auth area
  • only a refused finger reveals the auth area — every other result and both sawMessage values
  • a stopped reader is retried on a backoff before it is given up on — strictly increasing
  • the fingerprint backoff is finite — the fork-loop guard
  • the backoff outlasts a reader re-enumerating — pins the budget against the ~1s gap measured above

node --test: 294 pass, 0 fail (was 289). All five lock QML fixtures pass. The full
shell loads clean in the nested compositor — Configuration Loaded, zero errors — which is
what compile-checks services/Lock.qml, since no fixture can build it (it needs
WlSessionLock).

One existing test name was corrected: "stops the reader permanently" is no longer true
of the effect, only of the exhausted budget.

Not verified here — needs a human and a finger

I have no display and cannot present a finger, so neither fix has been seen working:

  1. From the resting view, present a wrong finger — the surface should reveal, and the
    chip should shake and say "Fingerprint not recognized. Try again."
  2. Present the right finger from resting — should still unlock without revealing first.
  3. The wedge is not reliably reproducible (one occurrence in 14 days of journal), so the
    backoff is best confirmed opportunistically: if the pill vanishes mid-lock again, it
    should come back within ~17s instead of staying gone until the next lock.

… a wedged reader

Two faults found running the locker on the real desktop.

A rejected finger was invisible while resting. The chip renders inside
the revealed tier, so its shake and its copy were drawn at zero opacity
and a refused finger was indistinguishable from a dead sensor. MaxTries
now also reveals, which puts the refusal where it is legible and lands
the user on the factor that still works. It is the only outcome that
reveals: every other one is the reader failing rather than the user
being told no, and a reader that vanishes must not open the password
field on a screen nobody is standing at.

A stop was permanent from the first refusal. Measured here: repeated
rejections drove enough claim/release cycles that a ReleaseDevice timed
out, the Prometheus wedged, and it fell off the USB bus and re-enumerated
about a second later. fprintd reported no device for that window, so the
refusal arrived while the hardware was merely mid-reset -- and the chip
then stayed absent for the rest of a lock the reader was healthy for.
Stops now spend a finite backoff before latching. The budget is bounded
because a missing quickshell-fprint policy refuses instantly and forever,
and only exhausting it tells that apart from hardware coming back; any
message resets it, since a module that spoke has claimed a real reader.

Refs #62
@bhdai
bhdai marked this pull request as ready for review July 29, 2026 14:55
@bhdai
bhdai merged commit 0949942 into main Jul 29, 2026
3 checks passed
@bhdai
bhdai deleted the feat-lock-fingerprint-recovery branch July 29, 2026 14:55
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant