It authenticated through PAM, called unlock_and_destroy(), and returned
Unlocked — and then the shell, whose GlobalStates.screenLocked is a separate
bool, re-registered, was told must_lock=true, and locked again. The protocol
has a lock directive and no unlock directive, so the fallback could open the
compositor's lock and had no way to tell the session it had. Casey hit it
exactly that way: "it logs in, and then qs says Locked still."
It was also not a last resort in practice. It is raised whenever shell_alive is
false, and that flag lied for 90 minutes on 07-29 because a scene reload's
re-registration was refused and the shell gave up (45fbbea). So it was the
first thing reached, on a bad signal, to do a job it could not finish — the
fallback shape the doctrine forbids.
Gone: auth.rs, the PIN pad, the keypad geometry and digit font, the
failed-attempt counter, SessionOutcome::Unlocked, Msg::AuthDone, and the
--pam-service plumbing down to the CLI.
Kept: acquiring ext-session-lock before any shell exists, which was never a
fallback but the ordering invariant from LOCK-DPMS-LESSONS.md §1, and the
Released handoff. A locked session with no shell now stays locked and says so
in the journal. Recovery is the shell returning, or a reboot.
79 tests pass.