Supporting reference · Working design and evidence, not a promise of complete support.

On this page

Rails authentication

Status: Implemented for the Field-only public Web Account boundary. Registration meaning outside it is listed as partial gaps.

Account meaning lowers to Rodauth Rails with email and password, plus self-service registration, email verification, password reset, and lockout when the Account requests each. Public signup collects the Account's required Fields with Scaffold-style controls, and the GapSet lists registration meaning it cannot generate. Generated Rails views and a generated mailer replace Rodauth's built-in rendering. Before Rodauth inserts the Account, a hook runs the full Active Record validation stack on the submitted product values. Every realized Account receives one authenticated /account page that reads current_account. Every sign-in is remembered with Rodauth remember, in browsers and native clients alike. Selected iPhone and Android clients receive the Account entry and sign in through the generated forms; Native Accounts describes the flow. Two private qualifiers exercise Association registration inputs and an iPhone session. The Accounts owner (repository-only) states what Account topology means.

On this page

Account prerequisite

Support: see the support inventory row (repository-only).

Model Account semantics first and lower them with Rodauth Rails in the first profile. The pure prerequisite accepts every email/password topology, including the ordinary representative corpus and the reduced Movie Catalog fixture, retains Analyzer-valid registration inputs for the Account Entity, and qualifies the fixed runtime namespace: Account columns, password methods, feature-dependent model Associations, derived nested constants, top-level runtime constants, and middleware routes. Model members and top-level constants are checked against target and Project claims. Nested constants are self-checked against the Rodauth Associations. Middleware routes are checked against the pinned target route namespace; general Project-route qualification remains deferred. Ordinary public input consumes this result for the Field-only Web boundary below.

AccountLowering is the target prerequisite shared by ordinary public input and the private qualifiers. It consumes the exact ordered Domain catalog from one sealed Project generation, a valid same-generation Reference catalog, and an existing Account registration-analysis result without SQL. It admits every email/password Account topology, enabling each optional flow only when requested, and retains Analyzer-valid registration inputs for that Account Entity. Ordinary public input then applies the stricter Field-only projection below; private qualifiers retain their reduced fixture shapes. Its immutable result distinguishes a declined Plan from a selected but blocked Account, retains the registration-analysis result as evidence, and owns only target-lowering diagnostics. The Account value exists only when both stages succeed.

The value retains the Account Entity, analyzed default bindings, observed conventional Rodauth dependency baseline, fixed feature set, target-owned storage, runtime namespace, and migration rank after Domain References. Its namespace claims the Account columns, password interface, feature-dependent model Associations, derived nested constants, fixed top-level constants, and middleware routes. The target-owned top-level set includes RodauthApp, RodauthController, RodauthMailer, and RodauthMain. Focused analysis tests source-address Project member and constant collisions, self-check the nested constants, and reject middleware overlap with the pinned target route namespace. Every Account adds the application-wide remember feature and its table. Selecting iOS or Android adds the /native-session-changed middleware route on every path.

A complete Rails lowering for Account meaning would derive:

The first pure target prerequisite now derives the Account Entity's table, reserved email and account_status columns, one authentication migration slot, and the auxiliary table names of the requested flows. All auxiliary names use the Account Entity stem, including user_password_hashes; the emitted configuration overrides each of Rodauth's independent account_* table defaults explicitly. Within the current reduced Domain slice, the prerequisite permits Analyzer-valid registration inputs and retains each source binding's identity, authored position, source pointer, and requiredness plus every analyzed default binding. Every Account enables i18n, login, logout, remember, change_login, change_password, and the target-chosen disallow_common_passwords safeguard. Registration adds create_account, recovery adds reset_password, and lockout adds lockout. Verification adds verify_login_change, and also verify_account when the Account has registration. Each optional feature brings only its own auxiliary tables, nested models, and routes. It derives and self-checks the Rodauth model's nested Association constants without claiming a general nested-constant namespace. The top-level set includes dependency-owned BCrypt, Roda, Rodauth, and Sequel plus RodauthApp, RodauthController, RodauthMailer, and RodauthMain. A Domain Entity such as Sequel therefore blocks the current Account qualification before source emission. That is a Rails target gap, not invalid Account or Entity meaning in the Foundation Plan; a different or later lowering may omit affected output or rename only compiler-private machinery while preserving public vocabulary.

That prerequisite records the exact dependency versions exercised by the conventional application: Roda 3.105.0, Rodauth Rails 2.1.2, Rodauth 2.44.0, and their named authentication support dependencies. Generated applications use Rodauth Rails 2.2.0 and Rodauth 2.48.0, whose real source still exposes the conventional storage configuration and remember APIs. Rodauth 2.48.0 includes the 2.47.0 security fixes, which make each emailed key work once under concurrent requests. The older observed lock remains a reproducibility fact for the conventional application. The isolated generated bundle routes Gemfile and Gemfile.lock through the application-wide dependency plan. Account contributes its complete immutable Gemfile block, four direct requirements, empty prerequisite list, and source identity but owns neither file. ApplicationTaskPlan retains one plan: Core returns its exact pair without projection, while a nonempty contributor set is inserted once at Core's dependency marker, projected exactly once by the exact vendored Bundler 4.0.10, and closure-verified against the maximal universe. No production Account Compilation consults an overlay. The resulting Account selection pins bcrypt 3.1.22, Roda 3.106.0, Rodauth 2.48.0, rodauth-i18n 0.11.0, rodauth-model 0.5.0, rodauth-rails 2.2.0, Sequel 5.107.0, and sequel-activerecord_connection 2.0.1.

