Rails data integrity
Status: Implemented for the bounded Validation slice below. Other Validation shapes are listed gaps.
Plan Validations lower to Rails model validations, and storage invariants add database constraints where they are
the clearest backstop. Required scalar Fields derive presence, inclusion, or numericality declarations plus
NOT NULL. The admitted slice adds comparison, length, format, absence, conditional presence, and uniqueness, and
uniqueness always receives a matching unique index. Every declaration targets an explicit Field or logical Reference
key, so forms show each error beside its input. An unsupported Validation keeps its bytes and is listed in the
reviewed GapSet, while the admitted declarations still render. The Validations owner
states what each rule means and when database enforcement fits.
On this page
- Direction
- Admitted slice
- Qualification
- Requiredness and numeric input
- Reference comparison
- Format
- Length limits
- Uniqueness
- Realization layers
- Executable first admission slice
- Ordinary numeric validation
- Ordinary Reference comparison
- Reusing enum predicates
Direction
Support: see the support inventory row (repository-only).
Choose model feedback and database enforcement according to their purposes; follow the validation
guidance when deciding whether a failure is
correctable input or broken application code. A constraint does not automatically require a matching model validation.
An Entity Validation's Field error_target uses its Field binding and a Reference target uses its logical local-slot
error key. Models and forms share those semantic bindings. Plan Validations require an explicit input target;
Rails-native lifecycle errors may still use errors[:base] and appear in form summaries. Begin with Rails' built-in
presence, absence, comparison, length, format, exclusion, and uniqueness families where they preserve the Plan
meaning. Closed when Expressions and dynamic comparison operands become Compiler-generated predicates, same-record
attribute symbols, or callables rather than authored Ruby. Optional Fields derive nil handling; Boolean presence means
non-null rather than truthy, and empty JSON objects or arrays remain present values. Attachments, rich text, and
References use their semantic existence APIs. Numeric Field types derive raw-input numericality validation before
Active Record coercion; integer-only behavior follows from the Field type rather than another Plan option. A target
must prove admitted format patterns safe for its pinned runtime. This profile starts with one runtime-independent
whole-value printable-ASCII character-class grammar and retains the framework Regexp timeout as defense in depth.
Every source outside that grammar stays a reviewed gap. The current admitted uniqueness slice always receives matching
structural enforcement. Compatible literal comparisons may become database checks; disclose rules that remain
model-only. A future pre-alpha partial may retain a model-only uniqueness validator as starter code while listing the
missing atomic backstop. There is no blanket trigger or RLS preference.
Admitted slice
The current profile's executable admission slice is narrower than that direction.
For each of the ten admitted stored scalar Field kinds, required: true derives a model validation as well as
database NOT NULL. Date, datetime, language-code, long-text, short-text, time-zone, and URL Fields use presence: true. For the realized required-datetime current_time default, Active Record materializes the model attribute
default before this check; explicit nil and later nil assignments remain invalid. Boolean Fields use inclusion in
[true, false], preserving false as present. Integer and decimal Fields use native numericality's default nil
rejection; optional numeric Fields add allow_nil: true. Required numeric Fields receive no duplicate presence
validation. Optional Fields receive no derived requiredness declaration. Each derived declaration is immutable,
follows Field order, and adds that Field's UUID to generated-model provenance.
The slice also resolves the declarations that the
executable first admission slice lists; the
support inventory (repository-only) records each family's
probe. Format accepts \A and \z around printable-ASCII character-class atoms with
optional ?, *, or
+, permits an optional case-insensitive flag, derives allow_nil: true, and emits inert source through
Regexp.new with a one-second instance timeout. A Field error target emits Rails' built-in uniqueness validator.
A Reference error target requires a composite tuple and uses the logical association name in the same native
validator. Rails binds it to the foreign key, applies the remaining physical columns as the scope, excludes the
persisted record, and adds :taken to the logical Reference key. Both forms emit one
unique index over the authored physical tuple;
nulls: "not_distinct" adds NULLS NOT DISTINCT, while distinct omits that option. citext tuple members keep
their column-defined equality, so generated validators do not add case_sensitive: false.
Date comparisons emit ordinary Rails comparison validators with Date.new(year, month, day) bounds. Only
canonical Plan dates from 1582-10-15 through 9999-12-31 lower: earlier literals remain source-addressed target
gaps because the Plan uses proleptic Gregorian dates while Rails 8.1.3.1 casts through Ruby's historical-calendar
Date.new.
This bound restriction preserves authored meaning without replacing Rails casting. Rails casts an
invalid calendar submission such as 2025-02-30 to nil; required presence then owns the error, while an optional
Field keeps Rails' nil behavior. This does not add a raw-date validator or change admission of authored date values.
Dynamic dates, path operands, and datetime comparison owners remain gaps. Development-data selection evaluates
supported date clauses with the Plan's Gregorian date values before retaining a record. A seed date before
1582-10-15 governed by one of these clauses remains unproved and its record is omitted with a gap.
Comparison and length declarations derive allow_nil because optional owners accept nil while required scalars
report missingness separately.
Reference inequality with a participating Reference error target uses native Rails comparison and normal
association loading. It compares records, including shared unsaved objects, and leaves missingness to required
Reference validation. Reference errors use the logical
Reference key. Native comparison's default message interpolates the other record's to_s, normally an object
reference such as #<Person:0x...>. Application wording and localization can use ordinary Rails error configuration.
Every source
outside the bounded grammar—including line anchors,
\Z, backreferences, and subexpression calls—plus negative or conditional format and long_text format remains a
listed gap. Every other Validation shape is skipped and listed. Compiler
input
requires every owner, structured condition operand and provenance record, uniqueness tuple column, normalization,
and comparison behavior to be emitted from the same generation.
The complete-file model renderer emits relationship declarations first, then required scalar declarations,
native numericality, state declarations where present, and authored Validation declarations, then any private
condition predicates. Integer numericality keeps integer syntax
and the effective intersection of signed four-byte storage bounds and unconditional authored bounds. Conditional
comparisons remain separate; an input-owned numeric error suppresses a residual comparison only for the same
attribute and actual native error signature. Date clauses remain separate calls.
Decimal inputs use native Rails numericality. Missing required numeric values receive :not_a_number; zero stays
valid. Ordinary malformed strings are rejected, and precise decimal attributes retain their stored precision.
Native parsing also rejects the old custom parser's "1D2" spelling; ordinary scientific "1e2" remains valid.
PostgreSQL's extreme integral/fractional overflow may instead raise during persistence; Ruby Float/BigDecimal NaN
and infinity may pass validation and persist. The Compiler adds no finite-value callback, digit parser, database
check, or exception translation. Foundation Plan literal and development-data admission remain unchanged.
This release emits no database CHECK for the admitted comparison
slice, so Rails owns enforcement. Generated-app verification exercises required Boolean, numeric,
and text behavior, optional scalar behavior, requiredness composed with an authored comparison, both scalar
integer and date comparison boundaries, safe positive format with an ordinary :invalid Field error, logical Field
and Reference
keys, and conditional behavior and numeric lexical/storage boundaries after migration and schema loading.
Photogram additionally proves create and update failure for follow.not_self, conditional requiredness of
feed_occurrence.source_follow while reason == "following", conditional absence otherwise, and the corresponding
successful writes after schema loading. This
remains the complete
Validation boundary for the current release. Correctable duplicate input reaches the ordinary Rails :taken and
422 form path; the matching index remains the atomic backstop for concurrent or validation-bypassing writes.
Qualification
Normal qualification reconstructs the Validation, Expression, Association-method, and Reference-storage evidence
from that one sealed graph. It runs structural and semantic Validation analysis, applies the target declaration
resolver, and aggregates every prerequisite diagnostic before task discovery. The valid result carries one
same-generation declaration set and verifies that each declaration's Entity and condition Fields belong to the
admitted Domain catalog. Bounded uniqueness additionally verifies every tuple column, normalization, comparison,
and ordinary Reference realization against emitted Domain storage. An unsupported Validation keeps its exact bytes;
it is skipped rather than
lowered, its blocked identity joins the reviewed AnalysisRun GapSet, and the admitted declarations still render.
Generated condition Ruby, ordered null-test attribute names, and subject provenance derive together from one closed
Compiler-created condition value. Ruby source is output, not input to a second parser. CompilationInput still
cross-checks those names and subject UUIDs against the admitted Domain as an independent provenance boundary.
Requiredness and numeric input
Scalar Field requiredness now derives one model declaration before authored Validations. Required Booleans use
inclusion in [true, false], preserving false; integers and decimals use native numericality, and the remaining
admitted scalar kinds use presence. All report invalid values on the logical Field key during valid?. The realized
required-datetime current_time default is a model-visible
attribute :field_name, default: -> { Time.current } declaration. Omitting the type preserves the schema-derived
PostgreSQL timestamp(6) precision. Active Record materializes it before
ordinary presence validation, while an explicit nil still reports the model error. The Domain database column keeps
NOT NULL and no default; direct or bulk inserts must supply the value. Optional scalar Fields derive no
requiredness declaration. Authored comparison and length declarations skip
nil so this derived declaration remains the single owner of a missing required scalar error. Database NOT NULL
remains the durable storage backstop for every required scalar. Every emitted integer and decimal Field also derives
a raw-input validation before authored comparisons. Native integer numericality requires integer syntax and the
intersection of signed four-byte storage bounds and unconditional authored bounds. Decimal Fields use native
numericality. Required numbers report :not_a_number for missing values; optional numbers allow nil. Native decimal
validation may accept Ruby non-finite values or leave extreme storage overflow to PostgreSQL. No custom parser or
finite-value callback is emitted. Input-owned numeric errors suppress only duplicate residual comparison errors for
the same attribute and actual native signature. The
Validation owner records the tradeoffs.
Reference requiredness uses the target's Rails 8.1 required-by-default belongs_to and matching database nullability;
optional References emit optional: true. The generated model therefore reports a missing required target before
the database rejects it.
Generated-file provenance includes every contributing Association, Reference, Predicate, Ordering, Validation, and
condition Field subject UUID. A Validation is omitted and listed when its owner or a condition Field is not emitted;
generated models never retain a declaration or predicate that calls absent storage. Compiler input cross-checks each
condition's structured null-test or enum-equality metadata against the exact emitted Field and provenance instead of
reparsing generated Ruby. A direct enum equality or its immediate negation in Reference presence or absence uses if:
or unless: with the actual allocated enum predicate; compound conditions retain private helpers. Straightforward
unconditional integer bounds join the Field's native numericality declaration while preserving original Validation
declarations and provenance. Conditional integer and date comparisons retain separate Rails comparison: calls.
Native numericality owns required numeric input errors; optional numeric Fields allow nil.
Reference comparison
The bounded Entity comparison handles unconditional not_equals between two distinct required ordinary References
with the same record type. A participating Reference error target emits native comparison: {other_than: ...}
and normal association loading. Validation error targets must name an explicit Field or Reference; ordinary Rails
lifecycle errors may still use :base.
Missing References retain their requiredness errors. The native comparison message interpolates the other record's
to_s, normally an object reference such as #<Person:0x...>; ordinary Rails error configuration can customize it.
These comparison shapes remain model-only; the schema renderer derives no database CHECK constraint.
Format
The format slice emits the built-in Rails validator on one stored short_text Field. Its pure target parser admits
only \A and \z around one or more printable-ASCII character-class atoms, each optionally followed by ?, *,
or +. Class-syntax bytes remain reserved, and ranges stay ascending within a-z, A-Z, or 0-9. Every source
outside that grammar—including line anchors, \Z, backreferences, and subexpression calls—remains a reviewed
target gap; the Compiler does not ask the First Draft service's Regexp engine to interpret it. Generated Ruby calls
Regexp.new with a quoted source String, the profile-owned option integer, and a one-second instance timeout; Plan
bytes never enter a Regexp literal or executable interpolation. allow_nil: true keeps requiredness as the sole
missing-value error owner. Negative, conditional, and long_text format remain reviewed gaps.
Length limits
The same immutable Validation declarations feed the shared schema snapshot. For a short_text Field realized as a
Rails string, the snapshot chooses the tightest upper bound from admitted unconditional maximum and
exact_length declarations and supplies one column limit to both the create migration and emitted schema. It emits
no limit for zero, conditional or minimum-only rules, long_text, citext, or a bound outside PostgreSQL's supported
range from 1 through 10,485,760 characters. Only declarations whose bound equals the emitted tightest limit join the
migration and schema provenance. The Rails declaration retains the complete rule; the database limit is only the
validation-bypass upper backstop for non-space excess. PostgreSQL may truncate excess trailing spaces to the declared
varchar length, so the model declaration remains the complete rule for the authored input.
Uniqueness
The uniqueness slice uses Rails' built-in validator for both Field and logical Reference error targets; every other
tuple member becomes a physical Active Record scope, including _id for an ordinary Reference. Rails binds a
logical Reference target to its foreign key, excludes the persisted record, and adds :taken to the logical
Reference key. It may load the Association through its ordinary reader. Neither path adds
case_sensitive: false: citext or the ordinary column type owns each Field's equality. The same declaration
supplies a unique schema index, so normal record and form paths report :taken before persistence while PostgreSQL
rejects a concurrent or validation-bypassing duplicate.
The executable first admission slice lists the admitted tuple shapes. Every other tuple, and tuple meaning whose Field lowering is incomplete, remains a reviewed gap.
Realization layers
A complete Rails realization should normally generate all applicable layers:
- an Active Model or Active Record validation for immediate record and form feedback;
- a database constraint or index where the same rule has safe structural enforcement;
- form attributes, hints, and error placement derived from the same meaning;
- localized error keys; and
- model, database, request, and form tests appropriate to the claimed rule.
Those layers can arrive incrementally in pre-alpha output. The Compiler may keep an ordinary Rails model validation or form hint while the GapSet discloses a missing database, request, localization, or interaction layer. It must not call the Validation completely realized until the layers required by its semantics and claim are present.
The first lowering direction is:
- conditional
presenceorabsencestarts with a built-in validator and generated predicate; Boolean presence and JSON containers use type-specific non-null semantics where Rails blankness differs; comparisonstarts with built-in comparison options or a generated closed cross-value check; compatible literal bounds may also becomeCHECKconstraints;lengthstarts with the built-in validator, while unconditional bounds can inform form hints andmaxlength;formatstarts with the built-in validator and a Regexp derived from a target-proven safe source grammar;exclusionstarts with the built-in validator when its equality matches the Field and otherwise receives comparison-aware lowering; anduniquenessstarts with the built-in validator when one target can anchor it and receives matching structural enforcement for a complete atomic-uniqueness claim.
Executable first admission slice
To add a declaration to this slice, follow the support-admission procedure (repository-only). It lists the code consumers, inventory row, and public Explorer text that move with an admission.
The rails-sketch/2026-09-bookmark-assets target currently resolves only these Rails declarations:
- an unconditional or conditional Field-owned
comparisonon a storedintegerordateField, with one or more matching literalgreater_than,greater_than_or_equal_to,less_than, orless_than_or_equal_toclauses; date literals currently start at1582-10-15because earlier Gregorian bounds differ from ordinary Rails casting (see the Rails target). The declaration derivesallow_nil: trueso requiredness separately owns a missing-value error; - an unconditional Entity-owned
comparisonwhose sole clause isnot_equalsbetween two distinct required ordinary References with the same record type. A participating Reference target uses Railscomparisonwithother_than: :other_referenceandallow_nil: true. Normal association loading and record equality preserve saved and shared-unsaved target behavior. Missing References retain their required-association errors. The native error message interpolates the other record'sto_s, normally an object reference such as#<Person:0x...>; application wording can use ordinary Rails error configuration; - a
lengthValidation on ashort_textorlong_textField, withminimum,maximum, orexact_lengthplus an optional admitted condition; the declaration derives the same nil handling; - an unconditional Field-owned
formaton a storedshort_textField, using positivematchesin the bounded whole-value printable-ASCII character-class grammar with an optional Booleancase_insensitiveflag; the declaration derivesallow_nil: true, compiles with a one-second instance timeout, and leaves missing-value errors to requiredness; - conditional
presenceorabsenceon a text Field; - conditional
presenceorabsenceon an ordinary single-target Reference with exact foreign-key storage; and - unconditional Entity-owned
uniquenessover one or two requiredshort_textordateFields or ordinary one-column References, with a Field error target or a Reference error target on a composite tuple; and - the exact three-member tuple of two ordinary required References plus one already-emitted required non-ordinal
enum Field, with a Reference error target. Oscar's
credit.movie+credit.person+credit.roleis the current representative.
An admitted condition contains direct same-record Field null tests and, only for Reference presence or absence,
equality between one total direct required non-ordinal enum Field and a literal in its closed domain. Either atom may
participate in not and and or or groups. The condition must always produce a Boolean, and every value binding
must be total; a path that can become unknown through an optional Association is rejected. The resolver retains
compiler-owned structured metadata from that closed grammar. A single direct enum equality reuses the actual emitted
enum predicate with if:; its direct negation uses unless:. An allocated enum prefix or suffix becomes part of that
same predicate name. Other admitted conditions retain an inspectable predicate method. Compiler input checks the
metadata against emitted Field storage and provenance rather than reparsing the Ruby source. It also keeps a Reference
Validation's error key on the logical Reference reader rather than its physical _id column.
Other Entity-owned rules, other Field comparison owners (including datetime), unsupported equality operators,
dynamic or path comparison operands,
non-text Field presence or absence, nonordinary Reference storage, broader pattern source, negative or conditional
format, long_text format, exclusion, optional or conditional uniqueness, all other three-member or enum tuple
shapes, and every other condition shape are skipped with deterministic source-addressed warnings. Focused tests
prove nil behavior, range
boundaries, conditional behavior, format safety and error keys, uniqueness error keys, and exact physical tuple
indexes.
Compiler qualification requires the declaration, Entity, owner, structured condition operands, condition Fields,
Reference-comparison columns, and uniqueness tuple storage to belong to one admitted generation. Uniqueness
additionally requires every Field's normalization and comparison
behavior to be realized. Missing meaning produces a source-addressed target-support gap instead of dangling code.
One complete-file model renderer composes the remaining declarations with forward and inverse Associations, then
emits ordinary validates calls. One trailing private boundary contains necessary condition predicates.
Straightforward unconditional integer
bounds join the Field's native numericality declaration; tighter authored bounds replace redundant storage bounds.
Conditional integer and date rules remain separate. Date comparisons retain one Rails comparison call per authored
clause. Comparisons emit no
database CHECK constraints. Unconditional emitted date clauses additionally supply the form's tightest inclusive
min and max: a strict lower bound advances one calendar day and a strict upper bound retreats one day. Bounds
outside the control's four-digit year range, conditional rules, and unsupported rules do not constrain the calendar.
The generated-app smoke proves both admitted integer and date range boundaries and Rails
comparison error kinds, unconditional comparison, active and inactive conditional comparison and length behavior,
conditional Field and Reference behavior, and positive format behavior after normalization. A conditional length
bound does not become an unconditional form maxlength.
For a short_text Field whose realized column type is Rails string and PostgreSQL varchar, the shared schema
snapshot also projects the tightest upper bound from its admitted unconditional length declarations. exact_length
contributes its exact value and maximum contributes its maximum. The migration and emitted schema receive the same
limit only when that value is in PostgreSQL's supported range from 1 through 10,485,760 characters. This projection
deliberately excludes zero, conditional rules, minimum-only rules, long_text, and case-insensitive
short_text stored as citext, whose PostgreSQL type rejects a length modifier. A larger admitted model bound also
stays model-only rather than producing an invalid migration. The full exact-length rule remains in Rails; PostgreSQL
rejects non-space excess above its upper half, while a shorter validation-bypassing write remains possible by design.
PostgreSQL also truncates excess trailing spaces to the declared varchar length, so the Rails validator remains the
complete correctable-error boundary for the authored input.
Runtime values and relationship paths in a comparison become Compiler-generated callables. For example,
{ "kind": "environment", "name": "current_time" } lowers to a callable returning Time.current.
Rails already resolves a same-record attribute symbol, so
{ "target": { "field": "follow.last_requested_at" } } can lower to
greater_than: :last_requested_at without a generated method. The author never writes Ruby expressions in the
Plan.
Model validation alone is not sufficient to claim atomic uniqueness because concurrent requests can pass the query before either write commits. The current complete lowering adds a matching unique index. A model-only intermediate is still useful starter code when the structural-enforcement gap remains explicit.
An emitted all-Field named Ordering with at least two terms may also use an exact subject-set match to one of these unconditional uniqueness declarations as its stability proof. The semantic set match does not reorder either declaration: when the Ordering and Validation author their columns in different orders, the schema retains the ordinary query index and the unique enforcement index separately. Single-term Orderings remain outside this target slice.
The model declaration remains the primary correctable-error path. Field and Reference error targets use Rails'
built-in uniqueness validator. A Reference target supplies its logical association name; Rails binds the association
to its foreign key, scopes the remaining tuple members, and excludes the persisted record itself. It adds :taken
to the logical Reference key. Ordinary generated forms can therefore return 422 against movie, person,
user, followed, or post rather than an implementation _id column. The database index derives from the same
rule and remains the final atomic backstop for concurrent or validation-bypassing writes; it does not become a
second home for business behavior.
When the error target lowers to one validator-compatible Active Record column and the rest of the tuple can be
expressed as a Rails scope, the Compiler uses the built-in uniqueness validator. Rails 8.1.3.1 also
resolves ordinary belongs-to association names to their foreign keys.
This preserves the logical error target without a private query method or error-remapping callback. The earlier
private query existed to put feedback on that logical input; the native validator already provides that behavior.
For example, Case Chat Participant uses validates :user, uniqueness: { scope: :conversation_id } alongside the
matching unique index. Issue #693 (repository-only) retains the preceding
Participant, Follow, and Credit model comparisons. This does not add optional or conditional uniqueness.
The first profile lowers every case_insensitive Field to PostgreSQL citext, so
ordinary equality on both the anchor and scope columns retains each Field's comparison behavior. The generated
validator should not pass case_sensitive: false: the column already supplies the semantics. Omitting the option
also lets Rails skip a redundant persisted-record validation when a matching unique index covers an unchanged
tuple.
The database receives a unique index over the complete physical tuple. nulls: "distinct" uses ordinary PostgreSQL
unique-index semantics; nulls: "not_distinct" emits NULLS NOT DISTINCT. Within the bounded executable slice,
citext members compare case-insensitively, while exact short_text, date, and foreign-key members compare
normally. This gives mixed-comparison tuples one race-safe enforcement layer without expression-index SQL.
When that tuple starts with a column that would otherwise receive a nonunique one-column Reference index, the
Compiler keeps only the composite index and transfers the Reference source to its provenance. A unique one-to-one
Reference index remains separate, as does a Reference index whose column is not the composite's leading member.
For example, if a Movie title were case-insensitive while its release date remained exact, the index could remain ordinary:
add_index :movies, [:title, :released_on], unique: true
That lowering is not universal for other reasons. A closed multi-target Reference may expand into several physical columns, and a polymorphic Reference includes both an ID and a type. The Compiler must generate a closed uniqueness check over the complete physical tuple whenever Rails' built-in validator cannot represent the semantic tuple safely. Such a lowering needs a pinned-framework comparison before adding custom code, and routes the error through the Rails profile's derived binding for the selected Field or Reference.
A Reference error target maps its logical local slot to the corresponding Rails error key. bookmark.movie
therefore maps to movie. The live Project graph supplies the mechanically maintained same-key, unqualified
forward Association used by ordinary forms and readers. The Compiler can lower Rails polymorphism directly; an
exclusive arc needs a generated reader over its physical foreign keys. Localization and validation errors remain
keyed to the semantic Reference rather than its storage strategy.
Not every application-level rule can or should become a database constraint. The target profile should disclose the enforcement boundary rather than pretend the layers are equivalent.
Ordinary numeric validation
The Compiler uses Rails numericality for decimal Fields. A required decimal uses
validates :rank_score, numericality: true; an optional one adds allow_nil: true.
Numericality already rejects nil by default, so required numeric Fields do not need a separate presence validator.
Their missing-value error intentionally becomes :not_a_number instead of :blank. Preserve integer-only
enforcement, the effective storage and business bounds, authored rules, requiredness metadata, and database
NOT NULL.
For straightforward unconditional integer bounds, combine the checks into one numericality declaration:
validates :score, numericality: {
only_integer: true,
greater_than_or_equal_to: 1,
less_than_or_equal_to: 5
}
Earlier separate comparison declarations inspected numeric errors to suppress misleading range messages. Rails numericality already stops before range checks when input is not numeric or integral, so the consolidated bounds need no extra guard. A tighter business range can subsume the storage range without admitting more values; extreme integers then receive the business-bound error. The change preserves authored conditions, error targets, and admission.
A 29-input comparison of the emitted Oscar Party Rating and two same-table model copies retained every acceptance
decision. All eight valid inputs saved and reloaded correctly. Consolidation alone changed only the two tested
storage-overflow messages to the 1–5 business bounds. A second copy also applied the approved requiredness cleanup
and changed nil/empty/whitespace errors to :not_a_number. This is model/persistence evidence using the emitted
migrations, not fresh Compilation or request qualification.
The removed decimal block parsed raw input with BigDecimal, rejected nonfinite values, and checked PostgreSQL's
numeric storage limits. Its replacement accepts ordinary Rails behavior: storage overflow may raise a
database exception, and programmatically supplied Float/BigDecimal NaN or infinity may validate and persist. The
usual form strings "NaN" and "Infinity" still failed validation in the probe. Do not replace the removed block
with another generated finite-value or digit-count guard.
The earlier source-only audit retained this block partly because Rails converts values while validating them.
That conversion does not replace the model's stored decimal value. A fresh 22-input emitted-model comparison
rejected "12oops" with both validators and saved/reloaded a 29-significant-digit decimal exactly. It isolated the
accepted overflow and nonfinite differences. Those model copies still retained presence validation; a separate
pinned validator probe confirmed the requiredness simplification for nil, empty, whitespace, zero, and an ordinary
decimal. Issue #703 (repository-only) retains those original comparisons; they
are not new qualification of the combined application.
This changes emitted runtime policy, not the Plan's JSON literal admission or development-data checks needed for seedable output. Preserve ordinary gaps for incomplete authored behavior. Potential application-specific follow-up is collected in the owner-guidance notes (repository-only).
Ordinary Reference comparison
The Compiler uses Rails' comparison validator when a participating Reference owns the error. For example, a transfer whose destination must differ from its source can use:
validates :destination_account,
comparison: {other_than: :source_account},
allow_nil: true
Ordinary association readers and Rails record equality replace the former helper's association-cache and foreign-key bookkeeping. Accept normal association loading; the application's agent can optimize an observed performance problem. A local 20-case model comparison preserved acceptance and unsaved-record persistence behavior, with two additional SELECTs for unloaded persisted References. It does not qualify a newly compiled application or its request/form behavior.
Use ordinary Rails error behavior without requiring new generated message overrides. Comparing records can put an inspected Ruby object in the default message; an application's agent can customize that copy through Rails I18n when needed. Keep authored labels and the existing locale infrastructure. Candidate continuation guidance is collected in the owner-guidance notes (repository-only).
This is a generic Reference comparison, not a self-follow-specific feature. The explicit example migrations and removal of record-level Validation targets are described under Entity Validation. Issue #694 (repository-only) retains the preceding model comparisons and decision.
Reusing enum predicates
A Reference presence/absence rule conditioned on one required direct non-ordinal enum value reuses that enum's Rails predicate:
validates :source_follow, presence: true, if: :following?
validates :source_follow, absence: true, unless: :following?
Use the predicate's actual emitted name, including an allocated prefix or suffix. The earlier Compiler emitted two private comparison methods through its general structured-condition machinery while the enum API was disabled. Restoring native helpers makes those wrappers unnecessary; the Compiler still retains structured provenance.
This reuse covers a single supported enum equality and its direct negation. Other admitted conditions retain their private predicate; the change does not expand admission. Issue #702 (repository-only) records the preceding 21-case model comparison of acceptance, error keys/types, full messages, and valid persistence. Those model copies remain historical evidence, separate from fresh Compiler and application qualification.