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

On this page

Generated Rails tests

Status: Implemented for the bounded generated application. Broader coverage is an evidence-backed direction.

Every generated application carries its own RSpec suite. The Compiler emits FactoryBot factories, model examples, request examples, system specs for constructible forms, and query-count checks for selected indexes. Each example exercises behavior the Compiler generated or a page that calls behavior it did not write, using records that bounded setup can derive. A scope the Compiler did not write raises until the owner writes it, so the examples that load its pages fail through it. A deletion outcome a refusing polymorphic target leaves out fails its own example, and so does a Validation the model declares with an empty method. The README's Behavior to write lists them all. When setup cannot supply valid data, the generated README lists the missing test under Test coverage to complete instead of emitting a placeholder. The application's own bin/ci runs the suite. Application CI owns application behavior, while First Draft and Core qualify the Dev Container lifecycle and the production-image harness upstream. Foundation quality (repository-only) states the verification policy these tests serve, and the Rails target profile routes to each subject's lowering.

On this page

Test ownership

Generated application CI owns application behavior, lint, security, setup, and the production-image smoke. First Draft owns the fixed profile's Dev Container lifecycle qualification upstream. Core tests its exported application, and the same upstream harness can test an actual committed Compiler output. Its specialized script and workflow are excluded from the application archive. Applications retain .devcontainer, setup, tests, and CI without a remote First Draft dependency. After changing dependencies or development-container configuration, owners rebuild and test that environment or add dedicated coverage; ordinary PR CI does not detect every breakage there. Compiler identity substitution and composition are qualified here against actual Compiler output. Core's former Photogram rename smoke duplicated that substitution with a second implementation; it and the Core layer-inventory test are retired instead of being carried into each app. The upstream Dev Container lifecycle gate exercises a renamed checkout, setup, boot, CI, and a warm restart; its separate configuration-only template smoke is redundant. Application owners can replace the starting home action or add attachment processing without a test requiring the original template shape. State Machine, Policy, and Scaffold behavior tests remain application-owned.

The application production smoke builds its actual Docker image, runs the normal entrypoint with a disposable PostgreSQL database, checks applied migrations plus /up and /ready, and requires a non-root runtime user. It follows the application's declared runtime configuration. Root-page status and a particular non-root UID are not requirements: an authenticated application may redirect / to sign-in. Application owners test their own jobs and channels. Core's archive-excluded production harness owns package exclusions, a real production 404, and synthetic Cache/Cable/Queue concurrency under a named Core configuration. The synthetic job does not ship or reserve an application model name. Core CI automatically qualifies exported Core; the same harness accepts a clean committed generated repository. The application smoke does not cover production page rendering or provider configuration; deployment remains separate.

Applications retain three guards for built-in production Queue worker/dispatcher and Cable polling defaults. They enforce a conservative one-second floor after clearing environment overrides, explain idle database traffic, and link the application's editable policy in DEPLOY.md. They measure neither billing nor a universal safe interval. Default intervals and runtime overrides remain unchanged.

Stack and spec paths

The Rails target emits RSpec examples and FactoryBot factories under ordinary spec/ paths. Core configures the Rails generators to continue that stack. Factories provide valid model setup; request examples send explicit HTTP parameters and log in through Rodauth. Factory creation does not stand in for registration or its Sequel write path. Account request examples separately exercise the default page's guest guard and the emitted Terms/registration assignments through HTTP. Authored-profile guest coverage is reused. Registration samples must be valid, and the assignment example needs at least one value distinct from its default; missing setup is disclosed in the README. The Account owner (repository-only) describes this bounded coverage. Request and Policy setup uses ordinary FactoryBot records without blanket reloads or preloads. Descriptor reads and membership assertions retain their intended relationships; application controllers still preload the associations their requests need. Core's Rails :rescuable test setting renders exceptions with HTTP mappings, such as scoped 404s, and raises unmapped exceptions such as NoMethodError. Resource suites need no global exception override. Dedicated error-page specs scope production-like presentation and deliberate 500 rendering to their own examples. Standalone resource forbidden HTML examples for a signed-in account check status, format, a nonempty body, and access or nonmutation assertions. A denied guest's HTML example instead expects the redirect to /login and the same nonmutation checks. For a GET page it then signs in and expects Rodauth's redirect back to that page, unless the Account factory is unavailable. Shared error-page specs own the heading, explanation, and recovery navigation. Non-HTML denials keep their empty-response checks; hidden scoped records still return 404. An authored name can coincide with a Core spec basename. In that case its resource suite uses spec/requests/<collection>/scaffold_spec.rb; an otherwise colliding State Machine suite uses spec/models/<stem>/state_machine_spec.rb, and a colliding form system suite uses spec/system/<collection>/forms_spec.rb. RSpec discovers these ordinary subdirectories. Unaffected suites retain flat paths, and test-file ownership does not reserve additional Entity names or rename application models and routes. A Field name can also coincide with a method that FactoryBot's definition block already answers, such as sequence, after, or concern. FactoryBot would read sequence { 1 } as its own sequence method, so that factory attribute uses FactoryBot's documented add_attribute(:sequence) { 1 } reserved-word form. Other attributes keep the bare title { "title" } form, and these names stay available to Fields.