Public registration

Public Account realization first requires one email identifier, one password sign-in method, and a successfully qualified Account-self surface. Registration, verification, recovery, and lockout may each be present or omitted; the projection below is empty without registration. The Accounts owner (repository-only) lists the signup members and the partial gaps for registration meaning the target cannot generate. An Association input, a required Reference, or an unsupported input no longer leaves the Account unrealized. Ordinary output gives selected native clients the same navigation as Web, Account self included, with or without email verification. Public Policy admission consumes this exact Account result and admits no Policy root without it. When Account and admitted State Machine behavior coexist, the dependency planner composes their qualified Gemfile blocks and projects one complete lock from the maximal universe. When admitted public Policy rules also coexist, the same projection adds Action Policy 0.7.6, ruby-next-core 1.2.1, and AASM 6.0.0 without a combination lookup or lockfile-section splice.

Ordinary input builds an immutable registration projection from the authored inputs it can generate, followed by a derived input for every other required stored Account Field that the model does not fill. Each input carries the Scaffold control kind for its Field: short_text and time_zone keep their text inputs, an enum becomes a select of its values, and the other stored kinds use the Scaffold helper for that kind. The projection also carries one omission per unsupported subject, which becomes a foundation_plan.gap.account.partially_generated record. Before the Sequel insert, the hook assigns every input, runs validations, and copies each input plus each required State Machine, realized-default, secure token, or appended position Field from the Rails model into the insert. When an omission blocks signup, such as a required Reference, a failed validation reports the model's full messages instead of a generic invalid-input message. Such a signup cannot succeed for any input, so it conflicts with the code-worth-keeping requirement. Omitting signup would leave no way into the application, so the owner decided that format 0.24 will reject (repository-only) a required Account Reference that signup cannot supply. A proposed Skill change teaches group membership in its place. Ordinary input then qualifies the exact Account self result and carries all three values as input-owned evidence. The default /account edit form uses the same control kinds. When an admitted State Machine and Account both contribute dependencies, the dependency plan composes their qualified Gemfile blocks and projects one complete lock from the maximal universe.

Account self and navigation

Every realized Account path derives one browser Account entry. ApplicationNavigation preserves its semantic person icon and fixed /account destination. Web navigation places the localized Account link after resource entries and before Sign out. It renders only for current_account, which exists only when Rodauth reports logged_in?. All Account controller actions use that current record, never a supplied record ID.

Without an authored profile, /account shows signup Fields and the normalized email. Without an authored update, /account/edit and PATCH /account permit the mutable, non-derived signup Fields. An update-only customization replaces editing inputs and the update Policy while retaining signup details. Custom scaffold.profile instead supplies the displayed details and collections, guarded by its read Policy. Display and edit selections are independent. A denied profile hides its details and queries while retaining the Account destination and credential links. Unsupported custom definitions remain gaps and do not activate permissive defaults. The serialized return route profile resolves to Account. Native clients reach the same /account page; for a Hotwire Native request its credential actions also include Sign out, because the native layout omits the web header.

The toolbar uses stock ghost actions with matching Lucide icons for Edit details, Change email, and Change password. Credential links use Rodauth's change_login, verify_login_change, and change_password routes. Email change retains the existing address until verification at the new one. The login_change_keys auxiliary table, nested Rodauth model, and mailer follow rodauth-rails 2.2.0's generator and Rodauth 2.48.0's features. Rodauth's own key lookup enforces the one-day email-change deadline and locks the key row, so a link works once. Profile fields remain ordinary Active Record updates with Rails errors.

The pure AccountSelf qualifier consumes that exact Account lowering and Domain catalog. It derives the fixed /account path, account helper, AccountsController, accounts view namespace, account-self:<Account subject_uuid> identity, Account Entity label, semantic person icon, and source provenance. It rejects collisions with the target namespace, Account authentication middleware routes, Project constants, public-index claims, and the private Policy entry. ApplicationNavigation combines this optional result with the exact public-index projection and preserves same-generation diagnostics. Its semantic entries contain no SF Symbol, tab-bar, or other iOS-specific fact.

Generated files

