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

On this page

Rails Domain storage

Status: Implemented for the bounded Domain slice the Compiler admits. Broader storage is an evidence-backed direction.

Each admitted Entity becomes one PostgreSQL table with a UUIDv7 primary key, created by an ordinary Rails migration. One immutable schema snapshot feeds the create-table migrations, one post-table foreign-key migration, and the complete db/schema.rb, so a migrated database and a schema-loaded database agree. Indexes come from References, uniqueness rules, and admitted Orderings. Migration versions are ranks within one artifact, qualified only against a fresh database. Proven development data lower to idempotent seeds; reference-data seeding remains a direction. The Data model owner states what Entities and data records mean. Field lowering and References and Associations cover columns and relationships.

On this page

Record identity

UUIDv7 is the Rails target-profile default for application-owned tables. The Domain renderer emits id: :uuid with PostgreSQL 18 uuidv7() explicitly in its create-table migration and verifies the generated identity at runtime. This is not a portable Field or a per-Entity Plan choice. The pinned Core configures ordinary Active Record generators with primary_key_type: :uuid for new primary and foreign keys. Its shared create-table template adds the uuidv7() default only to UUID primary keys; model, scaffold, and standalone create-table generators use it. Add-reference migrations retain native UUID foreign keys without a generated-value default. An explicit --primary-key-type=bigint retains native bigint keys and references. Mixed-key applications choose reference types to match their target tables. Already-migrated tables keep their types and defaults. Framework-owned migrations use their native conventions: Active Storage, Action Text, and Action Mailbox read the generator setting when their migrations run and use Rails' gen_random_uuid() for UUID primary keys. Core's existing Solid tables remain bigint. These framework migrations do not use the shared application create-table template. Rails 8.1.3.1 cannot configure the primary-key default itself. Check the copied template on Rails upgrades and retire it after a suitable native primary_key_default release is qualified; the upstream proposals are tracked in #688 (repository-only). The continuation qualification (repository-only) links the local combined-pin proof and preserves the earlier isolated candidate's history. Independent multi-Entity migrations have generated-app proof; their versions are artifact-local ranks qualified only on a fresh database. Stable cross-compilation migration identity and the complete generated Domain remain open. The ordinary Reference slice avoids table-dependency ordering by adding all admitted foreign keys in one deterministic post-table migration; stable evolution of that migration across compilations remains open.

Schema snapshot

The profile pins Core's complete schema by version and SHA-256 in the TARGET_CORE_SCHEMA_VERSION and TARGET_CORE_SCHEMA_SHA256 constants of the profile (repository-only). A pure schema snapshot verifies those bytes and supplies one shared Domain projection to the create-table, post-table foreign-key, and complete-schema renderers. Migrations preserve authored Field and Reference order, while the schema orders columns, complete rendered index lines, and foreign keys to match Rails 8.1's SchemaDumper; those physical orders differ without changing column semantics. The projection includes Reference UUID columns, an index that is unique for one-to-one References, and ordinary database foreign keys without deletion actions. Model rendering separately assigns an authored deletion outcome to one eligible Active Record inverse Association. It omits a nonunique Reference index only when a compatible generated composite starts with the same column. The shortest compatible composite wins, rendered index name breaks a tie deterministically, and the winner retains every omitted Reference source in its provenance. Unique one-to-one indexes and nonleading Reference indexes remain separate. The generated-app check independently withholds the emitted schema so Rails 8.1.3.1 and PostgreSQL 18 must apply the migrations and dump them byte-identically, then schema-loads a fresh database and repeats the runtime checks.

Every model, create-table migration, post-table foreign-key migration, and the complete db/schema.rb are complete files. One immutable SchemaSnapshot supplies the migration and schema renderers with the same primary keys, physical columns, nullability, indexes, foreign keys, Account-owned auxiliary deletion actions, timestamps, versions, and provenance. An admitted uniqueness declaration contributes one unique index over its authored physical tuple. The index emits nulls_not_distinct: true only for nulls: "not_distinct"; ordinary distinct keeps PostgreSQL's default. Identical physical indexes merge every source subject. Because this release requires every tuple column to be NOT NULL, otherwise-identical distinct and not_distinct rules are equivalent; their shared index keeps NULLS NOT DISTINCT when either rule requests it.

The target profile pins Core's exact schema version and file digest. SchemaSnapshot rejects a different Core schema, and the pure schema renderer updates that verified source with the Domain version, tables, and foreign keys without loading Rails, PostgreSQL, SchemaDumper, or pg_dump. The qualification smoke first applies every migration and asks the generated app's pinned Rails 8.1.3.1 and PostgreSQL 18 to dump the result. It withholds the emitted schema during that step because Rails 8.1 otherwise initializes an unmigrated database from db/schema.rb before db:migrate. The new dump's bytes must equal the emitted schema. The smoke then creates a fresh database from the emitted schema, runs db:prepare, and repeats the table, migration-version, column, index, foreign-key, UUIDv7, scalar round-trip, Reference-behavior, and Validation-behavior checks.