The current Compiler emits ordinary progressing factories and resets only named bounded sequences. It retains Core's Rails :rescuable test default and ordinary FactoryBot setup. Expected scoped 404s and explicit 403s retain their checks; unmapped exceptions such as NoMethodError raise.

Model examples

The bounded application layer gives each constructible Entity a factory and model examples for its emitted required and optional values, normalization, numerical input, References, and supported unconditional validation boundaries. Ordinary presence, length, and numericality requirements use Shoulda matchers with a valid factory subject where available. Required numerics use numericality and explicit nil rejection; booleans retain false acceptance. Numericality matchers qualify only authored bounds, such as .is_greater_than_or_equal_to(0) on a money Field. Compiler tests check the integer four-byte range and the money decimal(12, 2) bound, so removing either from a generated model fails no generated example. A URL Field's example checks an address, a blank value, and a rejected value against the :url message, which isolates UrlValidator from requiredness and other rules on the attribute. The validator's own spec/validators/url_validator_spec.rb uses an anonymous ActiveModel class and type: :model, the spec type where Shoulda includes its matchers. An image Field's example uploads the PNG fixture and then the text fixture, and checks only the :image error kind. spec/validators/image_validator_spec.rb checks the rule on an anonymous ActiveModel class with test doubles. When a page shows an enum value, spec/helpers/application_helper_spec.rb gains an enum_label example. It builds the first such record with its first enum key and expects the authored label from the locale, beside the uploaded_image_tag examples that image Fields add. Optionality uses allow_value(nil).for(:attribute) to check the whole attribute rather than one validator's nil option. A cycle of required relationships, the only factory limitation, keeps independent attribute examples on described_class.new and discloses examples needing complete records. Normalization examples use Shoulda's normalize matcher with concrete from and to values for the authored pipeline, including blank-to-nil cases. These examples exercise Rails' public normalizer; future custom setters or persistence interactions need their own application behavior tests. Assignment and persistence qualification remains in the upstream normalization smoke (repository-only). An exact length of one combined with blank-to-nil normalization checks its upper boundary with :wrong_length; the empty lower sample becomes nil, which the emitted length validator allows. Uniqueness examples create a validated seed and use validate_uniqueness_of on the authored logical error target, with all physical scope attributes. Case-insensitive columns use case_insensitive; downcased values use ignoring_case_sensitivity alongside the retained normalization example. A text seed unchanged by swapcase also uses ignoring_case_sensitivity: duplicate rejection remains checked, but that seed cannot establish case behavior. When more than one realized uniqueness declaration shares the logical error target, or that target is a one-to-one Reference, the example instead builds the complete duplicate tuple and checks :taken on the target. The matcher cannot distinguish overlapping validators with the same error message; changing a scope can still violate another rule. All model validators remain. Revisit these handwritten fallbacks when the pinned matcher can isolate overlapping same-message validators, so that matcher examples pass for one Entity that declares both uniqueness(title, studio) and uniqueness(title). The repeated saved-seed validity check is omitted; create-time and update-time validation can differ. Existing setup limitations and application-specific persistence and database checks remain. Date-boundary examples retain explicit Gregorian Date inputs; the comparison matcher's stringified samples change calendar interpretation near the admitted 1582-10-15 boundary. Realized Reference deletion rules receive concrete model examples where the existing factories supply their records. They check cascading removal, retained records with cleared References, or rejected deletion with both rows retained. A cascading join example also checks that its other required referenced records survive. Restriction follows the realized model rejection or ordinary foreign-key exception. A polymorphic target that refuses its own deletion keeps its example: a restriction passes, and a cascade or nullification fails with ActiveRecord::RecordNotDestroyed until application code writes it. When a record cascades to two children and one restricts the other, an example deletes the record with both and checks that all three are gone, which proves the declaration order the Associations owner describes. These examples assert outcomes rather than dependent: options; factory cycles and unsupported conditional setup retain actionable coverage omissions. State Machine event examples use ordinary persisted records and event methods; attribute-only checks use new records.

