Rails Core composition
Status: Implemented for the pinned Rails Core and the bounded Capabilities and Domain the Compiler emits.
Every generated Rails application starts from one pinned Foundation Rails Core archive. The Compiler loads that
archive as values, adds renderer output for the selected Capabilities and Domain, and checks path ownership before
anything is written. Core paths are immutable unless an explicit registry transfers one path to one named renderer.
A dependency plan projects one complete Gemfile and Gemfile.lock from a qualified universe without network
access. The Compiler also writes the application's README, AGENTS, and UI documents, substitutes the application
identity into Core's configuration, and verifies that no reusable-Core identity token remains. The
Rails target profile states the Core, Capabilities, and Domain concept, and the
Compiler owner owns rendering and packaging.
On this page
- Core pin
- Dev Container
- Path ownership
- Rails frameworks
- Dependency projection
- Application documents
- Identity and configuration
Core pin
The prototype bundles the current Rails Core pin (repository-only) as a
deterministic compressed Git archive. The only released tag, v0.1.0, predates material fixes, so this is recorded as
an exact revision rather than a release.
In pre-alpha mode, a Core repin may repair the current target profile's constant, route, and inflection census in
place only after complete local re-extraction and review of the active-pin surfaces. The
machine reference (repository-only) names that profile.
Cross-platform hosted reproduction on both profile platforms is the landing gate. The dangerous-attribute registry was
re-extracted from Core c7955bee62f3e5033f6fa2551aa4e12d22a55be6 under Rails 8.1.4 for the
rails-sketch/2026-10 profile and reproduced locally and in hosted CI. No retained-Project
migration or coexistence machinery is added; a profile repair starts a new Project or an explicit fork. A later
package update should move the same loading boundary to a verified Core release asset.
Moving the pin is an explicit maintenance operation. It verifies the archive digest, embedded revision, root,
regular-file count, uncompressed bytes, and lockfile digest; regenerates the maximal dependency universe when Core
inputs change; refreshes exact output characterizations; and reruns current-pin profile compatibility and
generated-app qualification. Review changed Core commands, dependencies, and UI APIs against the complete
application README, AGENTS, and UI templates so their operating guidance stays accurate. Reconcile relevant facts;
those templates need not copy Core's contributor prose. A repin removes the superseded archive and dependency pairs
unless code, tests, or a frozen profile name them; Git history keeps their bytes. User compilations remain
network-free. The loader
verifies the compressed-byte SHA-256 and Git
revision in the global PAX header before it exposes file values. It accepts regular files and directories, rejects
links and special entries, strips one required root, reduces Git's executable bit to 0644 or 0755, and passes
the complete file set through OutputManifest.
The profile workflow automatically compares every changed active archive with the frozen compatible surfaces on
arm64 Darwin and x86-64 Linux through script/verify_rails_target_profile_compatibility. The default Actions token
cannot read the private sibling Core repository, so script/release check reads Core main through gh on the
release operator's machine instead. When the pin lags, it warns and lists the missing commits. Merging a Core pull
request approves adopting it in the next Compiler release unless the pull request is labeled for separate approval;
see owner sign-off (repository-only).
Dev Container
The generated Dev Container selects PostgreSQL client 18 to match its PostgreSQL 18 service. It omits the unused Active Storage media feature; applications adding image variants or previews choose and install their processor. The Dev Container adoption (repository-only) records the exact package and qualification boundary. That adoption changed the Compiler release while retaining its Plan, API, Analyzer, and target profile.
Path ownership
All Core paths are immutable by default. CorePathRegistry contains only explicit path-to-renderer ownership
transfers. A registered replacement is required from that exact renderer owner; an absent replacement, a different
claimant, or any undeclared collision fails. The default registry transfers nine paths. Five concrete renderers own
config/application.rb, config/database.yml, config/locales/en.yml, render.yaml, and README.md;
application-document renderers own AGENTS.md and UI.md; the schema renderer owns db/schema.rb; and the
application-global renderer owns app/controllers/application_controller.rb. The active application plan adds the
Domain-routes and main-navigation Web transfers that Web files and routes
describes, plus unconditional Gemfile and Gemfile.lock transfers to their separate generic renderers. Account
qualification changes dependency-plan and ApplicationController content without changing global file ownership. It
also changes the generic Domain-routes and main-navigation outputs through shared semantic evidence. The Account-self
renderer owns only app/controllers/accounts_controller.rb, app/views/accounts/show.html.erb, and its locale. The
Web families do not claim Core's application layout. Core retains the layout and responsive navigation. The task plan
preflights every selected task-backed transfer, and CoreComposer enforces the planned owner during composition.
Rails frameworks
Implemented. An application requires the Rails frameworks its Plan uses, as Rails 8.1.4 rails new writes
them with the --skip flags that fit the Plan, instead of Core's require "rails/all". The owner made this
derived-frameworks decision (repository-only) during the emitted-code audit.
config/application.rblists every framework, asrails newdoes once one is skipped. Active Model, Active Job, Active Record, Action Controller, Action Mailer, Action View, and Action Cable are always required. Action Mailbox and Action Text stay commented out because no Plan generates them, andrails/test_unit/railtiestays commented out, as--skip-testwrites it, because applications test with RSpec.- Active Storage loads only when an emitted Field is an
attachmentorimage, the rule that also selects attachment storage, the Active Storage tables, and theiractive_record_doctorexemptions. Without one, the application leaves out what--skip-active-storageleaves out: theconfig.active_storagesettings inconfig/application.rband the three environment files,config/storage.yml,storage/.keep, and the storage entries in.gitignoreand.dockerignore. Rails then draws no Active Storage routes. Therails_frameworksrenderer owns the development and test environments and both ignore files for that edit, the application-domain renderer removes the production setting, and the path registry omits the two files. InDEPLOY.md, the Account or application-domain renderer replaces Core's note about the production:localservice with the steps that add Active Storage back. - Action Mailer stays in every application. Accounts send mail through it. A Plan without Accounts sends none, but
application.domainlowers only to Action Mailer's production host, so skipping it would change that field. - Action Cable stays in every application, although no generated code subscribes or broadcasts, and Turbo Stream
responses over HTTP do not need it. Core ships Solid Cable: the gem,
config/cable.yml, thesolid_cable_messagestable in the schema that the profile pins, and a polling-floor spec. Leaving it out would change that schema and the profile's/cableroute claim, which needs a new profile ID. The register (repository-only) records both departures as TL23. .active_record_doctor.rbignores only framework models the application loads: Active Storage's with an upload Field, and never Action Mailbox's or Action Text's.
Core keeps rails/all, so the profile's constant and route census still covers every Rails framework. An
application that loads fewer frameworks occupies a subset of those names and routes, which keeps name availability
conservative. Changing Core's requires would change both censuses, and a route-claim change needs a new profile ID.
Core itself leaves out jbuilder, as rails new --skip-jbuilder does, because no Plan has a JSON surface and the
Compiler only adds to Core's Gemfile. A Plan-derived removal would have needed a removal path, a projected lockfile
for every Plan, and new dependency pairs, all to remove a gem from every application. A future JSON surface can add
the gem through a dependency contributor. Pagy and include Pagy::Method stay in every application, without Pagy's
sample initializer, so an agent adding a list finds pagination already wired.
Dependency projection
RailsDependencyPlan receives the complete sealed Project, exact compilation input, and base rendering context.
Each selected contributor supplies one internal immutable identifier, direct-requirement set, complete Gemfile block,
prerequisite list, and source identities. Policy requires Account. The planner orders contributors, rejects duplicate
or conflicting claims, coalesces shared identical requirements, and checks every contributor against its digest-gated
universe entry, including its prerequisite list. Every contributed requirement must also be a direct dependency of
the maximal lock, not merely a reachable specification. Account, Attachment, Normalization, Policy, and State
Machine are the current entries. Attachment contributes pinned cloudinary only when an emitted Field is an
attachment or image. Normalization contributes pinned strip_attributes only when an emitted Field requests
trim. Like Normalization, Attachment entered only the maximal universe, with no overlay pair: the gem adds no
Active Record class or Relation methods, and the conservative relock left every existing locked version unchanged.
This is a linear contributor registry, not a table of contributor combinations.
The empty selection returns Core's exact Gemfile and Gemfile.lock without starting the projector. A nonempty
selection inserts the qualified blocks once into the exact Core Gemfile. It starts one isolated service-Ruby 4.0.6
subprocess with only the checked-in Bundler 4.0.10 package, an empty Bundler configuration, fixed locale and time,
private home and temporary directories, and an explicit environment allowlist. Bundler runs lock --local --print;
Compilation inherits no proxy, Bundler, Ruby, credential, or ambient gem paths and has no resolution route to the
network.
The lock verifier parses Core, the maximal universe, and the candidate. It requires exact direct dependencies,
remote, platforms, Bundler version, reachable specifications, and checksums. ApplicationTaskPlan retains the one
result in its derived rendering context. The two dependency renderers only emit that retained pair. Six baseline
snapshots retain the previous lockfile bytes and compose Gemfiles against the selected Core. A focused regression
projects the six additional normalization-bearing selections and verifies that only strip_attributes is added.
The standalone qualifier checks seven pairs, including the maximal universe, twice under hostile parent settings.
Production selection consults no overlay contract or bytes.
The maximal universe is an upper bound for the current contributor registry, not a permanent gem census. Adding Money, Position, or Counter contributors under the remaining Field-kind work (repository-only) requires regenerating and requalifying the universe. The same dependency advance must requalify the pinned named-scope target namespace (repository-only) facts. Optional library methods must remain qualified by the selected dependency rather than restrict apps that do not load it. Package provenance, qualification, and runtime installation live in the vendor owner (repository-only) and packaging decision (repository-only).
Application documents
The Compiler writes complete application-facing README, AGENTS, and UI documents. The README starts with the
application display name and a small replaceable product-description paragraph. Operational instructions derive
from the selected emitted clients and retained Rails source, and concrete test-coverage omissions remain visible.
Setup runs the specs before the first preview, and a Behavior to write section lists each
unwritten scope with the pages that call it and the specs that request them, and each
deletion outcome a refusing polymorphic target leaves unwritten.
AGENTS.md tells the agent to run the specs first and write what that section lists, previewing before it finishes,
and to debug any other failure without weakening a spec; Generated tests
owns the details. It also says that the retained Plan and GapSet record what was compiled and never govern later
code, and how to add a resource with the generated authorization pattern.
It does not turn Plan subjects or gaps into a feature catalogue. The user's agent can replace the paragraph from
retained design context after inspecting the emitted source and gaps. That fixed paragraph tells the owner to replace
it, which conflicts with the code-worth-keeping requirement.
Decided: Plan format 0.24 (repository-only) adds application.description
for the README opening and the web manifest description. Until then, the manifest description is the application
name, as Rails' own manifest template has it, from the
placeholder-copy work (repository-only).
Setup, runtime, preview helpers, and application tests work independently of removable .firstdraft/ context.
Core's changelog, contributor guide, and root composition document are omitted; they describe Core maintenance
rather than the application's history or operation. The Core path registry rejects replacement claims for these
paths. Useful UUID generator, Dev Container, and Codespaces guidance belongs in the
application README; UI retains reusable component and native geometry guidance; DEPLOY keeps its existing
Account-sensitive operational guidance. License-required third-party notices remain. The
placeholder-copy work (repository-only) moved Core's MIT notice from the root
LICENSE to licenses/first-draft-MIT.txt, with the same path under ios/ and android/, so the owner chooses
the repository's license; rails new emits no license file either. The minimal CLAUDE.md adapter stays
@AGENTS.md; removing it would require separate first-session agent-discovery qualification.
Identity and configuration
The identity renderers derive the Rails module, database prefix, display name, and Render service name from
Project#application_key and Project#name. README is a full-file renderer: its title is escaped, single-line
application display text; its iPhone and Android preview links use the same selected-client projections as the
emitted clients and guides. Requested but ungenerated clients receive no preview instructions. README preserves
the private Codespaces port default and routes temporary remote-preview exceptions and cleanup to the selected
guide. It no longer relies on exact Core prose markers or carries a historical forwarded-host receipt as instruction.
Other identity renderers read verified Core files through RenderingContext and return complete replacements.
The module name uses the declared target's camelization rather than mutable Designer inflections. Identity
derivation rejects a Project with another target/profile. CoreTemplate validates exact marker counts before one
atomic in-memory substitution and verifies replacement counts; JSON quoting and YAML-printable filtering keep
valid display text valid in nested Ruby/YAML source. Files retain Core modes. No renderer patches a materialized
file or consumes another renderer's output.
The application-configuration renderer substitutes the application namespace in Core's ordinary Rails configuration and the framework requires that Rails frameworks describes. The CSP baseline (repository-only) retains Rails' commented initializer and standard layout helper without custom middleware or nonce wiring.
The composition identity test (repository-only) requires complete Rails output to contain no bare reusable-Core identity token. README and AGENTS carry application guidance; historical Core provenance sentences are no longer exceptions. Replacement values preserve the original file modes, and required third-party notices remain.