The path emits seven runtime files and up to 21 web-flow files, including a custom mailer and up to four text templates. Each omitted flow omits its own views and mail templates, and Accounts that send no mail have no mailer. The default Account renderer owns the controller, show/edit/form views, shared credential/default-detail partials, and locale; edit/form are omitted when no editable fields exist. For admitted custom profile/update definitions, Scaffold renderers supply those four controller/view files while Account retains its shared partials and locale. The Account runtime replaces DEPLOY.md with a provider-neutral mail launch prerequisite and restores the Account clauses that Core's Account-free Terms omit, in the Terms view and its locale text. A separate schema-quality renderer owns .active_record_doctor.rb. It retains Core's global exceptions for the frameworks the application loads and adds detector-specific exceptions for the exact Rodauth auxiliary tables, nested models, and login/status attributes. Its missing-presence exceptions also name each required numeric Field and AASM-managed state attribute: active_record_doctor 2.0.1 recognizes neither numericality nor AASM's native validation. AASM deliberately relies on initialization and NOT NULL for internal nil state errors. No extra validator is emitted to satisfy the detector. Its length exceptions name each string or text attribute whose first length maximum differs from its column limit, such as a long_text maximum, because the detector reads only column limits. Data integrity owns which rules receive a limit. Fields owns the exceptions for counter caches and for Active Storage's tables and record index. Without any such exception, the Core config keeps its detectors and drops only the framework models the application does not load. Account-free output retains Core's DEPLOY.md and Terms. Separately, the conditional ErrorsController renderer replaces Core's error controller with the same controller plus a current_account helper returning nil, allowing public error navigation without an account lookup. Account-free output retains Core's error controller. The application-global ApplicationController plus generic Domain-route and main-navigation renderers retain their shared ownership. Account does not claim Core's application layout. Appearance replaces it for an omitted theme or light, dark, or auto; toggle retains the Core layout. Realized Account navigation does not transfer the stylesheet; Core owns its responsive rules. Appearance also selects the branding and theme-behavior paths that Appearance lowering describes. The iOS variant additionally lowers the same ApplicationNavigation entries and composes generated iPhone source without changing Rails dependency selection. Ordinary input lowers the same navigation for iOS and Android. When a client is absent, its projection declines with no native output while the Web self surface remains. Android remains excluded from the private Account qualifier. The smoke proves deterministic materialization, frozen dependency installation, asset builds, boot, migration/schema parity, password-hash storage and verification, the configured identifier label and auxiliary-table names, and cascading cleanup. On both runtime legs it drives freshly rendered authenticity tokens through browser and native-user-agent Rails integration sessions. Public Compilation realizes the Web Account only for the Field-only boundary above. Its separate Policy lowering consumes that realized Account context but does not widen registration. Name collisions remain ordinary-success omissions in the reviewed GapSet.

The runtime and web-flow Account families return no tasks unless the context carries the exact input-owned Account realization; otherwise they select seven runtime paths and the requested flows' web-flow paths. The runtime family conditionally replaces DEPLOY.md with the provider-neutral Account mail launch prerequisite and the Terms view with its Account clauses. One separate schema-quality renderer owns .active_record_doctor.rb, combining exact Rodauth exceptions with the required numeric, AASM-managed state, and model-only length attributes that its detectors do not recognize, and with the counter-cache and Active Storage exceptions that Fields owns. The Account-self family selects its default page and shared partials from that input's exact self projection; admitted profile/update definitions supply the custom controller and views.

The artifact emits the retained plan's verified Gemfile and Gemfile.lock through generic one-path dependency renderers, plus the authentication migration and schema projection, Account model integration, fixed Rodauth application and initializer configuration, Rails ERB forms, Account-aware navigation, a custom RodauthMailer, four text email templates, localized interface text, and the credential-redaction request spec. The conditional runtime and web-flow renderers own these paths. Account-only output exposes private current_account and authenticate methods backed by rodauth.rails_account and rodauth.require_account. A private Policy gate or public Policy lowering keeps those helpers and adds nullable Action Policy user context plus the narrow ActionPolicy::Unauthorized response (repository-only). Generalized Web Scaffold controllers consume that context for their protected actions and custom Account details. The Account destination itself requires authentication. Runtime tasks replace Core files only for an input-owned Account realization. Generated request examples cover the default Account page's guest guard without duplicating an authored-profile example. Constructible registration setup submits all selected scalar inputs, checks that missing Terms persists no Account, and checks assignment with at least one value distinct from its registration default. With verification, the same new-Account example checks refusal to sign in before email verification; without it, the example checks that the new Account reaches /account signed in. These examples exercise application-owned registration wiring; generic Rodauth recovery, token, and mail behavior stays with upstream and Service qualification. When bounded setup cannot supply valid or distinguishable values, the generated README names the missing coverage. Account-free output retains those Core files byte-for-byte. Its generic shared-navigation seam stays immutable when Appearance is absent; authored Appearance is the sole conditional whole-file owner.

Create-account form

The public create-account view renders every admitted Field control in authored order and marks it required. Before product assignment, an ordinary Rodauth before_create_account check requires terms-accepted=1 from the initially unchecked Terms checkbox. Its label links to public /terms; /privacy is a nearby notice. Missing or unchecked acceptance returns a localized, accessible 422 field error before any Account/authentication write or verification work. The signup links depend on Core's pages.terms.heading and pages.privacy.heading translations as well as terms_path and privacy_path; preserve these names when selecting or updating the Core pin.

Before Rodauth's Sequel insert, the generated hook assigns product input to Rodauth's existing rails_account object and runs its full Active Record validation stack. Sequel does not invoke those normalizations or validations. Both assignment and failed-form redisplay use Rodauth's param_or_nil, retaining its configurable max_param_bytesize (1024 bytes by default) and NUL handling. Non-string and invalidly encoded input is rejected before the accessor can stringify it. Missing input may use its analyzed default; a present rejected value never does. Defaults fit the pinned 1024-byte request boundary so their forms can round trip. The Rails model owns authored length rules after normalization; registration adds no character cap. With CSRF protection enabled, Rails' ActionDispatch parameter-encoding check rejects malformed UTF-8 form input with 400 before the hook. An invalid product value returns 422, owns its Field error, and rolls the Sequel transaction back before Account/authentication writes, and enqueues or delivers no mail. Valid normalized values are copied into the one base insert. Failed-insert handling keeps the submitted Rails model in a local variable and checks the login with Rodauth's _account_from_login, because since Rodauth 2.47.0 a route that loads a second account in place of its new one raises. It then attributes product uniqueness errors to that candidate. Create, verification-resend, and reset-request paths use generic direct HTML outcomes; executable tests prove equal redirect status, destination, notice, and alert text for their exercised eligible/ineligible pairs. A reset form POST whose key is used, expired, or invalid gets the same generic outcome, because Rodauth sends that failure through reset_password_email_sent_redirect. Concurrent eligible resend/reset requests are not single-flight: more than one local/queued message can be produced. HTTP response parity is normalized, but timing, rate limiting, duplicate-send suppression, and broader abuse controls remain unproved. Generated login and unlock-request responses still distinguish known from unknown email identifiers, so create/resend/reset normalization does not close application-wide enumeration. Field errors use shared field-errors composition and the stock destructive theme token.