Generated model tests exercise initial state, closed-domain storage, direct-assignment rejection, allowed and rejected events, and transition effects. A stubbed event keeps its allowed and rejected examples. The Compiler's Oscar runtime qualification checks the AASM method census used by collision admission. Dependency versions belong in the Gemfile and lockfile; generated tests do not freeze a gem's private methods or configuration. Omissions are not duplicated as skipped desired-behavior tests. No generated example covers a named Predicate or Ordering scope directly; request and query-count examples reach it through the pages that call it.

Request specs

Policy examples exercise permitted and denied records and actual relation results. Realized Scaffold resources get complete request examples for their routes where setup is derivable, including persisted writes, invalid required input, and protected access. Other-Account denial examples name the record owner separately from the signed-in user. Denied updates retain the expected 404 for scoped lookup, a sign-in redirect for a guest at a Policy gate, or 403 for a signed-in account at a Policy gate. They compare every persisted target-record attribute and intentionally omit a blanket model-row-count invariant; creation count checks and destroy existence checks remain. Error-kind examples use RSpec's ordinary predicate matcher on record.errors, keeping the error object and expected attribute/type available in a failure. They do not reduce the requirement to generic invalidity or translated message copy. Protected descriptors receive their own permitted setup. Examples assert observable data and access behavior rather than exact scaffold copy, HTML classes, helper inventories, or default redirect destinations. Index and show examples find their record by Rails' dom_id, the record identity that Turbo Streams also target: the index row's dom_id(record, :list) and the show page's dom_id(record). A text or UUID descriptor is checked as well. An index that filters rows by Policy answers a guest or another Account 200, so those denial examples check that the record's row is absent. Without that check, an index that dropped its Policy scope would pass them. A parent's collection page whose rows a child Policy filters gets a listing example: the signed-in account's child appears on that page and in the parent page's preview, found by dom_id(record, dom_id(parent_record, :habits)) for a Goal's Habits. When that Policy compares a Reference other than the parent, a second example gives the account's parent another Account's child, which must be absent from both pages. Without it, a collection page or preview that dropped the child Policy scope would pass its suite. On the Account profile the parent is the account itself, so a Policy on that Reference gets only the first example. A child Policy that goes through the parent Reference on another page, a membership rule, or an indirect Association keeps the page under Test coverage to complete. Collection pages whose rows no child Policy filters get no listing example. Request specs do not have view helpers, so specs that use dom_id include ActionView::RecordIdentifier, the module that Turbo's own test assertions delegate dom_id to. A collection-level gate denial needs no collection records, so it remains covered even when a valid collection factory cannot be derived. Member requests and scoped-list exclusions retain the records their assertions need. Each associated create form receives independent New and create request examples, including associated-only forms. A nested resource's listing, New, and create examples share its own suite, such as spec/requests/goals/habits_spec.rb for a Goal's Habits, as its controller is Goals::HabitsController; its create journey is spec/system/goals/habits_spec.rb. Each selected standalone POST receives its own coverage independently of New, an authored return, or associated forms. A partially generated create, standalone or associated, with no source for a required value keeps an application-completion step instead of a false successful example; the other context's coverage remains independent. Permitted setup satisfies the parent page, collection gate, and create Policy; collection item filters do not gate New. POST examples check submitted values, the route-bound parent, and each server binding, including competing submitted IDs for current-Account bindings. Write examples check image and attachment inputs with be_attached, since an upload reads back as an attachment. An optional upload that an Edit form can remove gets an update example that attaches a file, submits only its remove_<name> flag, and expects no attachment; the Account profile gets the same example. Protected routes receive guest denials and, for a parent record Policy, another Account's parent request. Denied POSTs preserve the record count. Parent and collection Account gates also get a signed-in denial when a direct Account Reference rule can independently deny access. Separate parent and collection record Policies and create Policies needing custom draft setup retain actionable README omissions. A Message created from its Conversation can have a create Policy that delegates to the Conversation page's own record Policy through the URL-bound parent Reference: allowed_to?(:manage?, record&.conversation, with: ConversationPolicy). The permitted parent already passes that Policy, so the New, create, invalid, and other-Account examples and the browser journey need no other setup and are emitted. Invalid-input examples omit an editable required value, never the URL-bound parent. No-input POSTs need no model parameter; parent and Account spoof checks remain in the emitted examples. For CREATE, existing direct or conjunctive Account Reference Policy setup can supply an authorized editable value; a nested create submits the signed-in account itself, since its parent setup already covers any delegated rule. New retains its before-input setup limits. A shared create Policy that needs an editable Account Reference cannot authorize the empty New record; its omission asks the owner's agent to complete application form-entry authorization while preserving submitted-record authorization. More fixtures cannot repair that behavior. The emitted New page therefore denies every request, which conflicts with the code-worth-keeping requirement and is a known Compiler defect (repository-only). Omitting such a Policy input in a standalone or associated POST checks authorization denial rather than 422. A successful form write without an authored return expects a redirect, follows it with follow_redirect!, and expects the landing page to answer 200. The example does not name the default destination, which owners may change. It follows only when its own account can read the landing page. That page is public, an index that filters rows by Policy, a gate that admits any signed-in account, the write's own current-Account gate, or a record Policy that the write already satisfies. A record Policy is satisfied when it is the write's own or when the write's current_account bindings set every Reference it compares, and an update does not reassign a Reference that Policy reads. Associated creates return to their parent's collection page, or to the parent's own page after a one-tap create, and the setup already passes those pages' Policies. The Account profile returns to the signed-in account's page. A delete without an index lands on its sole parent's page when the deleter may read it, and on the home page otherwise; a parent page that is public or behind the delete's own gate is its only landing. All of these follow. Otherwise the example checks only for a redirect with a Location, and the README lists the landing under Test coverage to complete. One listing names an update whose Policy admits an account that the record's show page denies; that account's save returns to a 404. Its next step returns the write to a page every such account may read, because an example account that both Policies admit would hide the 404. Request-only creates check 201 Created for public and otherwise supported protected endpoints; selecting New is not required. Required-input failures retain 422, while authorization denials retain their own status. A supported, explicitly authored return gets an ordinary redirect_to expectation for the invoked standalone definition, associated form, or Account profile. Profile returns take precedence over update returns; dynamic destroy destinations are captured before deletion. These authored-return examples do not follow the redirect, so protected return-page setup remains a separate browser-coverage concern.

