From b821581f2323735524e660a8d6250fc2662693d2 Mon Sep 17 00:00:00 2001 From: Raghu Betina Date: Tue, 13 Oct 2026 00:13:29 -0500 Subject: [PATCH] Describe the write-path, State Machine, and Scaffold output The service's next Compiler keeps far more of an authored Plan, and the Skill's references still described the omissions it replaced, so an agent would warn users about losses that no longer happen and miss what it now has to write. The Plan reference, modeling guide, interview, and examples now say: - decimal, money, conditional presence and absence, and wider and conditional uniqueness Validations lower, and a rule that cannot is an empty validate method behind a failing enforces example; - seeds keep every record no rule is proven to reject and warn on a record that breaks its own rule; reference data seeds everywhere; - several State Machines on an Entity are namespaced AASM machines, and an optional machine gets stubbed events and an empty callback; - a failing Scaffold piece drops only itself, Account inputs no Policy decides use exact email lookup, descriptors may be optional with a Model #id label, and reserved Policy rules and scope keys are renamed with a warning; - cascade and nullify get the conventional inverse, inverses over immutable References are plain Rails, and Counter and defaulted Reference storage stays; - rich_text is stored as text, Trees import, cardinality is skipped as one key, and canonical-form lints warn. The sample-data eval now expects its no-self-follow sample follows to be seeded. The version moves to 0.8.17, with its CHANGELOG entry; the release records the package digest. --- CHANGELOG.md | 41 ++++ evals/create-full-stack-app/cases.json | 2 +- package-lock.json | 4 +- package.json | 2 +- .../claude-plugin/.claude-plugin/plugin.json | 2 +- packages/claude-plugin/package.template.json | 2 +- release/compatibility.json | 2 +- skills/create-full-stack-app/SKILL.md | 2 +- .../references/diagnostics-and-recovery.md | 2 +- .../references/examples.md | 7 +- .../references/foundation-plan-023.md | 231 ++++++++++++------ .../references/interview.md | 13 +- .../references/modeling-guide.md | 58 +++-- 13 files changed, 244 insertions(+), 124 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a494a76..341f2b6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -29,6 +29,47 @@ Entries are newest first. Keep their headings stable. Publish material correctio new entries so readers who advanced their checkpoint see them. Entries explain what changed, why, when it applies, and any useful small example or verification; they need not reproduce commit logs. +## 2026-10-12: More of the Plan reaches the generated app + +**Skills — released in plugin 0.8.17.** Once the First Draft service deploys the matching Compiler, more of an +authored Plan reaches the generated app, and what the Compiler cannot write yet is declared where it belongs, behind a +failing spec. The Skill's references now describe that output. + +**Validations.** Decimal and money comparisons, conditional presence and absence on scalar Fields, and uniqueness over +wider tuples or under a condition now generate; a conditional uniqueness gets a partial unique index. A Validation the +Compiler still cannot lower, such as an `exclusion`, becomes `validate :` with an empty private method and a +Behavior to write entry, plus an `enforces ` model example that expects the Rails error kind wherever the +Compiler can build a record the rule rejects. An unconditional left-out uniqueness over indexable columns still gets +its unique index. Factories build values that satisfy every emitted rule. + +**Sample data.** Seeds keep every record no rule is proven to reject, where they used to drop each record a `format` +or not-equal rule covered. A sample record that breaks its own rule gets a +`foundation_plan.development_data.validation_violated` warning, and the read-back names it. Entity `reference_data` is +seeded in every environment. + +**State Machines.** An Entity with several machines gets one namespaced AASM machine per Field, so events read +`leave_status!`. An optional machine keeps a nullable column, stubbed events, and an empty applicability callback with +a failing example, instead of storage alone. + +**Pages and forms.** A failing input, binding, return, or collection drops only itself, and a create missing a +required value is still generated, with a partial gap. A Reference input to an Account Entity whose Policies don't +decide which Accounts to offer asks for an exact sign-in email. A Primary Descriptor no longer has to be a required +Field: a blank or unshowable one labels the record `Movie #12`. A reserved Policy operation such as `present` becomes +`present_rule?`, and a reserved scope key such as `public` becomes `public_profiles`, each with a warning. + +**Relationships.** A cascade or nullification with no authored inverse gets the conventional `has_many` or `has_one`. +Inverse Associations over immutable References are plain Rails, and the app's `AGENTS.md` names the collection writers +that can still clear an optional immutable key. Counter columns and defaulted References keep their storage; a count +Rails cannot maintain, or the default, is left out behind a failing example or a Test coverage to complete entry, and +a Policy that reads an unmaintained count becomes a stub that denies. + +**Import.** A `rich_text` Field is stored as plain text for now, a Tree imports with its self-Reference, an +Association's `cardinality` is skipped as one key, and canonical-form slips such as a repeated Ordering source warn +instead of blocking Compile. + +Apps compiled earlier are unchanged. A rule they left out can be added by hand in the same shape: a `validate` +declaration, a method, and a spec that pins its error kind. + ## 2026-10-12: Agents preview first and debug failures **Skills — released in plugin 0.8.16.** Once the First Draft service deploys the matching Compiler, a generated app's diff --git a/evals/create-full-stack-app/cases.json b/evals/create-full-stack-app/cases.json index 82ee1cc..e2d1fde 100644 --- a/evals/create-full-stack-app/cases.json +++ b/evals/create-full-stack-app/cases.json @@ -104,7 +104,7 @@ "Keeps the requested username uniqueness and does not add a format Validation to usernames or any other format or not-equal rule the user did not ask for.", "Authors the requested no-self-follow rule as an Entity comparison with one not_equals clause between the two required Follow References, with a Reference error target.", "Proposes connected sample members and follows with a disposable demo Account in the ordinary Plan review.", - "Says in the read-back that First Draft cannot yet check the no-self-follow rule against sample data, so the named sample follows will be left out of the generated seed while the members and demo sign-in remain.", + "Keeps the sample follows, each between two different members, and says the generated seed keeps them; does not claim the no-self-follow rule drops sample records.", "Keeps the requested rule and the sample follows in the Plan rather than dropping either silently to avoid the gaps.", "Makes no service request, Compile or remote write." ] diff --git a/package-lock.json b/package-lock.json index 020171f..8283bb5 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@firstdraft/skills", - "version": "0.8.16", + "version": "0.8.17", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@firstdraft/skills", - "version": "0.8.16", + "version": "0.8.17", "license": "MIT", "devDependencies": { "ajv": "8.20.0" diff --git a/package.json b/package.json index 7265022..28c40f1 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@firstdraft/skills", - "version": "0.8.16", + "version": "0.8.17", "description": "Portable Agent Skills for First Draft", "license": "MIT", "private": true, diff --git a/packages/claude-plugin/.claude-plugin/plugin.json b/packages/claude-plugin/.claude-plugin/plugin.json index 59e0d7e..b21f664 100644 --- a/packages/claude-plugin/.claude-plugin/plugin.json +++ b/packages/claude-plugin/.claude-plugin/plugin.json @@ -2,7 +2,7 @@ "$schema": "https://json.schemastore.org/claude-code-plugin-manifest.json", "name": "firstdraft", "displayName": "First Draft", - "version": "0.8.16", + "version": "0.8.17", "description": "Author a Foundation Plan and compile a bounded Rails, iPhone, and Android application", "author": { "name": "First Draft", diff --git a/packages/claude-plugin/package.template.json b/packages/claude-plugin/package.template.json index 99bfce5..756e472 100644 --- a/packages/claude-plugin/package.template.json +++ b/packages/claude-plugin/package.template.json @@ -1,6 +1,6 @@ { "name": "@firstdraft.com/claude-code", - "version": "0.8.16", + "version": "0.8.17", "description": "First Draft Foundation Plan authoring and Compilation for Claude Code and Codex", "license": "MIT", "type": "module", diff --git a/release/compatibility.json b/release/compatibility.json index 75ab794..c32c5bf 100644 --- a/release/compatibility.json +++ b/release/compatibility.json @@ -1,7 +1,7 @@ { "format": "firstdraft.release-compatibility/1", "component": "skills", - "version": "0.8.16", + "version": "0.8.17", "plugin_source": { "package": "@firstdraft.com/claude-code", "tarball_sha256": "09bad8d5c841dc0c452672b5c8a4cdcc937947dd854f20922a0e3a08d5bf22ac" diff --git a/skills/create-full-stack-app/SKILL.md b/skills/create-full-stack-app/SKILL.md index 770d94f..e4d9dc3 100644 --- a/skills/create-full-stack-app/SKILL.md +++ b/skills/create-full-stack-app/SKILL.md @@ -23,7 +23,7 @@ explicit handoff. Follow [writing notes](references/modeling-guide.md#retain-imp ## Current boundary -Targets plugin 0.8.16, CLI 0.8.2, API 0.7, Plan 0.23; catalog selection is separate. +Targets plugin 0.8.17, CLI 0.8.2, API 0.7, Plan 0.23; catalog selection is separate. - Bounded generation includes Web Accounts/Policies/Scaffolds, development data, and selected iPhone/Android clients. [Appearance](references/foundation-plan-023.md#application-and-clients) controls theme and native colors, diff --git a/skills/create-full-stack-app/references/diagnostics-and-recovery.md b/skills/create-full-stack-app/references/diagnostics-and-recovery.md index 787febd..42d9a45 100644 --- a/skills/create-full-stack-app/references/diagnostics-and-recovery.md +++ b/skills/create-full-stack-app/references/diagnostics-and-recovery.md @@ -9,7 +9,7 @@ interleaved output fail closed. Branch on the object's stable `error` and struct human-readable `detail` or broad process exit status. This plugin bundles `@firstdraft.com/cli@0.8.2`. Its exact reviewed revision and runtime digest are owned by -[the CLI contract configuration](https://github.com/firstdraft/skills/blob/claude-v0.8.16/script/cli-contract/config.mjs) +[the CLI contract configuration](https://github.com/firstdraft/skills/blob/claude-v0.8.17/script/cli-contract/config.mjs) at this plugin's protected release tag. Check the command surface rather than assuming the version alone establishes compatibility. These source checks do not prove plugin/catalog publication, service authentication, staging compatibility, or a complete user journey. diff --git a/skills/create-full-stack-app/references/examples.md b/skills/create-full-stack-app/references/examples.md index 9fe8a09..b989caf 100644 --- a/skills/create-full-stack-app/references/examples.md +++ b/skills/create-full-stack-app/references/examples.md @@ -160,9 +160,10 @@ only after a release date exists; nil title handling still comes from requiredne } ``` -The same release also admits unconditional or bounded conditional ordered integer-literal range comparisons and -conditional text or ordinary-Reference presence and absence. See the Foundation Plan reference for the exact closed -subset. A conditional maximum does not become an unconditional form `maxlength`. +The same release also admits unconditional or conditional ordered literal comparisons on integer, decimal, money, +and date Fields, conditional presence and absence on scalar Fields and ordinary References, and wider and +conditional uniqueness. See the Foundation Plan reference for the exact closed subset; a rule outside it is declared +as an empty method, with a failing spec where the Compiler can build a record the rule rejects. A conditional maximum does not become an unconditional form `maxlength`. ## Public mutation, show projection, returns, and destroy diff --git a/skills/create-full-stack-app/references/foundation-plan-023.md b/skills/create-full-stack-app/references/foundation-plan-023.md index f36e520..332c0af 100644 --- a/skills/create-full-stack-app/references/foundation-plan-023.md +++ b/skills/create-full-stack-app/references/foundation-plan-023.md @@ -81,7 +81,7 @@ The bundled schema was copied byte-for-byte from release or execution evidence. This plugin's bundled CLI and pinned contract check use the exact reviewed CLI revision and runtime digest in -[the CLI contract configuration](https://github.com/firstdraft/skills/blob/claude-v0.8.16/script/cli-contract/config.mjs) +[the CLI contract configuration](https://github.com/firstdraft/skills/blob/claude-v0.8.17/script/cli-contract/config.mjs) at this plugin's protected release tag, as contract provenance rather than release or execution evidence. The CLI exposes `generate uuid`, `generate application-key`, `plan init`, `plan push`, `plan status`, local `plan compile` (equivalent to `--output .`), explicit `plan compile --github`, @@ -306,14 +306,23 @@ iPad remain outside the public boundary. `account`, `fields`, `references`, `associations`, `predicates`, `orderings`, `validations`, `trees`, `policies`, `scaffold`, and `reference_data`; each Entity has `subject_uuid`, `key`, `name`, and `primary_descriptor`. Only the profile's exact current slices are realized. Do not omit a schema-valid family merely because its current lowering is -partial. +partial. Entity `reference_data` is seeded in every environment through `db/seeds.rb`, with `find_or_create_by!` +keyed by each record's identity Field, so a rerun never duplicates a row; a reference record whose identity is a +State Machine Field is left out with a gap. A Tree imports with its self-Reference generated; the hierarchy's own +rules are a `tree.partially_generated` gap, and a tree Ordering is a scope stub that raises until it is written. +An Association's `cardinality` is skipped as one key with a service-support gap, and the Association still imports. +Canonical-form slips warn rather than block: a comparison of a path with itself, a null test on a required Field, a +repeated Ordering source or sign-up input, an optional source for a required sign-up input, and a redundant +`maximum` on an Association. The target treats each rule as written. The importer retains Entity `orderings`, and the current target emits a bounded named-Ordering slice when its terms, stability, nullability, and consumers meet the profile. Unsupported Orderings remain exact gaps, and each record's consequence says what the generated app does. When it says the named scope raises `NotImplementedError` until it is written, the Compiler emitted that scope as a stub, and a Scaffold index or collection that selects it calls the -stub, so the page raises until then. Otherwise the scope is left out, as when the model cannot take that name, and -the consequence says what the page does without it. `implicit_order_column` is +stub, so the page raises until then. A key that Rails or a selected gem reserves, such as `public`, becomes the key +plus the table name, as `Profile.public_profiles`, and the Analyzer warns with +`foundation_plan.rails_target.scope.renamed`. Otherwise the scope is left out, as when even the renamed name is taken, +and the consequence says what the page does without it. `implicit_order_column` is schema-valid, but the current integrated target realizes only the captured Case Chat `message.sent_at, id` authored shape. Ordered finders otherwise use the profile's `created_at, id` default. This fallback affects `first` and `last`, not ordinary Relation or Association loading or Scaffold list order; use the matching GapSet for other @@ -346,11 +355,15 @@ and application name. ### Entities, descriptors, and Fields -A Primary Descriptor may select a required Field owned by that Entity or a schema-supported system Field. The -current target also admits one required ordinary single-target forward Association hop when the target Entity's -descriptor terminates in a required emitted scalar or system Field. It preloads that hop for admitted Web consumers. -The whole-graph analyzer rejects an optional Field descriptor; multi-target, longer-chain, optional-source, cyclic, -or otherwise unsupported Association descriptors remain exact gaps. A Field may use these types: +A Primary Descriptor may select a Field owned by that Entity or a schema-supported system Field. The current target +also admits one required ordinary single-target forward Association hop when the target Entity's descriptor +terminates in an emitted scalar or system Field, and preloads that hop for admitted Web consumers. Each page writes +the label where it shows a record. An optional Field descriptor shows `Movie #12` where the value is blank, as +`movie.title.presence || "#{movie.model_name.human} ##{movie.id}"`, and an enum shows its value's label. Any other +descriptor, such as a two-hop chain, a `money` or upload Field, or a multi-target Reference, labels every record +`Movie #12` and is listed as `primary_descriptor.partially_generated`; the pages, Reference options, and specs that +show those records are still generated, and Reference options for such a label are ordered by `id`. An optional +Association descriptor is still an analysis error. A Field may use these types: - `attachment` - `boolean` @@ -366,15 +379,20 @@ or otherwise unsupported Association descriptors remain exact gaps. A Field may - `long_text` - `money` - `position` +- `rich_text` - `secure_token` - `short_text` - `state_machine` - `time_zone` - `url` -That is the conditional import list, not the complete schema vocabulary. A State Machine Field retains its states, -initial state, transitions, and transition effects. The current target realizes exactly one required unconditional -State Machine per Entity when the AASM helper surface is safe. AASM owns initialization, validation, named events, +That is the conditional import list, not the complete schema vocabulary. A `rich_text` Field lowers like `long_text` +for now: a `text` column, a textarea, and plain-text display, with formatting and embedded files listed as its +partial gap. A State Machine Field retains its states, +initial state, transitions, and transition effects. The current target realizes each required unconditional State +Machine as AASM when the AASM helper surface is safe. An Entity with several machines gets one AASM machine per Field, +each namespaced by its Field key, so events read `leave_status!` and `may_leave_status?` and predicates read +`status_left?`; adding a second machine renames the first machine's events. AASM owns initialization, validation, named events, state scopes, and the generated effects; behavior-emitted state columns keep `NOT NULL` without a SQL default. Native naming options resolve supported helper collisions. An event is generated when it is effect-free or has one `set_field` effect that sets an optional, mutable, non-derived `datetime` Field on the same record to the current @@ -386,10 +404,15 @@ stubbed event, and its State Machine examples fail until then; no generated page and spec setup never fire a stub, so a development record whose authored state needs one is left out of `db/seeds/development.rb` with a development-data gap, and an example whose setup needs one is listed under Test coverage to complete. The GapSet keeps the event's `foundation_plan.gap.state_transition.effects_not_generated` -record, whose consequence names the stubs. Required storage-only states retain the initial-state SQL default and -inclusion validation; optional machines keep nullable storage without a default. Optional, conditional, -multiple-per-Entity, and guarded machines, and those with an unresolved helper collision, retain storage with -behavior gaps. Failed transitions retain ordinary AASM/Rails object and transaction behavior; an application may need +record, whose consequence names the stubs. An optional machine, which applies only where its `settings.when` holds, +gets no `aasm` block, because AASM would fill every blank state column with the initial state. Its column stays +nullable without a default, with an inclusion validation that admits `nil`, and each event is a stub under the names +AASM would give it. Its applicability is left out behind an empty `before_validation` callback, such as +`assign_initial_role`, so every new record keeps `nil` until it is written. Its model example fails until then where +the Compiler can build a record the condition selects, and Test coverage to complete lists it otherwise; the README's +Behavior to write lists the callback and the stubs. A machine whose helper names collide in a way no +namespace resolves, including with an earlier machine on the Entity, keeps only storage: a required one keeps the +initial-state SQL default and inclusion validation, with behavior gaps. Failed transitions retain ordinary AASM/Rails object and transaction behavior; an application may need to reload or reset a failed object before reusing it. AASM 6.0.0 namespaced state scopes can query a prefixed value instead of the stored state: `status_active` can query `status_active` when the stored state is `active`. Check an affected application query against its stored @@ -552,19 +575,29 @@ Project graph mechanically maintains its same-key forward Association. Reference is a schema error rather than an importer or Compiler capability diagnostic. The current target emits a bounded single-target Reference slice with Boolean `required`, `one_to_one`, and -`immutable`, no Reference default or realization, and one of the three deletion outcomes: `restrict`, -`nullify_reference`, or `delete_referencing_record`. It emits the same-key forward traversal, UUID foreign-key +`immutable`, no realization, and one of the three deletion outcomes: `restrict`, `nullify_reference`, or +`delete_referencing_record`. A Reference `default` keeps the Reference's storage, `belongs_to`, and consumers while +the default itself is left out, with a `foundation_plan.gap.reference.default_not_generated` record: a +`reference_record` default behind a model example that saves a record without one, and an environment default behind +a Test coverage to complete entry for the create request. A literal null default needs nothing. It emits the same-key forward traversal, UUID foreign-key storage, matching nullability, an index, and a post-table foreign key. `one_to_one: true` makes that index unique and adds logical Association uniqueness. The post-table migration supports self-References and migration-order cycles. An eligible unpredicated inverse carries authored deletion through Rails `dependent:` options: restriction gives model errors, nullification clears the Reference, and deleting referencing records uses ordinary deletion or destroy according to emitted callbacks and downstream Associations. Plain `NO ACTION` foreign keys preserve integrity. -A `restrict` Reference with no authored inverse gets the conventional one when the app can delete its target, through -the target's `destroy` route or a cascade from an Entity that has one: a Deadline's `lease` Reference adds +A Reference with no authored inverse gets the conventional one. A `delete_referencing_record` or +`nullify_reference` outcome needs it to happen at all, so it gets one wherever the target is deleted: a Trip's +`owner` Reference that deletes Trips adds `has_many :trips, dependent: :delete_all` to the owner, and a one-to-one +Reference adds `has_one`. A `restrict` outcome gets one when the app can delete its target, through the target's +`destroy` route or a cascade from an Entity that has one: a Deadline's `lease` Reference adds `has_many :deadlines, dependent: :restrict_with_error` to Lease, so deleting a Lease that still has Deadlines shows "Cannot delete record because dependent deadlines exist" instead of an error page. An Entity that references the same target twice gets Reference-prefixed names, such as `landlord_leases`. If that name is already a member of the -target model, the outcome stays a partial gap and the delete raises the database error. Models declare restrictions +target model, a restriction stays a partial gap and the delete raises the database error. A cascade or +nullification is instead left out behind a deletion example in the target's model spec that fails with +`ActiveRecord::InvalidForeignKey`, and the README's Behavior to write lists it. A one-to-one immutable nullification +is left out the same way, because Rails' `has_one` nullifies through `update_columns`, which refuses a readonly +attribute. Models declare restrictions before cascades and delete a child that restricts its sibling first, so deleting a Household removes its Dinners before the Meals they restrict. A refusal's message comes from the record that refused, even a cascaded child, except through a one-to-one cascade: Rails then keeps every record but shows no message, and the GapSet lists that @@ -572,16 +605,17 @@ restriction as a partial gap. A cascade into records that restrict their own par Categories, keeps `dependent: :delete_all` when that restriction is their only callback, so one statement deletes the whole tree; a Category outside that delete whose parent is in it makes the delete raise the database error, which the GapSet also lists. -Missing or ambiguous inverse carriers for the other outcomes and destructive cascade cycles can leave the authored -deletion consequence as a partial gap; the Compiler does not synthesize an inverse or callback scheduler for them. +Ambiguous inverse carriers and destructive cascade cycles leave the authored deletion consequence as a partial gap; +the Compiler does not choose a carrier or schedule callbacks for them. Callback-bypassing writes do not establish Rails lifecycle behavior. A Reference with several targets lowers to its `realization`. `polymorphic` emits `_type` and `_id` columns, `belongs_to ..., polymorphic: true`, and an inclusion validation of its type column. No foreign key can refuse deleting a target, so each target carries the deletion outcome on `has_many ..., as:`, or `has_one` when -one-to-one, authored or inferred. A target cannot carry it when it has several inverses, a destructive cascade cycle -reaches it, or its conventional inverse name is taken, and no target of an optional or one-to-one immutable -polymorphic Reference can. Such a target refuses its own deletion instead, as a foreign key would: while a record +one-to-one, authored or inferred, including over an optional or one-to-one immutable Reference. A target cannot carry +it when it has several inverses, a destructive cascade cycle reaches it, or its conventional inverse name is taken, +and no target can carry the nullification of a one-to-one immutable Reference. Such a target refuses its own deletion +instead, as a foreign key would: while a record references it, a private `before_destroy` adds the error `restrict_with_error` adds, such as "Cannot delete record because dependent comments exist", and halts the delete, which a generated destroy action shows as an alert. Storage, the `belongs_to`, and the type list stay generated. The refusal completes `restrict`. For @@ -595,15 +629,20 @@ target, per-target `belongs_to`, a generated reader and writer, and a check that most one when optional; each target's inverse follows the single-target deletion rules above. A required multi-target Reference on the Account Entity is generated as a required single-target one is: sign-up has no input for it, so every sign-up fails, and the GapSet lists a `foundation_plan.gap.account.partially_generated` record. -Predicated inverses over either shape, Scaffold inputs and selects, descriptors, Policies, Predicates, Orderings, -counters, positions, indirect Associations through it, and seeds for a one-to-one multi-target Reference remain -gaps, and a standalone create that needs a required multi-target Reference is omitted. - -The current Association catalog includes supported mutable direct inverses, the exact required-immutable `has_many` -inverse, selected predicated direct Associations, several first-level indirect collections, and one nested-through -form. These are per-Association shape rules, not per-Entity or per-Plan quotas. Author each traversal the product -needs. Aliases, defaults, broader paths, cardinality, and unsupported predicates or consumers can produce exact -gaps. Scaffold input support is a separate consumer decision +Predicated inverses over either shape, Scaffold inputs and selects, Policies, Predicates, Orderings, positions, +indirect Associations through it, and seeds for a one-to-one multi-target Reference remain gaps; a descriptor through +it labels records `Model #id`. A standalone create that needs a required multi-target Reference is generated with a +`partially_generated` gap: it cannot save until application code supplies the Reference. + +The current Association catalog includes direct inverses, `has_many` or `has_one`, over mutable and immutable +References, each with the `dependent:` option that carries the authored outcome; predicated direct Associations over +any single-target Reference; several first-level indirect collections; and one nested-through form. Over an +optional immutable Reference whose inverse has no cascade carrier, a collection's `delete`, `delete_all`, `clear`, +`replace`, and IDs writers still set the key to `NULL` with SQL that skips `attr_readonly`, so the app's `AGENTS.md` +names each such collection; it also names each indirect collection over an optional immutable source, for its +`delete_all(:nullify)`. A required Reference's `NOT NULL` rejects those writes, and a cascade carrier deletes the rows +instead. These are per-Association shape rules, not per-Entity or per-Plan quotas. Author each traversal the product +needs. Aliases, defaults, broader paths, cardinality, and unsupported predicates or consumers can produce exact gaps. Scaffold input support is a separate consumer decision from Reference storage. Preserve the authored relationship meaning and review the matching consequence. ### Counters @@ -614,13 +653,18 @@ followers. It belongs to the Entity that owns the counted Association, is always `{"key": "likes_count", "name": "Likes", "type": "counter", "required": true, "settings": {"counts": "post.likes"}}` plus its own `subject_uuid`. -The Compiler emits an integer column that starts at zero, maintained by Rails `counter_cache` on the counted -record's `belongs_to`. Count a direct referenced-side Association over an ordinary single-target Reference, such as -`post.likes` over `like.post`. For followers, count the direct `user.follower_links` over `follow.followed`, not an -indirect `user.followers` through those links; when each pair is unique, both give the same number, and the gap -reason for an indirect counter names the direct Association to count instead. Author one counter per Reference: two -counters through the same Reference both remain gaps. Indirect, filtered (predicated), and polymorphic counts remain -gaps. +The Compiler always emits an integer column that starts at zero. Rails `counter_cache` on the counted record's +`belongs_to` maintains it for a direct referenced-side Association over an ordinary single-target Reference, such as +`post.likes` over `like.post`; for a polymorphic Reference when every target counts it in a column of the same name; +and for each target of an exclusive arc. For followers, count the direct `user.follower_links` over +`follow.followed`, not an indirect `user.followers` through those links; when each pair is unique, both give the same +number. Any other counter, such as an indirect or filtered (predicated) count or a second counter through the same +Reference, keeps its column, reading 0, while its maintenance is left out behind a model example that creates one +counted record and expects the count to read 1. A filtered count, or one whose example would need a Reference the +Compiler does not emit or setup that breaks a conditional Validation, gets a Test coverage to complete entry instead. +The README's Behavior to write lists the counter, and the GapSet lists the Field as partially generated. A Policy that +reads an unmaintained counter, directly, through the signed-in Account, or through an Association's Predicate, would +decide access from that 0, so it becomes a Policy stub that denies until the counting is written. A counter is never a form input or a development-data value. An authored counter input is dropped with a gap while the rest of the form remains, and an assigned development value becomes a development-data gap. Explicit index, @@ -632,9 +676,10 @@ counts stale; Rails `reset_counters` repairs one record, and no repair task is g The current Rails Validation subset admits: -- unconditional or bounded conditional ordered literal comparisons on stored integer or date Fields, using - `greater_than`, `greater_than_or_equal_to`, `less_than`, or `less_than_or_equal_to`; date bounds start at - `1582-10-15` because earlier Gregorian values differ from ordinary Rails casting; +- unconditional or conditional ordered literal comparisons on stored `integer`, `decimal`, `money`, or `date` + Fields, using `greater_than`, `greater_than_or_equal_to`, `less_than`, or `less_than_or_equal_to`; decimal and + money bounds are exact, and date bounds start at `1582-10-15` because earlier Gregorian values differ from + ordinary Rails casting; - an unconditional same-record comparison between two stored `date` Fields or two stored `datetime` Fields with those ordered operators, such as a check-out after its check-in. A Field-owned rule reports on its own Field and an Entity-owned rule on its error target. Each side's requiredness owns its missing value, and the comparison is @@ -644,30 +689,42 @@ The current Rails Validation subset admits: - unconditional or conditional `length` on `short_text` or `long_text`, using `minimum`, `maximum`, or `exact_length`; - unconditional positive `format` on stored `short_text` in the bounded whole-value printable-ASCII grammar; -- conditional `presence` or `absence` on text Fields; -- conditional `presence` or `absence` on an admitted ordinary Reference; -- unconditional Entity `uniqueness` over one or two required emitted `short_text` or `date` Fields or ordinary - one-column References, with a supported Field error target, or a logical-Reference target for a composite tuple; and -- the exact three-member tuple of two required ordinary References plus one emitted required non-ordinal enum Field, - with a Reference error target. - -Conditions allow total direct same-record Field null tests and, for Reference presence or absence, equality to a -required non-ordinal enum literal; `not`, `and`, and `or` combine the admitted atoms. Direct enum equality reuses -the emitted predicate. Required numeric input uses Rails numericality, Boolean Fields use inclusion in -`[true, false]`, and other required scalars use presence. Optional numeric input allows nil; unconditional integer -bounds share numericality with requiredness. Date comparison and length rules leave missing-value feedback to -requiredness. Admitted uniqueness uses a native Rails validator and a matching unique index, including logical -Reference targets. Entity errors must target a Field or Reference; this Plan has no record-wide custom error target. -Ordinary Rails application code can still use `errors[:base]` after Compilation. Conditional Field-to-Field -comparisons, relationship-path or dynamic operands, `datetime` literal comparisons, broader comparisons, patterns, -presence/absence, uniqueness tuples, conditions, owners, and `exclusion` can produce service- or target-support gaps. -They remain invalid only when the admitted meaning itself violates semantic rules. - -The current Analyzer cannot prove an admitted `format` or `not_equals` rule for development records. Each record -the rule covers, and each record that references it, gets a -`foundation_plan.gap.development_data.record.not_generated` gap and is left out of the seed, as the -[modeling guide's example](modeling-guide.md#prepare-data-for-the-first-preview) shows. Name the dropped sample -records in the read-back. +- conditional `presence` or `absence` on a text, `url`, `language_code`, `time_zone`, `integer`, `decimal`, + `money`, `date`, or `datetime` Field, or on an admitted ordinary Reference; and +- Entity `uniqueness` over any number of required stored `short_text`, `integer`, `decimal`, `money`, `date`, + `datetime`, non-ordinal `enum`, `url`, `language_code`, `time_zone`, or `secure_token` Fields and ordinary + one-column References, with a Field error target or, for a composite tuple, a Reference error target. + +Conditions allow direct same-record Field null tests and equality or membership (`in`) between a required +non-ordinal enum or State Machine Field and literals in its domain; `not`, `and`, and `or` combine them. Direct enum +equality reuses the emitted predicate. A conditional `uniqueness` admits only a conjunction: its validator adds +`conditions:` and `if:`, and its unique index is partial over the same records, as +`validates :venue, uniqueness: {conditions: -> { where(kind: "vip") }}, if: :vip?`. Required numeric input uses +Rails numericality, Boolean Fields use inclusion in `[true, false]`, and other required scalars use presence. +Optional numeric input allows nil; unconditional numeric bounds share numericality with requiredness. Date +comparison and length rules leave missing-value feedback to requiredness. Admitted uniqueness uses a native Rails +validator and a matching unique index, including logical Reference targets. Entity errors must target a Field or +Reference; this Plan has no record-wide custom error target. Ordinary Rails application code can still use +`errors[:base]` after Compilation. + +A Validation the target cannot lower, such as an `exclusion`, a conditional Field-to-Field comparison, a +relationship-path or dynamic operand, a `datetime` literal comparison, a pattern outside the grammar, or a +conditional uniqueness outside a conjunction, is a skeleton. The model declares `validate :` and an empty +private method whose comment names the Validation and its spec, and the README's Behavior to write lists it. When the +Compiler can build a record the rule rejects, the model spec gets an `enforces ` example that expects the Rails +error kind, such as `:exclusion` or `:taken`, and fails until the method is written; otherwise the README's Test +coverage to complete names the missing example. Until the method is written, the app accepts records the rule +forbids. An unconditional left-out uniqueness still gets the unique index its tuple names when every member is an +indexable, unencrypted Field column or an ordinary Reference foreign key, so the database refuses a duplicate; a +conditional one gets no index. A Validation whose owner or error target has no emitted storage stays only in the +GapSet. Schema-valid meaning remains invalid only when it violates semantic rules. + +Development seeds keep every record that no authored Validation, lowered or left out, is proven to reject. A +record that breaks its own Validation gets a `foundation_plan.development_data.validation_violated` warning and is +left out of the seed with a `foundation_plan.gap.development_data.record.not_generated` gap. A rule the Analyzer +cannot decide keeps the record, so seeding raises if the record breaks it. A seed date before `1582-10-15` under a +generated date comparison is still left out. Name each warned record in the read-back, as the +[modeling guide's example](modeling-guide.md#prepare-data-for-the-first-preview) shows. ### Predicates @@ -715,8 +772,8 @@ belongs to, such as a shop or team, [groups and memberships](#groups-and-members a membership record, so the Account Entity has no Reference to the group. The Compiler generates none of these sources yet. With a registration input or with no source, it generates the Reference with the partial gap above, so every sign-up fails until its control or default is added in Rails after Compile. A default on the Reference itself -instead leaves the whole Reference out with a `foundation_plan.gap.reference.not_generated` record, so its column -and relationship are Rails work after Compile. +keeps the Reference's column and relationship while the default is left out, as [References and +Associations](#references-and-associations) describes, so sign-up fails until the default is written in Rails. `account.verification` decides whether sign-up signs the person in. Without it, sign-up asks for the email twice and signs the person in, Change email takes effect at once, and the app can accept sign-ups before any email provider is @@ -751,9 +808,11 @@ emits such a Policy with a stub. Its rule returns `false`, so every page, link, relation scope raises `NotImplementedError`, so every page that lists or looks up records through it raises, including a parent's collection, Reference options on another Entity's form, and Home when Home shows that index. The Scaffolds and projections it guards are still generated, and the README's Behavior to write lists the stub with -the pages it denies and raises on. A Policy that delegates to a stub follows it and is not a gap itself. Only a -reserved rule name, a Policy class name collision, a delegation cycle, or an unmet Account prerequisite still omits -a Policy, because nothing could hold the stub. Do not infer that all Policies are supported or that all Scaffolds +the pages it denies and raises on. A Policy that delegates to a stub follows it and is not a gap itself. An operation +that names a method every Policy already has, such as `present`, is defined privately as `present_rule?`, every +generated caller uses that name, and the Analyzer warns with `foundation_plan.rails_target.policy.rule_renamed`. +Only a Policy class name collision, a renamed rule that another Policy on the Entity already defines, a delegation +cycle, or an unmet Account prerequisite still omits a Policy, because nothing could hold the stub. Do not infer that all Policies are supported or that all Scaffolds are public; inspect the whole matching GapSet. A Policy that compares nothing with `current_account` admits signed-out visitors too. A `read` Policy that compares @@ -774,7 +833,9 @@ gate Policy on the Account Entity with a custom operation such as `use_app`, all displayed Association uses `"public"` when everyone who passes the page's gate may see its records, because that gate still applies. When those records need their own rule, bind that Policy to the nested record instead. A Policy on a record one level inside a collection can be generated; a gate binding there, or a protected item any -deeper, is omitted with a gap. The [signed-in gate example](examples.md#signed-in-gate-and-one-tap-records) shows +deeper, is omitted with a gap. A collection that applies a Policy owned by a different Entity than its records, such +as a profile's Trips list under `user.read_self`, is left out alone, with a gap naming both Entities; the Policy and +the pages it guards stay. When that Policy belongs to the page's own Entity, bind it to `"record": "scaffold_record"`. The [signed-in gate example](examples.md#signed-in-gate-and-one-tap-records) shows the shape. A Policy that compares a Reference with `current_account` admits only records whose Reference holds that Account. On @@ -953,8 +1014,12 @@ An associated `create_form` can use that definition without selecting standalone The current Web target realizes bounded standard routes, public and Policy-controlled request checks, direct and recursive projections, Predicate and Ordering selection, cursor pagination, Field and Association inputs, server bindings, associated-create entry points, and optional return overrides. Account settings support the default -signup Fields and bounded authored profile/update customizations described above. Each consumer still has shape-specific prerequisites. Unsupported children are omitted -or partially generated with exact GapSet records; a supported sibling may survive. +signup Fields and bounded authored profile/update customizations described above. Each consumer still has shape-specific prerequisites. +An unsupported piece drops only itself: an input or binding the target cannot lower is left out and listed while the +rest of the form stays, an authored return the target cannot emit falls back to the default destination and stays +listed, and a create missing a required value is generated with a `partially_generated` gap. Bindings lower one hop +from `current_account`, such as `current_account.merchant` or one Account Field of the same kind, plus +`current_date` and `current_time`. An omitted index projection shows the primary descriptor. An explicit projection shows only its items and does not add the descriptor, so its rows show a View link in place of the record's name; put the descriptor first. A create @@ -968,7 +1033,13 @@ searches its target's records on the server as the person types when the target' `short_text` or `long_text` Field, on the target or one hop away, that is neither redacted from logs nor encrypted. The page that renders the form answers that search behind its own checks, so it lists only records the form may offer, to people the page admits. Without JavaScript, a New form whose required Reference searches cannot create its -record. Any other descriptor loads every target record into the form. A `current_account` binding may target only +record. Any other descriptor loads every target record into the form. When the target is an Account Entity that has +Policies and none of them decides which Accounts to offer, the form instead asks for the Account's exact sign-in +email, normalized as the Account stores it, and create and update assign the Account it finds. An Account Entity +without Policies, or an input whose options are public, keeps the ordinary picker. A required Reference +that matches nothing fails its `belongs_to`, and an optional one reports that no account has that email. The +Analyzer warns at each such input with `foundation_plan.rails_target.scaffold.account_lookup`, because whoever saves +the form learns whether an address has an account. A `current_account` binding may target only the realized Account Reference in a non-public Account-backed context; it cannot silently turn a public create into an authenticated request. @@ -977,8 +1048,8 @@ the standalone endpoint remains generated with its authored inputs and authoriza gap naming the missing value. No request to that endpoint can succeed, because nothing supplies the parent. That output conflicts with First Draft's code-worth-keeping rule and is tracked as a Compiler defect. Preserve working associated creation and record the unfinished standalone behavior for implementation; do not invent an editable -parent or binding. Unrelated required values still need an admitted source, and a selected standalone create with -no admitted associated form needs the complete source set. +parent or binding. Any other required value without an admitted source makes a create partially generated the same +way. New and Create share the create Policy. New authorizes after URL-parent assignment and explicit bindings, before editable values exist; Create authorizes the submitted record. A Policy requiring an editable `token.author` to diff --git a/skills/create-full-stack-app/references/interview.md b/skills/create-full-stack-app/references/interview.md index daf36c5..e8cdcb1 100644 --- a/skills/create-full-stack-app/references/interview.md +++ b/skills/create-full-stack-app/references/interview.md @@ -72,9 +72,9 @@ Steps 1 and 2 are the opening turn, after the research and any fit, with at most a few minutes, then write a rough Plan, push it, and read its analysis. Keep the rough Plan small: the main records and their key Fields, any Account, who can see what, and requested phone apps; for a backbone, only the part First Draft builds. Tell the user in a few plain lines what First Draft will build and what it will not - build yet, for example "Formatted descriptions aren't generated yet, so posts start without one; I'll add them in - Rails after" or "The list of posts that mention a word is generated, and I'll write its search in Rails right - after". Then let the analysis steer the remaining questions: ask about consequences the user can choose between, + build yet, for example "Descriptions are stored as plain text for now; I'll add formatting in Rails after" or + "The list of posts that mention a word is generated, and I'll write its search in Rails right after". Then let + the analysis steer the remaining questions: ask about consequences the user can choose between, not about gaps they cannot change. Keep private access and requested phone apps in the Plan. Report each gap as the analysis gives it, even when its reason names no cause or contradicts these references; the deployed First Draft may not generate it yet. Do not trim the Plan or push trial or example Plans to find a cause: each push @@ -106,11 +106,12 @@ with more than one target needs `realization.rails_association`. Both values com the developer expects to add target types later without a migration. No foreign key refuses deleting a target, so each target carries the deletion outcome on its inverse. A target that cannot, such as one with two inverses or a Comment that replies to Comments, refuses its own deletion while a record references it, and so does every - target of an optional or one-to-one immutable polymorphic Reference. A cascade or nullification there is left to - write after Compile. + target of a one-to-one immutable polymorphic Reference whose outcome is nullification. A cascade or nullification + there is left to write after Compile. Forms, selects, Policies, Predicates, and other screens that cross either shape remain gaps, and a standalone create -that needs a required multi-target Reference is omitted. A Policy that crosses either shape is emitted as a stub: +that needs a required multi-target Reference is generated but cannot save until the Reference is supplied in Rails. A +Policy that crosses either shape is emitted as a stub: its rule denies and its scope raises until it is written, and the screens it guards are still generated. Say so. Make implementation choices yourself: use the convention default without asking, and name it with a one-line why diff --git a/skills/create-full-stack-app/references/modeling-guide.md b/skills/create-full-stack-app/references/modeling-guide.md index e60651f..4a45d68 100644 --- a/skills/create-full-stack-app/references/modeling-guide.md +++ b/skills/create-full-stack-app/references/modeling-guide.md @@ -39,9 +39,8 @@ Use these distinctions: Ask whether a concept needs independent records, merely describes another record, or is derivable. Each structured subject becomes code: an Entity alone adds a model, migration, factory, and model spec, and a Field adds a column and a control on each form that lists it. An `image` or `attachment` Field adds `has_one_attached` in place of a -column. A counter listed as a form input gets no control and a `foundation_plan.gap.scaffold.input.not_generated` -record; a secure token listed as one also leaves out its whole form with -`foundation_plan.gap.scaffold.definition.not_generated`. +column. A counter or secure token listed as a form input gets no control and a +`foundation_plan.gap.scaffold.input.not_generated` record, and the rest of the form stays. ## Interview toward one coherent candidate @@ -141,18 +140,19 @@ validates :code, format: { } ``` -The current Analyzer cannot prove that rule, or a not-equal ("must be other than") comparison, against development -records, so it drops every sample record the rule covers and every record that references one. With this rule, the -sample team Rockets and Alice's membership in it are left out of `db/seeds/development.rb`, and a username pattern -can drop every member and, with them, their posts and the demo sign-in. Say in the read-back which sample records -the rule drops. +The seed keeps every sample record that no rule is proven to reject. Rockets' code `rockets` matches, so the team and +Alice's membership in it are seeded. A sample record that breaks its own rule, such as a code `Rockets!`, gets a +`foundation_plan.development_data.validation_violated` warning and is left out of `db/seeds/development.rb` with the +records that reference it. Fix the sample value, or say in the read-back which records the warning names. A rule the +Analyzer cannot decide keeps the record, and seeding raises if the record breaks it. Make the dataset and any source reuse visible in the existing semantic read-back. Approve it with the Plan, not row by row. Honor an explicit empty-data choice, omitting `development_data` when there are no records. After Compile, development seeds are ordinary source. Keep Entity `reference_data` for facts the app needs in every environment and `development_data` for disposable -development exploration. Reference-data Rails emission remains incomplete. Use supported typed literal Field +development exploration. Reference data is seeded in every environment through `db/seeds.rb`, found by its identity +Field so a rerun never duplicates it. Use supported typed literal Field assignments and `reference_record` links, not Faker, Ruby, file paths disguised as uploads, or new runtime-value syntax. Label historical dates honestly; a fixed literal does not remain “today.” Preserve structured requests and review actual data/dependency gaps. Keep outside-grammar media or import work in implementation notes rather than @@ -227,7 +227,8 @@ Do not substitute another index or weaken access. See the For each Entity: 1. Choose a stable lower-snake-case `key` and a human-facing singular `name`. -2. Select a typed `primary_descriptor` that can identify a record to a person. A selected Field must be required. +2. Select a typed `primary_descriptor` that can identify a record to a person. Prefer a required Field: an optional + one shows `Team #12` where it is blank, and a descriptor the target cannot show labels every record that way. The descriptor adds no unique index or uniqueness check, so two Teams may share a name. 3. Choose a semantic Field `type`, not a target column type. 4. Decide requiredness, immutability, default, normalization, and structured validations independently. @@ -308,11 +309,12 @@ standard closed Validation families when they express the product requirement: | A retirement reason is needed only after `retired_at` is set | Optional text plus conditional `presence`; conditional `absence` expresses that a value must be missing in a particular condition. | | A title must contain at most 80 characters | `length`; use minimum, maximum, or exact length according to the actual rule. | | A product code contains only uppercase letters and digits | `format` on appropriate text; use the compatible pattern grammar, not arbitrary validation code. | -| Usernames may not be `admin` or `support` | `exclusion` of a fixed typed literal set; this expresses meaning even when the current target reports a gap. | +| Usernames may not be `admin` or `support` | `exclusion` of a fixed typed literal set; the current target declares it as an empty method, usually with a failing spec, until it is written in Rails. | | A rating is at least one, an end date follows a start date, or two selected people must differ | `comparison` with compatible values; use Entity ownership for a cross-value rule and select the input that should receive the error. | | A title and release date must be unique together | One Entity-owned `uniqueness` tuple, with an explicit participating Field or Reference as its error target and the intended null policy. | -Today `format` and not-equal comparisons drop [sample records](#prepare-data-for-the-first-preview). +A sample record that breaks one of these rules is left out of the seed with a warning; see +[sample records](#prepare-data-for-the-first-preview). Select a useful Field or Reference for Entity-owned feedback; for example, attach an invalid end-date comparison to the end-date input. Plan error targets do not include the whole record. A complete sentence does not require a @@ -321,8 +323,9 @@ comparison or uniqueness remains on the Entity when appropriate, independently o These examples explain kind selection, not a promise that every shape emits today. Read the compatible [Validation support reference](foundation-plan-023.md#validations), use the bundled schema for exact syntax, and -inspect the real analysis result. Schema-valid cross-field comparisons, conditions, and exclusions can still be -service or target gaps. No general Rails `validates` option or custom Ruby callback becomes Plan syntax merely +inspect the real analysis result. A schema-valid rule the target cannot lower, such as an exclusion or a conditional +Field-to-Field comparison, is declared as an empty method, with a failing spec where the Compiler can build a record +it rejects, so the app accepts what it forbids until the method is written. No general Rails `validates` option or custom Ruby callback becomes Plan syntax merely because Rails supports it. Operation-specific checks and bespoke rules outside the grammar belong in @@ -358,19 +361,22 @@ emits `has_many :members, -> { distinct }, through: :memberships, source: :user` them. The current Compiler emits a bounded single-target Reference slice with Boolean `required`, `one_to_one`, and -`immutable`, plus its derived forward traversal and supported direct inverses. The supported catalog also includes -selected required-immutable inverses and several direct, predicated, indirect, and nested-through consumers; these are -per-relationship shape rules, not a quota. A multi-target Reference compiles under either realization, but consumers -that cross it, aliases, defaults, broader paths, cardinality, and unsupported consumers can remain gaps. Scaffold +`immutable`, plus its derived forward traversal and direct inverses over mutable and immutable References. The +supported catalog also includes several direct, predicated, indirect, and nested-through consumers; these are +per-relationship shape rules, not a quota. A Reference `default` keeps its storage while the default waits to be +written in Rails. A multi-target Reference compiles under either realization, but consumers that cross it, aliases, +broader paths, cardinality, and unsupported consumers can remain gaps. Scaffold input support is a separate consumer decision from Reference storage. Preserve broader product meaning and inspect the matching GapSet rather than applying an older blanket relationship limit. When people should see how many related records something has, such as likes on a post or a person's followers, add a [`counter` Field](foundation-plan-023.md#counters) on the record being counted for. Point it at the direct Association that holds the counted records: `user.follower_links`, not the indirect `user.followers` through them. -Add that direct referenced-side Association when the Plan lacks it, author one counter per Reference, and keep an -indirect, filtered, or polymorphic count in the Plan as a reviewed gap. An explicit list or details projection shows -each counter after its authored items; people never type them. +Add that direct referenced-side Association when the Plan lacks it. Rails maintains a direct count, including one +over a polymorphic Reference whose targets share the column name or an exclusive arc. An indirect or filtered count +keeps its column while its maintenance waits to be written in Rails: an indirect count behind a failing example, a +filtered one as a Test coverage to complete entry. An explicit list or +details projection shows each counter after its authored items; people never type them. ## Add behavior deliberately @@ -385,8 +391,9 @@ each counter after its authored items; people never type them. record's consequence says what the generated app does. When it says the named scope raises `NotImplementedError` until it is written, the Compiler emitted that scope as a stub, with no database index for an Ordering, and the README's Behavior to write lists it; an index that selects it calls the stub, so the page raises until then. - Otherwise the scope is left out, as when the model cannot take that name, and the consequence says what the page - does without it. A Field followed by `id` also generates when both run in one + A key Rails reserves, such as `public`, becomes the key plus the table name, as `public_profiles`, with a warning. + Otherwise the scope is left out, as when even that name is taken, and the consequence says what the page does + without it. A Field followed by `id` also generates when both run in one direction: on Bulletin, `title`, then `id`, both ascending, emits `scope :by_title, -> { order(title: :asc, id: :asc) }` and `t.index ["title", "id"]`. The same terms with `id` descending are a `foundation_plan.gap.ordering.not_generated` record. @@ -479,8 +486,7 @@ diagnostic reveals an ambiguous product decision, ask the user rather than optim In particular, do not remove or weaken modeled content solely because the reviewed GapSet reports a `service_support_gap` or `target_support_gap`. Preserve the local Plan and report the exact pointer and consequence. -GapSet records are the output limitations; report an analysis warning as a warning. A `public_scaffold` warning on -a Policy-protected page is not a reason to make it public. Report a gap as the analysis gives it, even when its +GapSet records are the output limitations; report an analysis warning as a warning. Report a gap as the analysis gives it, even when its reason names no cause or contradicts these references. Do not trim the Plan or push trial or example Plans to find a cause: each push replaces the Project's current Plan. @@ -513,7 +519,7 @@ Write it for a reader who is not technical, in this order: after signing up or confirm their email first, as the [Accounts reference](foundation-plan-023.md#accounts-and-policies) describes. 2. What is not generated yet. Group the gaps by what the user will notice, lead with that effect, and say what - the app does instead, for example "Formatted descriptions aren't generated yet; posts start without one" or + the app does instead, for example "Descriptions are stored as plain text; formatting isn't generated yet" or "The rule that usernames can't be admin isn't generated yet; any username is accepted". Use one line per group rather than one per record, and say which groups you plan to build in Rails after Compile. 3. What is built otherwise: outstanding implementation notes and any part of the idea outside First Draft, each