For ordinary public output, the create-account view maps all admitted Field controls in authored order and marks each required. Before the base Sequel insert, the generated hook assigns the request values to an Active Record Account and runs its full validation stack. Invalid input returns 422, attributes errors to the authored control, rolls back before any Account/authentication write, and produces no mail job. Valid normalized values are copied into the one Sequel Account insert. The generated create, verification-resend, and reset-request responses use generic notices, and the deep public smoke proves equal redirect status, destination, notice, and alert text for its eligible and ineligible pairs. Concurrent eligible resend/reset requests are not single-flight: more than one local/queued message can be produced. HTTP response parity is normalized, but timing, rate limiting, duplicate-send suppression, and broader abuse controls remain unproved. Generated login and unlock-request responses still distinguish known from unknown email identifiers. Inline errors use the shared field-errors composition and destructive theme token.

All controls are required; a Field's analyzed default appears only when the parameter is absent, while an explicitly blank submission remains blank and invalid. The public representatives cover one short_text, two short_text Fields, and a short_text plus time_zone. Product text uses Rodauth's configurable max_param_bytesize, whose pinned default is 1024 bytes. This byte limit is a request boundary. Applications can customize Rodauth's normal input handling in their emitted source. Failed forms retain accepted parameter values and never redisplay either password. The Appearance renderer derives a destructive text color with sufficient contrast against its surfaces.

Self-service Web signup also requires an initially unchecked Terms acceptance checkbox. Its label links to the public /terms page, and the nearby /privacy link is a notice, not another consent request. The ordinary Rails checkbox submits terms-accepted=1 when checked and 0 when unchecked; a missing value is also rejected. The hyphenated request parameter cannot collide with an authored Field key. Rodauth's existing before_create_account hook requires the exact checked value before assigning product inputs or saving the Account. Its throw_error_status API returns a localized field error associated with the checkbox. Rejected submissions retain safe product and email input, clear both passwords, and write no Account, authentication row, or verification mail/job. Checked submissions retain the existing validation, normalization, and duplicate-login behavior. The checkbox remains checked after an unrelated field error. Programmatic clients of the signup endpoint must submit the same value; direct model, factory, and seed creation are unaffected. This is a signup requirement with no stored acceptance/version history or reacceptance workflow.

Before Rodauth executes its Sequel Account insert, the generated hook assigns every admitted product value to Rodauth's existing rails_account object and calls its complete Rails validation stack. This follows the upstream model-validation integration. Sequel's insert does not run Active Record normalization or validation, so that pre-insert check and the normalized-value copy remain necessary. The pinned Rails integration provides the unsaved model; no separate registration model or instance variable is needed. Invalid product input reports Field-owned errors and marks the Sequel transaction for rollback before any Account or auxiliary row can be written. Valid normalized values are then copied into the Sequel Account hash for the one base insert; no callback or post-create update repairs the row afterward. Submitted authentication-owned columns such as account_status remain ignored. An existing login keeps the generic create response; a product uniqueness collision revalidates the submitted candidate and reports its Field-owned errors. Rodauth 2.48.0's parameter handling owns the byte and NUL handling used by both registration assignment and redisplay.

Rodauth configuration

The explicit path emits an Account migration and complete schema projection, Account-only Rodauth model integration, fixed Rodauth application and initializer configuration, a Core-qualified dependency pair through the generic dependency tasks, Rails Account forms, Account-aware navigation, a custom mailer with four text templates, and localized interface text. Its runtime family replaces DEPLOY.md with the provider-neutral mail launch prerequisite; Account-free output retains that Core file. The separate schema-quality renderer combines exact Rodauth exceptions with required native numeric, AASM-managed state, and model-only length attributes, and with the counter-cache and Active Storage exceptions that Fields owns. When none of those exceptions is needed, it keeps Core's .active_record_doctor.rb detectors and drops only the framework models the application does not load. The generic ApplicationController task owns that global Core path for every application. Without Account qualification it copies the exact Core bytes with empty subject provenance. With qualification it extends those bytes with current_account and authenticate; the first returns rodauth.rails_account only when Rodauth reports logged_in?, and the second delegates to rodauth.require_account. The task records the Account Entity as provenance. The configuration realizes the authored identifier name as Rodauth's login label. It keeps built-in Tilt rendering disabled and constructs each requested flow's mail through the generated mailer. Its emitted send hook wraps deliver_later in Sequel's db.after_commit callback. Rodauth errors use Rails' alert flash key so they enter the Core's assertive live region.