A scoped Reference input gets its own examples. Each page's search has examples in that page's spec, such as new_place_options in plants_spec.rb, signed in as the page's own examples sign in: it lists the signed-in Account's readable target record and not another Account's; a picker that does not search has New, or Edit when its form has no New page, check its select the same way. Every search also lists only the record whose descriptor contains the upper-cased query, stops at 20 of 21 records, matches a wildcard pair such as %_ literally, and ignores a NUL byte. It refuses each requester its page refuses, as JSON: a signed-out request, an Account whose Reference that the gate reads points elsewhere, or another Account's edited record or parent, with 403 or 404; a profile search sends a signed-out visitor to /login. When the Account can read only one target record, such as through a one-to-one owner Reference, the matching and limit examples become listed coverage gaps. Create and update submit the other Account's record and expect 404 without a write; a create whose own Policy reads that Reference expects that Policy's 403 instead. Other permitted writes create their scoped Reference values through the target's Policy for the signed-in Account. When that Policy is a membership the Account carries itself, such as User.member_place, setup assigns the signed-in Account's own Reference instead of creating another Account. An immutable membership Reference cannot be reassigned, so those examples become listed coverage gaps. When the edited record's own Policy reads the Reference, Edit and update reuse the record's current value, because that Policy already ties it to the signed-in Account and a new value could fail it.

System specs

Constructible resource forms also receive owner-local RSpec system specs. They visit the actual new/edit form, address controls by their public HTML names, select native options by value, and check persisted values after submission. These locators preserve repeated labels and authored Unicode whitespace; Core's accessibility checks confirm that controls have labels. Each supported form keeps its valid-first-submission journey. Ordinary rejection and non-persistence remain in model/request examples; required text does not add a generic browser recovery journey. Compiler browser qualification retains representative Turbo error re-render, retained input, correction, and relationship context. Owners add recovery journeys for concrete browser interactions or regressions as needed. This deliberately reduces per-form downstream recovery coverage. Associated creation visits each emitted parent context and checks its persisted inverse Reference. Protected forms use existing factories and bounded Policy setup, then sign in through the browser. Server bindings receive persisted-value assertions. Each journey whose action sends a success message checks that the page shows it; a one-tap toggle sends none. Core supplies Capybara, Selenium, and Turbo synchronization; the Compiler adds no browser framework. These examples do not assert a default redirect, other scaffold copy, or layout.

