# `drift/detect` _(no submodules)_ ## Purpose Search outside the regular verify window for clock drift. Callers use this to render UI hints like _"your device clock is 47 seconds fast"_ — the verify path with `window=1` would simply reject such a code, leaving the user with no idea why. `detectDrift` sweeps a wider range and returns both the signed step delta and the inferred clock offset in milliseconds. ## Features - Default `±60` steps (= ±30 minutes at the default `period=30`). - Returns `{ delta, suggestedClockOffsetMs }` discriminated union. - Constant-time across the sweep range — same defence as `verify`. - Honours the same TOTP option bag as `verify` (algorithm / digits / period / t0 / clock). ## Usage: Basic Example ```ts import { detectDrift } from "@suaveplan/otp"; const r = detectDrift(code, secret); if (r.delta !== null && Math.abs(r.delta) > 1) { show("Your device clock is " + r.suggestedClockOffsetMs + " ms off."); } ``` ## Usage: Advanced Example ```ts import { detectDrift } from "@suaveplan/otp"; const r = detectDrift(code, secret, { maxSteps: 120, // sweep ±60 minutes at period=30 clock: () => Date.now(), }); ``` ## API Reference - `detectDrift(code, secret, opts?): DriftResult` — both `delta` and `suggestedClockOffsetMs` are `null` when no candidate matched. ## Implementation Notes The negative-step guard is critical near time origin (t < `period * maxSteps`) because BigInt arithmetic does not raise on underflow; the guard lets the sweep skip unreachable negative steps without affecting wall-clock timing of the loop. The inferred `suggestedClockOffsetMs` is `delta * period * 1000` and is signed so the UI can render direction. This module is intentionally separate from `verify` because the two have different threat models: `verify` is on the hot path of every login and must be tightly bounded, while `detectDrift` runs only when `verify` already failed, sweeps a wider range, and exists to render a helpful error message rather than to authenticate. Callers should never accept a code on the basis of `detectDrift` alone — its purpose is diagnostic, not gating.