The emitted configuration uses Rodauth 2.48.0's documented already_logged_in hook as already_logged_in { redirect login_redirect }. Login, registration, verification, reset, and unlock routes call it before processing an active Account's request. Signed-in GETs and CSRF-valid POSTs return to the existing login destination, currently /, without processing another identity. Recovery starts signed out. Rails rejects missing or pre-login-session CSRF tokens before the hook runs.

Guest destinations use two settings from the rodauth-rails 2.2.0 generator. With login_return_to_requested_location? true, require_account remembers a signed-out GET path, so a guest sent to sign-in from a protected page returns there afterwards. reset_password_redirect sends a completed password reset to sign-in, and create_account_redirect and default_post_email_redirect do the same for a guest after registration or an email request. The step's notice therefore stays visible even when Home requires an Account. A signed-in Account keeps the existing destination.

Generated Account forms use Core's centered card, stock large submit control, and exactly one in-card pair of persistent flash regions. Field errors stay attached to their controls and submitted passwords stay blank. The documented login_failed_reset_password_request_form hook returns nil: Rodauth's ordinary Forgot Password link opens the standalone recovery page instead of embedding that complete page inside failed Login. The built-in login header and footer hooks remain available. Sign-in and sign-out use login_notice_flash nil and logout_notice_flash nil, matching Core's choice to let the destination communicate those outcomes. Email next-step notices remain persistent guidance. Account settings uses the shared detail card and a quiet password-change action.

Account settings links to the built-in change_password feature. Its Rails form uses Rodauth's current-password, new-password, and confirmation parameters with the existing Account field partial. Rodauth checks the current password, confirmation, and password requirements, then changes the stored hash and redirects to /account. Since Rodauth 2.46.0, a successful change also calls clear_tokens(:change_password). That deletes any pending reset or email-change key and the Account-wide remember key, so remember cookies held by other clients stop working. The current session stays signed in, and load_memory gives it a new remember key at its next hourly renewal. Other open Rails sessions also stay signed in. This is the library's conventional password-change behavior; it is not an Account-wide sign-out action.

The emitted web configuration sets Rodauth's login label from the authored identifier name, limits normalized email login to 254 characters, checks trim and case in Postgres, admits internal statuses 1 and 2, uses password bounds of 15 characters and 72 bytes, enables common-password rejection, and permits ten invalid logins. Verification, reset, and email-change keys expire after 24 hours; lockout state has a separate 24-hour deadline. Expired verification lookup is read-only until loaded-Account resend rotates the key. Each emailed key works once, even when two requests present it together: inside the route's transaction, Rodauth locks the key row with FOR UPDATE, and the generated verification lookup takes the same lock. A form POST reads its key only from the session that the link's GET set. A completed verification replaces the key instead of deleting its row. Token IDs that are not UUIDv7 stop before SQL: the emitted convert_token_id rejects them for emailed keys, and remembered_session_id rejects them for the remember cookie. The verification-resend and reset-request hooks load the Account before Rodauth's route loads it again, so each eligible request logs Rodauth's repeated-retrieval warning. Every variant emits a one-year remember deadline, no /remember route, and an HttpOnly, request-secure, SameSite=Lax cookie at /. With extend_remember_deadline? true, load_memory moves the deadline a year out on each restoration and at most hourly while a remembered client stays signed in, so people who keep using the app stay signed in. As in the rodauth-rails generator, after_login remembers every login, and an autologin account creation is remembered too; the login form has no "Remember me" checkbox. load_memory runs before ordinary Rails routes, and explicit logout removes the Account-wide server key. When its hourly renewal falls due on an email-change confirmation, it loads the Account by session before the route loads it by key. Since Rodauth 2.47.0 that second load raises, so the emitted account_reload allows exactly that pair and the link still works. A native login, autologin account creation, or logout then redirects to /native-session-changed so the native shell restarts its tabs; browsers keep their ordinary redirects. Its built-in signed-out reset calls clear_tokens(:reset_password), whose remember extension removes the Account-wide key. Former clients' stale remember cookies are rejected and expired when next presented without a Rails session. No custom post-reset hook is needed for that flow. Existing Rails sessions are not revoked by this configuration. The native redirects depend on the declared Hotwire Native user-agent marker, not device attestation. The smoke supplies that marker directly rather than deriving it from the emitted iOS shell.

The artifact keeps built-in Tilt rendering disabled and uses its generated Rails views and RodauthMailer. The emitted configuration registers verification, reset, and unlock deliver_later calls through Sequel's db.after_commit; focused tests pin those exact bytes, while the smoke does not independently observe enqueue ordering. Rodauth errors map to Rails' alert flash key. Generated DEPLOY.md requires an owner to configure a production delivery method, verified sender, canonical HTTPS link host, queue operation and retention, SPF/DKIM/applicable DMARC, monitoring, and external verification, reset, and unlock delivery proof. The profile does not select or prove a provider.