Selection reuses the existing bounded test data. Scalar text, number, checkbox, enum, and time-zone controls are supported; time-zone expectations use the canonical identifier submitted by the select. Date and datetime inputs use Capybara's typed values. Reference selection uses the visible picker and a qualifying related record with a distinguishable descriptor; a scoped input's record is created through the target's Policy for the signed-in account, and a public form cannot offer it. The native select's public name identifies its enhanced control. A searching picker types the record's label into its search, as a person would; another picker's option value identifies the label to choose. This does not test calendar navigation or the picker library's primitives.

Conditional validations, unconstructible factories or Policy setup, and inputs without useful bounded values retain explicit missing browser-coverage disclosures. New forms need authorization before editable values are supplied; setup cannot replace that application behavior with a permissive draft. Distinct parent and collection record gates need owner-written setup; a parent's Account environment gate does not qualify its record for a collection gate. A submit-only form has no generated browser journey. Its continuation suggests a submit-and-result browser example when the action merits one; it does not ask the owner to invent editable inputs. Request proof remains separate. Associated form setup includes the parent, create, and collection gates. Collection item filters only select displayed records; they do not add sign-in or qualifying-record setup for New. The return collection may omit the created record. Standalone forms account for their explicit return or default show/index page's Policy. Associated forms without an explicit return use their already-qualified parent collection. An item-scoped index return needs its sign-in setup and may show an empty collection; an environment gate still needs its Account setup. Direct return setup follows the selected record: scaffold_record reuses the associated parent's setup, while mutation_record uses the mutation's own setup. Sharing an Entity and Policy does not qualify a different record. Same-Policy reuse requires authorization of that record; an Account environment gate qualifies only the Account. A protected return through one unchanged Reference uses a qualifying related factory. A different Policy on the mutation record, conflicting Policy-owned References, or longer protected return paths need explicit setup. These records support the submitted form's navigation without asserting its destination. An edited Reference used by the mutation record's show Policy also needs qualifying browser input, even when the form and show page use the same Policy. Other request and model coverage, including denied access, remains independent.

Query-count specs

Selected index requests also compare query growth with n_plus_one_control at two and five records. Their ordinary FactoryBot setup supplies readable records before measurement; the request must return their text or UUID descriptors and each record's row dom_id, since factory descriptors can repeat. Selection reuses a complete successful index setup and permits public access, current-Account gates, or a direct Account Reference rule that can own several records. Account indexes, complex record rules, and any authored uniqueness in the setup's factory dependencies remain application-owned query-test work. This conservative boundary avoids exhausting finite sequences or reusing a unique Account relationship; it does not attempt to solve arbitrary multi-record constraints. Bullet's separate N+1 checks still run across the emitted examples.

Test coverage to complete

When bounded setup cannot supply valid data, the generated README adds a Test coverage to complete section. Each entry names the affected behavior and spec or factory path, the concrete setup limitation, and the owner's next step. Selection and disclosures use the same derivation. Independent runnable examples remain; no empty, skipped, or pending placeholder example substitutes for coverage. Setup omissions describe missing tests. These disclosures do not change the frozen reviewed GapSet or introduce a Foundation Plan testing DSL, a generic constraint solver, or an LLM during Compilation. Straightforward derivable setup belongs in the Compiler. Each unwritten scope also lists its own spec here, in its model's spec path, because no generated example pins what a named scope returns.

Behavior to write

When the Compiler cannot lower a Predicate or Ordering, it emits an unwritten scope that raises NotImplementedError, and the generated README adds a Behavior to write section. Each entry names the scope and its model, says what it returns or how it orders, and names the pages that call it. It then names the request specs with an example that requests those pages: the page's own index or show example, the Account profile example, or a collection listing or one-tap toggle on that page. A gated collection's page adds none, and a profile whose read Policy may deny an account adds no profile example. Their entries say which Policy admits the accounts the page raises for. Without such an example, the entry names the model spec to add. An Ordering entry spells out its terms; a Predicate entry points at its rule in .firstdraft/submitted-foundation-plan.json. The README also says that NotImplementedError is a ScriptError, so generic rescues miss it and production answers 500, and that bin/ci and a published repository's CI stay red while those specs fail. The introduction says to delete an entry once its specs pass. Its Checks section says that until then the Tests: RSpec and Tests: System steps fail through that behavior and every other step should pass. Its setup steps run the specs before the first preview. A deletion outcome left unwritten gets an entry too: the model, the outcome, the Reference it follows, and the model spec whose deletion example fails until it is written. A refusing polymorphic target fails it with ActiveRecord::RecordNotDestroyed, and a target whose conventional inverse name is taken with the foreign key's ActiveRecord::InvalidForeignKey. A Counter that counter_cache cannot maintain gets an entry whose example creates one counted record and fails with RSpec::Expectations::ExpectationNotMetError while the column reads 0. A Reference default gets one too; its model example builds a record without the Reference and fails the same way, and a default read from the signed-in Account, which only the create request has, is listed under Test coverage to complete instead. A required Reference with a model default gets no example that expects its absence to be rejected, because the written default supplies it before validation. When the section lists anything but scopes, its introduction and the Checks sentence say "behavior" instead of "scopes".

