Working backlog from a full-codebase review (main @ bd49310, v0.2.0), updated after the 2026-08 fix pass. Ordered by user impact. Security-relevant items live in security-review.md and are only cross-referenced here. Check this list before "fixing" surprising code — some quirks are shipped public API.
Access-token TTL is copied into the row (expires_in column); refresh-token TTL is computed from live config at check time, so changing the config re-times every existing token (see data-model.md). At minimum keep documented; ideally add a refresh_token_expires_in column for symmetry.
spec/requests/configuration_overrides_spec.rb covers authorization.location = :header/:params exclusively, sign_up.enabled = false, refresh_token.enabled = false and sign_up.extra_fields end-to-end; spec/requests/refresh_token_rotation_spec.rb and spec/requests/paranoid_mode_spec.rb cover rotation_enabled, paranoid and verbose_account_state; spec/devise/api/token_spec.rb covers expires_in_infinite procs and custom generators; spec/devise/api/configuration_spec.rb covers overrides on fresh instances. Still untested: custom authorization.key/scheme/params_key and base_token_model/base_controller overrides.
ErrorResponse#devise_lockable_info now emits both the canonical failed_attempts and the deprecated misspelled failed_attemps (kept for backward compatibility). Drop the typo at the next major release; changelogged.
The refresh action now returns invalid_refresh_token (400) for missing/unknown refresh tokens instead of the generic invalid_token (401). Breaking-ish for clients matching on the error symbol; changelogged.
Authenticate now returns invalid_email only when :email is one of the model's authentication_keys, and a new generic invalid_login (400, "Login credentials are invalid") otherwise. With paranoid enabled, both collapse into invalid_authentication.
Consolidated into Controllers::Helpers with memoization mirroring current_devise_api_token; the controller-level override was deleted.
#authenticate_service was never called and referenced params / resource_class, which didn't exist on this service — it would have NameErrord if invoked. Copy-paste leftover; deleted as part of the coverage push.
sig/devise/api.rbs declared only the VERSION constant; the sig/ directory was removed.
Standardized on Time.current (Token#expired?/#refresh_token_expired?/#revoke!, TokensService::Revoke).
Backfilled 0.1.0 → 0.2.0 from git history plus an Unreleased section for the current pass. Releases without changelog entries should fail review.
TokensController now uses private render_token_response / render_error_response / render_resource_owner_service_result / perform_refresh helpers; all rubocop:disable comments are gone.
Fixed the "orginally" typo, the rake rspec command, and the service example (.new(...).call instead of the non-existent class-level .call).
All six spec/services/** files now assert the monad contracts (Success/Failure per branch, per services.md), including the failure paths unreachable through the HTTP API (:invalid_resource_owner, :devise_api_token_create_error, :devise_api_token_revoke_error, sign-up transaction rollback).
rails g devise_api:install is covered by spec/devise/api/generators/install_generator_spec.rb (migration template rendering with the current Active Record version, unique token indexes, locale copy, migration numbering).