Every Account requires Rodauth's application-wide remember feature and an Entity-scoped remember table. This follows the rodauth-rails generator, which remembers all logged-in users and extends the deadline, and the Rails 8 authentication generator, which stores every session in a permanent cookie. The earlier conventional experiment remembered only native logins; this prerequisite follows its one-year deadline and renewal, matching Joe Masilotti's advice to treat native sign-in as signed in forever, and keeps its own cookie settings. The configuration sets no fixed maximum since the last password sign-in, although NIST SP 800-63B-4 recommends one of at most 30 days for password-only (AAL1) sessions. Rodauth keeps one remember key per Account, so a later login on any client reuses that key and its current deadline. Explicit logout reads the Account identifier before the session is cleared and deletes the single server-side remember row, which revokes remember cookies held elsewhere. A Rails session still open on another browser or app stays signed in, and its next hourly renewal issues a fresh remember key; a password reset leaves those sessions the same way. Hotwire Native is selected by the client-declared user-agent marker, not an authenticated device signal; it now chooses only the session-change redirects.

The Account iPhone session smoke (repository-only) compiles the Movie Catalog Plan through the private qualifier and runs one XCUITest in an iPhone Simulator. It observes native WebKit login, the Account tab's /account page before termination and after restoration, deliberate Rails-session loss, same-Account remember restoration that extends the deadline, restored logout, a second login, and a clear-both signed-out control. Login and logout redirect through the session-change reset, which reopens the public Movies tab, so the test reads the signed-out state from the Account tab's sign-in form. Ordinary Compilation of the same Plan realizes the same Account and remember feature and omits only the Favorite movie signup input, which the test never uses. The runner checks that before building. The smoke is not physical-device, signing, release, production, hosted Compilation, or authored-profile evidence.

The emitted configuration sets Rodauth's login label from the authored identifier name, limits login identifiers to 254 characters, normalizes trim and case, and backs that choice with a database check and unique index. Reset, unlock, and email-change lookups are Rodauth's own, including its bounded deletion of expired reset and email-change rows. These emitted configuration and storage boundaries remain covered alongside the browser journey.

Accounts without verification

An Account that omits verification lowers to the same configuration without Rodauth's verify_account and verify_login_change features. The emitted application has no verification or login-change key tables, nested models, routes, views, mail templates, or mailer methods, and no verification-specific hooks or notices.

The unverified Account smoke (repository-only) compiles the unverified probe Plan (repository-only) and runs the generated specs. It then drives CSRF-protected signup to a signed-in /account, a duplicate signup, a rejected and an accepted email change, password reset, and lockout with unlock, using the test mail adapter.

Accounts without registration, recovery, or lockout

Registration, recovery, and lockout each switch one Rodauth feature, as verification does. An omitted flow emits no feature, auxiliary table, nested model, route, view, mail template, mailer method, locale entry, or hook for it. Policies over current_account and the pages they guard still lower, because they need only a Rodauth session.

DEPLOY.md names only the requested mail flows. Without verification, recovery, or lockout it says that Accounts send no mail, and the configuration omits send_email and the email-token convert_token_id hook.

The no-signup smoke (repository-only) compiles the no-signup probe Plan (repository-only) and runs the generated specs. It creates an Account with the README's runner recipe in the production environment against the local database, signs in, uses a Policy-guarded page, confirms an email change by mail, resets the password, and locks and unlocks the Account. The no-recovery smoke (repository-only) compiles the no-recovery probe Plan (repository-only), signs up, reaches the same pages, fails sign-in fifteen times without a lockout, and sets a new password with the README's runner recipe. It then counts the limiter in Solid Cache: the eleventh sign-in post from one address is redirected, while page views, another address, and the next window are not. Neither smoke proves the recipe on Render or Neon.

Private qualifiers

The private Account-only artifact qualifier admits reduced web-only and iOS Movie Catalog variants. Both derive /account with signup-field details and editing, without consulting a Policy. Web admits no Scaffold and emits no ios/; selected iOS admits only public indexes on non-Account Entities and lowers the same target-neutral navigation. The primary path requires the full reduced registration shape: one required Account-owned short_text Name input with a nonblank analyzed literal default of at most 1024 bytes and one optional conventional forward Favorite movie Association whose target has a required short_text Primary Descriptor. The fixture projection pins those local keys to name and favorite_movie; generated parameters and locale entries use the corresponding name, favorite_movie_id, and favorite_movie vocabulary. The typed projection retains authored order and covers every required Account product member in this slice. The Account iPhone harness uses this projection. The qualifier rejects zero-input, partial, mixed, or broader input populations, uncovered required members, other native clients, delivery channels, Scaffolds on the Account Entity, and broader Scaffold behavior. The application-wide dependency plan receives Account's immutable Gemfile block, direct requirements, prerequisite list, and source identity. It projects one complete Core-qualified pair and retains it for the separate Gemfile and Gemfile.lock tasks. Account owns neither file.

