-- §6.2 / v0.10.0: user-set passcodes after OTC (roadmap item #8).
--
-- After a successful OTC sign-in, a contributor may set a passcode
-- (numeric PIN or short alphanumeric). Subsequent sign-ins on the same
-- account can use email + passcode instead of email + OTC. OTC remains
-- the structural fallback — a forgotten passcode is recovered by
-- requesting a fresh OTC and signing in via that path. Per-account
-- lockout after 5 consecutive verify failures redirects the user to
-- the OTC path for 15 minutes; the OTC path itself is unaffected by
-- the passcode lockout (a locked-out user can still receive a fresh
-- code and sign in).
--
-- The columns are additive to the `users` table from `012_otc.sql`.
-- v0.8.0's `permission_state` column (roadmap item #6) lands in the
-- driver's integration order ahead of this migration; we do not touch
-- that column here. v0.7.0's nullable-`gitea_id`/`gitea_login` shape
-- is preserved verbatim.
--
-- Storage shape:
--
--   * `passcode_hash` (nullable) — bcrypt hash of the passcode.
--     NULL means "no passcode set"; the user is OTC-only.
--   * `passcode_set_at` (nullable) — timestamp of the most recent
--     `passcode/set` call. Updated when a passcode is set or
--     replaced; cleared when the passcode is removed.
--   * `passcode_failed_attempts` — count of consecutive failed
--     verify attempts since the last successful verify (or since
--     the lockout cleared). Resets to 0 on success and on lockout
--     expiry. Defaults to 0 so existing rows post-migration are
--     not implicitly half-locked.
--   * `passcode_locked_until` (nullable) — if populated and the
--     timestamp is in the future, passcode verify is refused with
--     HTTP 423. Cleared on successful verify after the window
--     expires, or by the operator via direct DB intervention if
--     ever needed (no admin endpoint surfaces this in v1).
--
-- v0.10.0 introduces no new env vars. The lockout window (5 attempts,
-- 15 minutes) is hard-coded in `backend/app/passcode.py`; raising or
-- lowering it is a future-§19.2 candidate. Passcode hashing reuses
-- the bcrypt dependency added in v0.7.0 for OTC; no new secret is
-- required (the existing `SECRET_KEY` continues to sign sessions).
--
-- Note on SQLite: ALTER TABLE ... ADD COLUMN is supported, so this
-- migration does not need the rebuild dance that `012_otc.sql`
-- required. The runner wraps each file in a single BEGIN/COMMIT
-- block — see `backend/app/db.py` — so either every ADD COLUMN
-- here lands or none do.

ALTER TABLE users ADD COLUMN passcode_hash TEXT;
ALTER TABLE users ADD COLUMN passcode_set_at TEXT;
ALTER TABLE users ADD COLUMN passcode_failed_attempts INTEGER NOT NULL DEFAULT 0;
ALTER TABLE users ADD COLUMN passcode_locked_until TEXT;
