wake-alarm/phone_app/test/services
Krzysztof kuhy Rudnicki 150a8cd7e5 Require two tags, the walk, and three answers to dismiss
The dismiss gate was an OR — NFC tag *or* QR *or* (25 steps + 2 min) —
followed by one challenge. QR never worked at ring time (no scanner
package, no CAMERA permission, `onQrScanned` had no caller), so the real
bar was "walk 25 steps, wait 2 minutes, answer one question" and the NFC
tag was already fully bypassable.

It is now a strict, latched sequence owned by `DismissGate`:

    tag 1 -> 25 steps + 2 min -> tag 2 -> 3 challenges

Every stage is required, in that order, and a stage once passed never
reverts. That last part also fixes a real bug: `_onNfcTag` and `_trySteps`
both assigned the room-proof result straight onto `_inRoom`, so a wrong
tag tapped after the gate had passed sent the user back to the start.

Two *distinct* tags are required; the same id in both slots is refused at
registration, since one tag satisfying both stages removes the walk that
is the whole point. The existing single-tag registration migrates into
slot 1 rather than being stranded.

Answers are cumulative, not consecutive: a wrong one costs the attempt and
nothing else.

The bypass is the only fallback and is deliberately narrow. After 5 minutes
of ringing, or immediately when NFC is off or fewer than two distinct tags
are registered, the two *tag* stages are skipped and the count rises to 6.
The walk is never skipped — jumping straight to the challenges would let
someone lie in bed for five minutes and answer questions without standing
up, which is weaker than the gate this replaces. Without some such hatch a
dead tag at 06:00 leaves an alarm that cannot be switched off at all.

Also deletes the `flash` challenge outright, along with the now-orphaned
`generateCode`/`kDismissCodeLength`/`_dismissChars`. It was the only kind
that removed its prompt from the screen, which is unsolvable for someone
who has just walked in from another room.

The ring screen takes an injectable clock: `Stopwatch` reads the wall
clock, which `tester.pump()` does not advance, so without that seam no
widget test could reach the two-minute floor or the five-minute bypass.

Verified on the Pixel 6a: the existing tag migrated into slot 1 and both
rows render. 102 Dart tests pass; analyzer clean.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PcdngP688mW6rJye3LRkSN
2026-08-30 15:09:00 +02:00
..
alarm_policy_test.dart Make the alarm fully automatic and un-weakenable (08:00, earlier-only) 2026-08-27 23:03:00 +02:00
alarm_scheduler_test.dart Make the alarm fully automatic and un-weakenable (08:00, earlier-only) 2026-08-27 23:03:00 +02:00
alarm_sync_service_test.dart Discover alarm device directories instead of reading a fixed phone path 2026-08-09 17:06:43 +02:00
challenge_test.dart Require two tags, the walk, and three answers to dismiss 2026-08-30 15:09:00 +02:00
dismiss_gate_test.dart Require two tags, the walk, and three answers to dismiss 2026-08-30 15:09:00 +02:00
room_proof_settings_test.dart Require two tags, the walk, and three answers to dismiss 2026-08-30 15:09:00 +02:00
room_proof_test.dart Require two tags, the walk, and three answers to dismiss 2026-08-30 15:09:00 +02:00