Indexes

An index snapshot has exactly one nonempty column list or one nonempty expression. Column indexes may carry frozen Rails order options whose keys belong to that list; expression indexes carry no order map. Expression and order are part of collision identity, and an expression index never suppresses or absorbs a leading-column Reference index. Both create-table migrations and db/schema.rb render these as ordinary Rails t.index declarations.

After that exact merge, the snapshot omits a nonunique one-column Reference index only when a compatible generated composite index starts with the same column. The strictly wider index preserves the Reference lookup prefix. If several composites qualify, the shortest wins, with the rendered index name as the deterministic tie-break; the winner absorbs every source subject from the omitted index. A unique one-to-one Reference index is never omitted, and neither is a Reference index whose column appears only later in another index.

Migrations and columns

Migration ranks follow authored Entity position with subject UUID as the deterministic tie-break. The one deterministic post-table migration, <timestamp>_add_foreign_keys.rb with class AddForeignKeys, adds every Reference foreign key after all Entity tables, so self-References and cycles do not require dependency ordering. Schema tables, complete rendered index lines, and foreign keys are sorted to reproduce Rails 8.1's SchemaDumper. Each create-table migration preserves authored Field and Reference order, while the schema renderer sorts columns by name. Migration-built and schema-loaded databases therefore have different physical column positions but the same column semantics. Scalar lowering maps boolean, date, datetime, decimal, integer, language_code, long_text, short_text, time_zone, and url, emits required Fields as null: false, uses PostgreSQL 18 uuidv7() record identity, and adds timestamps exactly once. An emitted short_text Field with case_insensitive comparison instead uses PostgreSQL citext. One conditional pre-Domain migration enables the extension, and the shared schema snapshot renders the same extension declaration and t.citext column. Fields without that comparison remain ordinary string columns. Reference lowering emits belongs_to, adding class_name only when the profile's pinned inflections do not reproduce the target and optional: true only for optional References. Required References use the target's Rails 8.1 required-by-default setting. The Compiler emits foreign_key when Rails' reader-based inference would not reproduce the Reference column. It also emits a UUID column with matching nullability, a useful leading index, and a database foreign key. An immutable Reference emits attr_readonly for its foreign-key attribute, so assigning that attribute after persistence raises ActiveRecord::ReadonlyAttributeError under the pinned Rails profile. This is model-level write-once behavior rather than a database trigger. When one_to_one is true, the index is unique and the model validates uniqueness on the logical Association with allow_nil: true so requiredness separately owns missing-target errors. Entity, Field, and Reference subject UUIDs provide task identity and generated-file provenance; readable names determine paths only through the pinned profile.

Domain migration versions are artifact-local ranks, not stable per-Entity identities across Plan edits. Adding or reordering an Entity can renumber every later migration. The current artifact is qualified only against a fresh database; incrementally upgrading an already migrated generated application would require stable persisted migration identity and a separate evolution design.

Admitted Domain slice

This is a bounded release slice under the ignore-and-list default, not generic model generation. Before task discovery it requires the complete sealed Project capture. Admitted Domain rows are limited to Entities, plain scalar Fields, required enum Fields with their ordered FieldValues, the required one-per-Entity Primary Descriptor envelope, ordinary single-target References, their ordered targets, their exact mechanically derived forward Associations, direct unqualified referenced-side Associations over mutable References, and direct has_many inverses over required immutable References. A no-cardinality direct inverse in that last shape may use an exact same-generation admitted result-Entity PredicateScope condition. Mutable inverses lower to has_one when one_to_one is true and has_many otherwise. Optional immutable has_many and every immutable has_one are listed and omitted. One first-level unqualified indirect Association may compose an admitted direct has_many with one mechanically derived forward belongs_to backed by a mutable or required immutable Reference. The through step may be predicated or unpredicated. An unpredicated direct through step may instead use an admitted predicated direct has_many source backed by a required immutable Reference. Its resolved family must be has_many. One further exact collection may traverse that admitted read-only source shape to a required immutable non-one-to-one derived forward belongs_to. Optional immutable sources, a predicated direct through step with a collection source, broader nested indirect paths, referencing-side aliases, cardinality-qualified Associations, and multi-target, defaulted, or realized Reference shapes remain gaps. The executable first admission slice lists the admitted Validation declarations. The public mutation slice admits Association controls for required and optional ordinary References with one_to_one: false and requires every admitted conditional-presence Validation owner in both its create and update input lists. Scaffold qualification retains that one-to-one rejection even when a broader Domain release admits one-to-one storage. Rails enforces comparisons; unconditional date bounds also supply inclusive form min and max hints. Everything outside these admitted shapes follows the ignore-and-list default: an unlowerable Field kind, Reference, Association, unsupported Predicate, Scaffold surface, Account, Policy, or Application-configuration slice is omitted from the generated output and listed in the reviewed GapSet, while comparison behavior, encryption, and Field derivation are ignored and listed per modifier.

