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 actually generated, using records that bounded setup can derive. 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 check status, format, a nonempty body, and access or nonmutation assertions. 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.

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. Optionality uses allow_value(nil).for(:attribute) to check the whole attribute rather than one validator's nil option. A 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. 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. 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.

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 or 403 for a Policy gate and compare every persisted target-record attribute. They 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. 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. Each selected standalone POST receives its own coverage independently of New, an authored return, or associated forms. A partially generated standalone create with no source for its required parent keeps an application-completion step instead of a false successful example; associated 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 and the route-bound parent and current-Account bindings, including competing submitted IDs. 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. 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. 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. Omitting such a Policy input in a standalone or associated POST checks authorization denial rather than 422. Successful form-write examples require a redirect response with a Location, without fixing the default destination. 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 examples do not follow redirects, so protected return-page setup remains a separate browser-coverage concern.

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. Current-Account bindings receive persisted-value assertions. Core supplies Capybara, Selenium, and Turbo synchronization; the Compiler adds no browser framework. These examples do not assert a default redirect, 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. The native select's public name identifies its enhanced control, and its option value identifies the record 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. 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.

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. 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.

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 under #678 (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.