The generated AGENTS.md tells the agent to run the specs first and to write the behavior that Behavior to write lists until its specs pass, then delete the entry: a stubbed scope or event raises NotImplementedError, a stubbed Policy rule returns false, and the spec of other left-out behavior fails with the error its item names. It also says to write every listed item, including a denying rule that no failing spec names. The first preview does not wait for that work. The agent first writes any item whose entry says Home raises, then starts the preview and tells the user in one line which pages deny or raise until the rest are written, without asking. The agent debugs any other failure as its own: it fixes the cause, never skips, deletes, or loosens an example to get a green run, and changes a spec only when the spec contradicts the behavior the application should have. When the cause is generated code, it adds one line under "First Draft defects" in .firstdraft/design/implementation-notes.md and keeps working. The owner's 2026-10-12 decision (repository-only) set this order. A failure report (repository-only) that would send such defects to First Draft is proposed.

The request specs those pages already get are the scope's dependents. They stay emitted only when their setup holds for every correct implementation of the authored meaning. An unwritten Ordering never decides whether a record is listed, so the index's listing and query-count examples stay and fail through the scope. An unwritten Predicate's listing example stays only when every operand is a stored-value Field of its own Entity and the authored rule admits the factory record. Otherwise Test coverage to complete lists it, because a correct implementation might exclude that record.

Unwritten Policies

A Policy stub gets an entry too. It names the rule and its relation scope, points at the authored Policy in .firstdraft/submitted-foundation-plan.json, and lists the pages the rule guards without its scope, which deny every account, and the pages that look records up through the scope, which raise. It names Home when Home shows a raising index and the Policies that delegate to the stub, which follow it.

A stub's meaning is unknown to the Compiler, so no generated example asserts whom it, or a Policy that delegates to it, admits or denies. Spec setup treats both as unsatisfiable, so every example that needs an allowed or a denied record moves to Test coverage to complete, with a next step that waits for the stub. What remains holds for every correct implementation:

Unwritten events

A stubbed State Machine event gets an entry that names its bang method and model, the states it moves between, and each effect it applies, points at the transition in .firstdraft/submitted-foundation-plan.json, and says to add the event to the aasm block and delete its three stub methods. The section's events introduction says that no generated page fires an event, so only its State Machine examples fail, and that seeds and spec setup never fire one.

The State Machine spec keeps the examples an AASM event gets: one per source state it moves from and one per other state it rejects. Each holds for every correct AASM implementation of the authored transition and fails at its first may_ call until the event is written, so the stub's message names that spec. The examples check only the effects that each fit the generated shape, an optional datetime Field on the same record set to the current time or nil, and whose target no emitted Validation reads, since the examples' fixed values could otherwise make the record invalid before the event runs. Test coverage to complete lists every other effect for the agent to check after writing the event. Without a factory, the stub names the spec to add, and Test coverage to complete lists the transition as for any event. Development seeds and Policy spec setup search only generated events. A seeded record whose state needs a stubbed event is left out, and an example whose setup needs one moves to Test coverage to complete, with a reason that names the event.

An optional State Machine has no AASM machine, so its stubbed events keep only their allowed examples, which check the stored state rather than an AASM predicate. How a hand-written event refuses is not known, so Test coverage to complete lists each event's rejected states instead. Its applicability is left out behind an empty before_validation callback, and one example, "role begins in member where it applies", saves a new record that meets the condition and expects the initial state. It fails with RSpec::Expectations::ExpectationNotMetError until the callback is written. The Compiler builds that record, and the allowed examples' records, when the condition is an equality with a literal on the record's own Field or on a Field one Reference away, or a conjunction of those, and no emitted Validation reads a Field or Reference it sets. Otherwise Test coverage to complete lists the example, and the allowed examples use the plain factory record.