A separate private AccountQualificationInput reconstructs the narrow reduced Movie Catalog prerequisites from one sealed generation and returns a valid Domain input paired with the exact selected Account lowering. It also qualifies one AccountSelf result against target, Project, and generated route, constant, controller, and view claims. That result fixes GET/HEAD /account, account-self:<subject_uuid>, the semantic person icon, the Account Entity label, and source provenance. ApplicationNavigation combines it with the exact public indexes without adding client-specific meaning. RenderingContext may carry those Account values only when they are the exact input-owned realization for the same generation. ApplicationTaskPlan then selects the complete seven-task Account runtime family, 18-task web-flow family, and 3-task self family. The application-global ApplicationController task reads the same whole-Project context. The generic Domain-route and main-navigation tasks add the self route and authenticated link; Account owns neither global path. The application-wide dependency plan observes that exact context, adds the Account requirement contribution and source identity, and projects the exact Gemfile and Gemfile.lock pair before Core composition. Normal Compilation constructs the same output context only for an input-owned public Field-only Account realization. Ordinary input does not admit Association registration. The private qualified input has a distinct internal type, and the rendering context rejects forged or mismatched input-owned evidence so this seam cannot silently emit a Domain-only subset. The builder owns an explicit handled-root list. The private qualification treats capability warnings as blocking so that every root must be handled; every other nonempty captured Project root rejects before shared Domain serialization can widen this seam. Predicates remain in that list only so the existing Reference catalog can return their source-addressed warnings. Validation roots are rejected before the deliberately empty Validation declaration value is constructed. The Account-only iOS variant admits Scaffold roots only when they describe public indexes on non-Account Entities; the Account-only web variant still rejects every Scaffold. Account registration-input roots are handled only through an immutable typed projection built from the exact Account lowering plus same-generation Domain, Reference, and Primary Descriptor evidence. The primary admitted projection contains exactly one required Account-owned short_text Field with a nonblank analyzed literal default of at most 1024 bytes and one optional conventional forward Association whose target has a required short_text Primary Descriptor. It retains authored input order and proves that every required Account product member in this slice has an input. This fixture projection pins the Field key to name and the Reference key to favorite_movie; generated parameters and locale entries use the corresponding name, favorite_movie_id, and favorite_movie vocabulary. The generated Account iOS request-oracle gate records the Account-specific harness selecting this primary projection. Zero-input evidence is no longer admitted. The harness prepares a disposable instrumented copy from the primary projection, and one selected live test completed its bounded WebKit lifecycle. The qualifier and both Account emitter families independently reject zero-input, forged, repeated, unmatched-default, or broader input evidence. The separate self renderer rejects forged or mismatched Account-self evidence.

The private Association create-account view maps those typed inputs in authored order. Missing Name displays and applies the analyzed default; present blank, whitespace-only, nested, NUL-bearing, or over-limit values fail. Missing or empty Favorite movie becomes nil; every nonempty value, including whitespace-only input, must be a canonical UUIDv7 resolving in the admitted Movie table under PostgreSQL FOR KEY SHARE. Rodauth's before_create_account hook completes every product check before any DML and merges accepted values into its base Account insert. It does not use an Active Record callback or an after_create_account update. Failed forms redisplay only bounded safe Name strings and valid admitted Movie selections, clear passwords, and leave the Account and all authentication tables unchanged.

The private Favorite movie control enumerates the complete admitted Movie table into an anonymous response, including descriptor labels and UUID values. This is bounded evidence for the small non-sensitive fixture only. Target-level authorization, filtering, search, and pagination must be designed before a public profile admits comparable Association choices. In the private Policy-gate qualifier, this control lists Movies to anonymous visitors although its gated Movie index shows them only to signed-in Accounts. That output conflicts with the code-worth-keeping requirement and is a known Compiler defect (repository-only).

The private Association create-account view maps its typed projection in authored order. An absent Name displays its analyzed default. A present String with valid encoding uses Rodauth's param_or_nil for assignment and redisplay. Its configurable byte limit defaults to 1024, and admitted defaults fit that boundary so their forms can round trip. Authored length rules run after normalization in Rodauth's rails_account model. Registration adds no character cap. Admitted values redisplay, including whitespace-only values that the creation hook rejects as blank; every rejected shape redisplays empty. The optional Favorite movie select orders admitted Movies by their qualified direct scalar Primary Descriptor and id, retains only a valid admitted selection after failure, and leaves password controls empty. Rodauth's before_create_account hook performs every product check before any DML, merges the Name and foreign key into the base Account hash, and never relies on an Active Record callback or post-create update. Missing Name applies the analyzed default, while present blank, whitespace-only, nested, NUL-bearing, or over-limit values fail. Missing or empty Favorite movie becomes nil; every nonempty value, including whitespace-only input, must be a canonical UUIDv7 resolving under FOR KEY SHARE to the exact admitted target table. Malformed, nested, stale, and foreign values fail before Account or authentication rows exist. Authentication-owned columns are not request inputs. The private form currently publishes every admitted Movie descriptor and UUID to an anonymous caller. It is proof for a small non-sensitive fixture, not a general Association-choice lowering; authorization filters, bounded search, and pagination remain absent.

The private Policy-gate qualifier admits only the favorite_movie-targeted Movie index plus its existing private Web Account prerequisite. It admits the optional Association registration input only when Movie has a direct required short_text Primary Descriptor; the 193-file hosted predecessor and the current-base Core-repin's 194-file one-hop control therefore omit it. This does not broaden public Account registration. Association admission and native session restoration remain outside ordinary public CompilationInput, CompileCapturedProject, and the durable Compilation lifecycle.

The optional Favorite movie select has one empty choice and orders the admitted Movie targets by their qualified Primary Descriptor and id. Ordinary Compilation omits an Association registration input and lists the gap. Under the code-worth-keeping requirement, a later public select would need to limit an anonymous visitor's choices to records that the target's Policies allow.

Code and checks