Reference and development data

Entity-owned reference_data remains a direction for idempotent db/seeds.rb behavior in every environment. For a proven nonempty development_data residual, the current Compiler replaces Core's db/seeds/development.rb; Core's ordinary seed entrypoint loads it only in development. The Compiler first admits only complete records and assignments whose values, generated required validations, numeric bounds, lookup and uniqueness tuples with target null-distinctness, Reference dependencies, implicit one-to-one targets, and Account payloads are provably seedable by this profile. Datetime tuples compare stored instants at PostgreSQL's microsecond precision. Realized citext tuples compare component by component, proving a result whenever locale-stable ASCII comparison is decisive and leaving locale-sensitive ASCII or non-ASCII outcomes unproved. Decimal candidates first satisfy the shared canonical semantic literal predicate, whose admitted spellings are injective across numeric values. Development seeds pass whole decimal literals to BigDecimal as Ruby Integers and fractional literals as strings, preserving exact values while satisfying the pinned generated StandardRB rules. This rendering does not change decimal admission or use Float conversion. The Compiler deterministically retains the first possible duplicate, reruns dependency closure, and lists every resulting omission rather than letting a valid Plan fail during Compilation or seeding. It then derives creation order from retained Reference assignments, uses the same admitted lookup tuples with ordinary find_or_create_by!. Readable Ruby locals support References, Account setup, and State Machine events. Existing ordinary matches return without assignments or save callbacks. Account seeds retain find_or_initialize_by, mutable setup, creation-only immutable attributes, and missing-password repair. Generated State Machine events run only when the authored target state has not been reached. After the complete event path, the seed restores only explicitly authored values overwritten by its effects, inside that same guard. Unsupported values or dependencies are omitted at the smallest coherent boundary and remain precise GapSet entries. Seed-file provenance includes each selected enum or State Machine FieldValue used by emitted source, including a required initial State; an optional nil State assignment emits neither transition source nor a selected-value claim. Semantic data-record IDs name generated locals or lookup results; they do not become persisted application columns. Reference-data reconciliation across later Plan revisions remains open.

Code and checks

A committed qualification script imports one Plan containing Movie, Director, Post, Series, and a synthetic Credit join Entity, all ten supported scalar Field kinds, seven ordinary References that include self-Reference and cross-Entity cycles, five admitted inverse Associations, two admitted indirect Associations, representative Movie Validations, exact public-index Scaffolds, a domain, and a selected iOS client. It captures that generation once, builds one qualified input and matching rendering context, calls the normal ApplicationManifest coordinator, and materializes the complete Rails and iOS manifest. Through the emitted application's own dependency boundary, it installs and checks the locked Ruby bundle with Bundler state outside the artifact root, installs the locked JavaScript packages with an npm cache outside the artifact root, and builds the generated JS and CSS. It creates a uniquely named disposable PostgreSQL test database and verifies that database begins with no tables. It saves and removes the temporary artifact's emitted schema before db:migrate, forcing Rails to run the complete migration history, then requires Rails' new schema dump to equal the emitted bytes. After checking the exact Core-plus-Domain tables, migration versions, authored physical column order, models, representative values, and UUIDv7 identity, it verifies explicit belongs_to, has_many, and has_one reflection metadata, UUID Reference columns, nullability, ordinary indexes, the one-to-one unique index, logical :taken feedback, and the database uniqueness fence. It also verifies database foreign keys, forward and inverse traversal, and ordinary and eager-loaded Director.series traversal deduplicated across two Movies that reach the same Series. The synthetic Credit path, whose through and source References are both required immutable, proves a second distinct indirect traversal. Its runtime checks cover ordinary join creation with both endpoints assigned before the first save and multi-owner eager loading. For each removal or replacement branch, the proof loads a fresh strict owner with both the indirect target and through-source chain preloaded. It then proves whole-row deletion or replacement without deleting final targets and the delete_all(:nullify) branch's NOT NULL failure. Persisted Credit endpoints remain readonly. Unrestricted mutation is outside this proof. Indirect declarations have no dependent option. Exact direct inverse lifecycle carriers use conventional Rails dependent: behavior; every Domain foreign key remains an ordinary non-deferrable integrity backstop. The script also verifies required-model feedback, unconditional length, active and inactive conditional length, conditional presence and absence on Field and logical Reference error keys, ordinary foreign keys, and live Rails-dependent restriction, nullification, destruction, and child destroy callbacks. It also checks ordinary and uncountable route helpers, true-empty and populated index responses, Series page 2, and an actionable page 999 response. It then drops and recreates the database, loads the emitted schema, runs db:prepare, and repeats the same semantic and request checks with SchemaDumper's physical column order. bin/ci runs this proof. It byte-checks but does not build or run the generated iOS project, and it does not exercise broader Association, Validation, or Scaffold behavior, another Capability, a production image, or the future production tool runner.

The current materialization smoke imports its complete candidate Plan once and calls ApplicationManifest from one captured generation.

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.