Compiler qualification expects exactly those failures, as a strict expected failure does. The harness asks the Compiler which scopes and events it left unwritten, including each stubbed Policy's relation scope, which controller actions call each one, and which emitted examples request those pages. A stubbed Policy's agreement example calls its scope directly, and a stubbed event's State Machine examples call the event, so their failures count outside a request. It does not name an example for a gated collection's scope, or the profile example when the profile's read Policy may deny its account, because that account may not reach the scope. A results formatter records each example's outcome, its failure's cause chain, and the actions its requests reached. A failure counts only when one of those scopes raised it, directly or as the cause of ActionView::Template::Error, inside a request to an action that calls the scope. A system spec that visits a raising page, such as Core's baseline pages spec visiting a stubbed Home, fails its assertions about the error page and then gets the stub back from Capybara, so RSpec reports both together. That failure counts when one of its errors is the stub, raised in an action that calls it, and each other error is an assertion rather than another exception. The harness also asks which behavior the Compiler left out. The Compiler names each item by its GapSet entry's code and pointer, because several Field modifiers on one Field share a readable path. Each example that pins left-out behavior carries the error class its failure raises, and the failure counts only with that class: ActiveRecord::RecordNotDestroyed for a deletion outcome a polymorphic target refuses, ActiveRecord::InvalidForeignKey for one a foreign key rejects, and RSpec::Expectations::ExpectationNotMetError for left-out State Machine applicability, a left-out Validation, a Counter, or a Reference default. When the Compiler cannot supply such an example's setup, Test coverage to complete lists the example instead. Left-out behavior with neither a pinning example nor such an entry fails qualification. Every example the Compiler names must fail. The harness identifies an example by its spec path and its own description, so it rejects a pinning example that shares both with another example. An example that pins left-out behavior sits outside any context block, and its description names the subject it pins, so no other example in its spec shares it. Other examples may still share a description, for example when two authored length rules on one Field share a boundary. Any other failure, a pending example, or an error outside an example fails qualification. So does a bin/ci step that reports no result, or a red step other than RSpec. The map stays in the harness, not in the generated application, the GapSet, or the API.

Behavior to write covers unwritten scopes, Policy stubs, stubbed events, left-out State Machine applicability, left-out deletion outcomes, left-out Validations, Counter maintenance, and Reference defaults.

Left-out Validations

Rails calls a validation on every save, so a Validation the Compiler cannot lower is a write-path skeleton rather than a stub that raises. Its model declares validate :method and an empty private method, named after the Validation's key, whose comment names the Validation, its source pointer, and its spec. A Validation whose owner or error target has no emitted storage stays only in the GapSet.

The model spec gets one example per skeleton, enforces booking.guests_within_capacity. It builds a factory record that the rule rejects and expects the error kind the Rails validator for that rule adds:

it "enforces booking.guests_within_capacity" do
  record = build(:booking)
  record.venue.capacity = 1
  record.guests = 2
  record.validate

  expect(record.errors).to be_of_kind(:guests, :less_than_or_equal_to)
end

The kind is pinned so that another rule cannot pass the example in the left-out rule's place: :exclusion, :taken, :invalid, :blank (:inclusion for a Boolean), :present, and for a comparison the Rails comparison validator's kind for its operator, with equals as :equal_to and not_equals as :other_than. Setup uses the shared evaluator and bounded derivation: factory samples, the literals the rule names, at most four Fields assigned to switch its condition on, and operands at most one required Reference away. A uniqueness example saves one record and repeats its whole tuple on a second. Two related records that the factory builds apart already break an equality between them. The rejected value must pass the generated rules on the same attribute, so the example cannot pass through one of them. When setup cannot build such a record, Test coverage to complete lists the example instead, with the reason.

Each skeleton gets a Behavior to write entry naming its method, model, Validation, source pointer, and error key, and the GapSet consequence says the model declares an empty method for it. A left-out uniqueness also keeps the unique index its tuple names, as data integrity describes. Qualification expects each example to fail with RSpec::Expectations::ExpectationNotMetError.

An expected failure alone does not show that the example passes once the rule is written. The write-path smoke therefore runs a completion probe after its ordinary qualification. It replaces each empty method with a hand-written completion, as an owner would, and then requires every example to pass, FactoryBot.lint to build every factory, and db:seed:replant to seed the development data from an empty database. A pinned example that stays red, a factory that breaks a completed rule, or a seed record that does fails the smoke.

Factory samples

