Rails authentication
Status: Implemented for the Field-only public Web Account boundary. Other Account shapes are listed gaps.
Account meaning lowers to Rodauth Rails with email and password, email verification, password reset, and lockout.
Public Compilation realizes the Account only when its registration inputs are required short_text or time_zone
Fields that cover every required Account Field. 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. Native clients receive no Account navigation, and the GapSet lists that omission. 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
- Public registration
- Account self and navigation
- Generated files
- Create-account form
- Rodauth configuration
- Private qualifiers
- Code and checks
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 the complete email/password topology in 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 the complete email/password Account topology exercised by the
ordinary representative corpus and the reduced Movie Catalog fixture, retaining 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. The private native qualifier may add the application-wide remember feature and table; ordinary input
passes an explicit false restoration decision regardless of selected clients.
A complete Rails lowering for Account meaning would derive:
- authentication-owned storage and model integration;
- sign-in, sign-out, registration, verification, recovery, and lockout flows requested by the Account;
- sessions, routes, controllers, mail, and secure parameter handling;
- a runtime representation of
current_accountfor Policies, supported defaults, and Scaffold requests; - create-request assignment for bindings whose Value is
current_account; - external transactional-mail prerequisites;
- allowed, denied, expired, recovery, and lockout behavior tests; and
- setup and repository guidance for owner-controlled credentials.
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 needed by the complete email/password
Account behavior exercised by the reduced Movie Catalog fixture. 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. The required topology gates one fixed Rodauth feature set: i18n, create_account,
verify_account, login, logout, change_login, verify_login_change, change_password, reset_password,
lockout, and the target-chosen disallow_common_passwords safeguard. 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. A 2026-08-04 audit of
RubyGems and the upstream
source found Rodauth Rails 2.2.0 current; the same
RubyGems and
source check found Rodauth 2.45.0 current. Their real source
still exposes the conventional storage configuration and native remember APIs. 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.45.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 registration first requires one email identifier, one password sign-in method, self-service
registration, email verification, password-reset recovery, lockout, and a successfully qualified Account-self
surface. It admits one or more contiguous ordered inputs only when every input is required and names a unique
emitted Account-owned Field of kind short_text or time_zone. Compatible analyzed literal defaults are optional,
every required emitted Account Field must be covered, and the Account Entity must own no required Reference.
A required enum on the Account Entity can emit independently as Domain storage, but it cannot appear in this input
list, so public Account lowering remains a gap.
Ordinary output derives Web Account self but an
Account-free ios_navigation, and explicitly disables native remember restoration regardless of selected clients.
An Association registration input leaves the Account unrealized and listed as a support gap;
when an iPhone or Android client is emitted after Web Account realization, the Account retains a precise partial
GapSet row for omitted native self-navigation and session restoration.
Public Policy admission consumes this exact Account result and admits no Policy root without it.
This paragraph describes the current Account result claimed as realized, not a target-wide pre-alpha admission
rule. A later renderer may emit a conventional partial Account or registration flow with precise missing-flow,
input, and Policy gaps.
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 only when there is at least one input, positions are
contiguous in authored order, every input is required, every source is a unique emitted Account Field of kind
short_text or time_zone, and the inputs cover every required emitted Account Field. Compatible analyzed literal
defaults are optional, and the Account Entity must own no required Reference. It then qualifies the exact Account
self result and carries all three values as input-owned
evidence. Association inputs make that public realization unavailable and retain the Account support gap. 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. These Web choices add no destination to ordinary native
clients.
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.45.0's
features. The key lookup adds the same UUIDv7 guard as other generated token lookups because this target disables
Rodauth's integer-ID conversion. 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 six runtime files and 21 web-flow files, including a custom mailer and four text templates. 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. A separate conditional schema-quality
renderer owns .active_record_doctor.rb. It retains Core's global framework exceptions 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. Without any such exception, the
Core config stays unchanged. Account-free output retains Core's DEPLOY.md. 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 instead lowers the separate Account-free iOS navigation. When iOS is absent, its projection declines
with no native output while the Web self surface remains. Android remains excluded from this private Account
qualifier. Its ordinary public-only lowering is independent of the private iOS path. 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. Unsupported shapes 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 six runtime and 21 web-flow paths. The runtime family conditionally replaces
DEPLOY.md with the provider-neutral Account mail launch prerequisite. One separate conditional schema-quality
renderer owns .active_record_doctor.rb, combining exact Rodauth exceptions with the required numeric and
AASM-managed state attributes that its presence detector does not recognize. 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. Two 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. The same new-Account example checks refusal
to sign in before email verification. 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 all
three 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 preserves the submitted Rails model in a local variable across
account_from_login, which can replace or clear Rodauth's account, 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. 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.45.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 and AASM-managed state attributes. It retains Core's .active_record_doctor.rb bytes
when none of those exceptions is needed. 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 verification,
password-reset, and unlock 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.45.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.
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 its default destination, currently /. In pinned 2.45.0 this feature does not call clear_tokens:
current and sibling sessions, native remember credentials, and pending reset tokens remain valid. 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 and
reset tokens 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. Malformed UUIDv7 verification, reset, unlock, and remember
token IDs stop before SQL. The native-capable variant emits a 14-day non-renewing remember deadline, no
/remember route,
and an HttpOnly, request-secure, SameSite=Lax cookie at /. It issues the credential only after Hotwire Native
login, loads it before ordinary Rails routes, and removes the Account-wide server key on explicit logout. 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.
This client distinction is 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 web-only variant emits none of this remember behavior.
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.
An iOS client additionally requires Rodauth's application-wide remember feature and an Entity-scoped remember table.
The intended activation is native-only, but the feature itself is not client-scoped. The conventional experiment
disabled its public route and invoked it only after a native login. This prerequisite does not adopt that
application's one-year deadline, renewal, or cookie settings. Explicit logout reads the Account identifier before the
session is cleared and deletes the single server-side remember row. Here, Hotwire Native is selected by the
client-declared user-agent marker, not an authenticated device signal; the ordinary-browser control omits that marker.
The selected harness has observed native WebKit login, termination, deliberate Rails-session loss, same-Account
remember restoration without renewal, restored logout, a second login, and a clear-both signed-out control. The proof
is one local unsigned Debug Simulator run that predates /account, not live Account-self cross-navigator, the current
Web profile surface, physical-device, signing, release, production, hosted Compilation, or ordinary public native
Account 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 lookup preserves Rodauth's bounded deletion of its expired reset row. These emitted configuration and storage boundaries remain covered alongside the browser journey.
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 or native restoration; it owns a distinct Account-free iOS
navigation projection. 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.
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. A local pre-alpha starter may still use a conventional unfiltered select when the
exposure and missing choice contract are disclosed as gaps.
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. Separate native-user-agent sessions prove native-only remember issuance,
restoration after removing the Rails session cookie, deadline nonrenewal, expired-credential rejection, disabled
/remember, and Account-wide logout and password-reset revocation. An ordinary browser receives no durable
credential. 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.
Native sessions separately prove issuance, restoration on the next page request after session-cookie loss, deadline
nonrenewal, 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.