On both runtime legs, the generated-app smoke enables Rails forgery protection and uses one Rails integration session to submit freshly rendered authenticity tokens. It creates an unverified Account, proves that login remains closed, requests a verification resend, follows the emailed two-step verification flow, and observes the exact signed-in Account through current_account. The same session signs out through the generated POST navigation control, signs in, requests and follows password-reset mail, rejects the old password, accepts the replacement, triggers the fixed ten-failure lockout, requests unlock mail on the next attempt, and follows it to unlock and sign in. A separate tokenless create request returns HTTP 422 before any write.

Before that longer journey, the smoke verifies both product controls, authored order, requiredness, descriptor choices, defaulted and explicit Name values, missing and empty Favorite movie values, and one exact Movie selection. Invalid Name and Favorite movie shapes leave the Account table and all six authentication tables unchanged, enqueue no job, deliver no mail, safely redisplay admitted product values, and never redisplay either password. A valid registration adds exactly one Account, password-hash, and verification row and no other authentication row.

The smoke executes generated messages only through the test Active Job and Action Mailer adapters. It proves local text rendering, addressing, job execution, link behavior, and one redirect-delivered error and notice through the Core's alert and status live regions. An ordinary browser session proves remember issuance, restoration after removing the Rails session cookie, the disabled /remember route, and logout revocation. Separate native-user-agent sessions prove issuance, restoration, deadline extension on restoration, expired-credential rejection, and Account-wide logout and password-reset revocation. The emitted configuration registers deliver_later through Sequel's db.after_commit, and focused tests pin those exact bytes; the smoke does not independently observe enqueue ordering. It does not prove production mail, broader registration-input assignment or other Account product-member assignment, bounded or authorization-filtered Association choices, production queue operation, equal response timing, closing the generated login/unlock identifier disclosures, Policy enforcement outside the separate record-gate slice, broader abuse controls, public native Account/session restoration, or WebKit session continuity for ordinary Compilation.

The separate compiler_account_runtime_smoke (repository-only) materializes the qualified iOS reduced Movie Catalog Account bundle twice and compares every path, byte, and mode. Its fixture has one public Movie index, a required Name registration input with analyzed Movie fan default, and an optional Favorite movie Association input. It verifies the frozen generated bundle, composed iPhone source, migration/schema parity, fresh schema loading, both boot paths, the configured runtime namespace, Account-only model integration, password-hash behavior, auxiliary storage, and cascading cleanup. It builds the generated Rails assets once, then on both runtime legs drives the generated CSRF-protected forms through browser sessions and sessions that declare the client-controlled Hotwire Native user-agent marker. The smoke supplies that marker directly rather than deriving it from the emitted iOS shell. The browser journey completes creation, resend and email verification, exact current_account continuity, POST logout and login, password reset, fixed lockout, email unlock, and final logout. An ordinary browser session proves remember issuance, restoration after session-cookie loss, and logout revocation. Native sessions separately prove issuance, restoration on the next page request after session-cookie loss, deadline extension on restoration, expired-credential rejection, disabled /remember, and logout and password-reset revocation from a restored session. Generated mail executes through the test Active Job and Action Mailer adapters. A separate tokenless create request returns HTTP 422 before any write. On both database setup legs, anonymous /account enters Login and signed-in /account renders only the exact current_account identifier despite unrelated Account parameters. The manifest's generated iOS application definition carries account-self:<subject_uuid>, /account, and the lowered person symbols. The same Association-bearing shape does not realize Account through ordinary public CompilationInput. This smoke does not build the iPhone source or prove a live tab transition, WebKit self navigation, Simulator behavior for this entry, production mail, broader registration inputs, authorization, or ordinary native Account admission.

The Policy-gate smoke repeats this current-record and anonymous-link boundary beside conditional Movie navigation. Focused Web-only output tests prove the self renderer selects without iOS. Focused native tests prove semantic-icon mapping and iOS task selection, not a live tab transition.

Generator tests pin route-title wiring for every Account page, and the smoke confirms that exercised flow headings appear in their rendered document titles. It does not prove a production sender identity, provider or network delivery, DNS, retries, bounce handling, or deliverability. Verification resend is exercised after an already committed unverified Account exists; an initial enqueue or delivery failure is not simulated. deliver_later serializes verification, reset, and unlock key arguments into the queue backend, so public production-mail qualification must review queue access and bounded-retention controls.

The public deep smoke compares redirect status, destination, notice, and alert text for duplicate versus successful create, unknown versus eligible verification resend, and unknown versus eligible reset request. Each pair is equal, while only eligible flows change state or send local test mail. Photogram's concurrent unique-handle create race returns 422 to the loser while the pair produces exactly one Account row, one Authentication row, and one verification message.

The old resetting-client cookie-expiry assertion belonged to the superseded signed-in recovery setup; logout and subsequent stale-cookie presentation retain the relevant cleanup checks. Broader Policy algebra and native enforcement, timing parity, closing the known login and unlock-request identifier disclosures, broader abuse controls, session expiry and concurrency, broader registration input kinds and populations, bounded or authorization-filtered Association choices, other Account product-member assignment, live Account-self cross-navigator behavior, native profile behavior, signed-device behavior, deployment, and production mail remain absent. Exact-topology public Web Account Compilation does not imply any of those safeguards or native-session behavior.

The separate opt-in compiler_account_ios_session_smoke (repository-only) now uses that primary Account shape and materializes the generated Rails and iOS application around one public Movie index.

References marked “repository-only” name implementation or internal material outside this public guide. They are intentionally not links. Publishing a design does not prove its implementation.