A factory infers a value for every rule the Plan authors on its record, lowered or left out. A text sample is the first candidate that every emitted rule accepts and that no authored rule is known to reject. The candidates are the Field's own key, a few stock words, the development data's literals, and strings that an admitted format generates itself, shortest first: RubyPatternSamples enumerates the profile's character-class grammar, so \A[A-Z][0-9]+\z offers A0. The evaluator checks length, format, exclusion, and literal and same-record comparisons after the Field's normalizations and comparison mode, and a candidate that breaks a left-out exclusion is passed over. A required Reference that an unconditional equality rule ties to a record one required hop away takes that record, as venue { booking&.venue }, which still builds when an example sets booking: nil.

Where inference runs out, the factory still carries its best sample: the plain sample for the Field's kind. A comment at the top of the factory names each rule the sample may break, such as # movie.unique_code needs distinct values, and these samples repeat, so a second record breaks it. Edit the sample if a spec fails. Factories are editable setup; a spec that fails through one points at the sample to fix. Only a cycle of required relationships leaves an Entity without a factory.

Bounded setup and sequences

Ordinary unique text uses conventional FactoryBot progression, such as sequence(:title) { |n| "title_#{n}" }. Reaching the Compiler's 1,000-candidate search budget does not cap an ordinary text sequence without a format constraint. Dates likewise progress in a direction without an authored limiting comparison when the sampled run succeeds. These patterns do not promise valid values at every mathematical counter; Rails validation and storage remain the boundaries if a suite reaches a real limit.

Constraints that end a sampled run early retain finite sequences. Arbitrary formats also retain bounded samples; 1,000 matches do not prove future matches. A format that no counter suffix fits, such as letters only or a fixed width, steps through String#succ from a matching sample, as sequence(:code, Enumerator.produce("AA000", &:succ).take(1000).each), which stops after the values every rule accepts. For a two-character field, the chosen c1 through c9 pattern describes this setup, not the whole valid domain. Generated RSpec support calls FactoryBot.rewind_sequence(:factory, :field) only for emitted bounded sequences before each example, because transactions do not reset Ruby sequence state. Exhaustion within one example remains visible. Ordinary and application-added sequences keep progressing, and an application without bounded sequences receives no reset support file. The existing missing-setup disclosures remain.

Date and datetime samples also satisfy same-record Field comparisons for the write that submits them. Factories, request and system writes, and nested creates share this rule. A write moves only the Fields it submits. Every other Field keeps what the record holds: the factory's value for an update, its current-time default, or nil, which makes Rails skip the comparison. Ordinary samples start in 2000. A comparison with a sequenced Field uses its first value. Contradictory rules stop after a bounded number of passes, and Rails validation reports the failure. Setup assigns at most four values to meet conditional rules.

An example whose write compares a sample with a current_date or current_time default freezes time with Active Support's travel_to Time.utc(2026, 1, 1) do ... end block. For example, an update form may submit checked_out_at, which must be after checked_in_at, and leave checked_in_at at the current_time default it received when the example created the record. A sample that must follow the frozen time moves to 2027, offset by the write's variant, so that update submits Time.utc(2028, 1, 1). Other examples run at the real time. A factory therefore assigns a current-time default a literal sample when a comparison reads it against another value the factory sets, which keeps factory records valid on any date. spec/support/time_helpers.rb includes ActiveSupport::Testing::TimeHelpers when a model compares a Field with a current-time default. Development seeds run at the real time and leave such comparisons unproved.

Conditional rules in setup

Every value a factory, seed, or example writes is checked against the Entity's conditional rules by one evaluator over the Plan's authored meaning, FoundationPlan::Analysis::ValidationEvaluator. It reads a record's stored values and answers whether a rule's when selects the record: true, false, or unknown when the condition reads something it cannot see, such as a path through an Association. The Plan's own unknown, a test that reads a null, selects nothing, as Rails' Boolean condition does for a total operand.

Code and checks

The emitted suite runs through the application's ordinary bin/ci without reading .firstdraft/ or an authoring context file. Compiler qualification separately checks composition, deterministic paths, dependency and method censuses, and the exact reviewed GapSet. Its private Minitest helpers belong to this repository and do not require an emitted Minitest test suite. Broader conditional validation, query, deletion-graph, and application browser coverage remain follow-up work (repository-only). The private AssociatedCollections browser qualification waits for the / destination of its turbo: false Login form before explicitly auditing that page, so the audit does not scan the old Login document. Its initial Login visit retains automatic auditing.

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.