From cbdf4f21d1ddcfe60cd077d86731bfb61dc90d06 Mon Sep 17 00:00:00 2001 From: Raghu Betina Date: Mon, 12 Oct 2026 07:55:40 -0500 Subject: [PATCH] Say that ungenerated State Machine events are stubs The service's next Compiler keeps a required, unconditional State Machine alone on its Entity in AASM when an event has effects it cannot generate. Before, one such event made the whole machine storage only, with no events, AASM initialization, or assignment guard, even for the events it could generate. Now the aasm block lists the generated events, and each other event is emitted as its bang form, plain form, and may_ predicate, under the names AASM would give it, each raising NotImplementedError until it is written. The README's Behavior to write lists each one, its State Machine examples fail through the stub, and seeds and spec setup never fire one. The generated AGENTS.md now says a stubbed scope or event raises. The Skill's rule for failing specs named only a stubbed scope, so the test run and continuation now say a stubbed scope or event raises, as the app's AGENTS.md does. The Plan reference said cross-Entity, multi-effect, and broader effect shapes kept only storage; it now says which events are generated, describes the stub, the left-out seed record, and the GapSet record, and keeps storage only for optional, conditional, multiple-per-Entity, guarded, and colliding machines. Until the service deploys that Compiler, no README lists an event under Behavior to write, so the rule holds on both sides of the deploy. Plugin 0.8.13 is published and selected, so this moves to 0.8.14 with a CHANGELOG entry. Packing main (84bb47f) with CLI 92f27714 on Node 24.18.0 reproduced its digest before the repack. npm ci --ignore-scripts, npm audit, sh script/check with the stub CLI and with that CLI, and check-cli-contract pass. --- CHANGELOG.md | 28 ++++++++++++++++++ package-lock.json | 4 +-- package.json | 2 +- .../claude-plugin/.claude-plugin/plugin.json | 2 +- packages/claude-plugin/package.template.json | 2 +- release/compatibility.json | 4 +-- skills/create-full-stack-app/SKILL.md | 2 +- .../references/diagnostics-and-recovery.md | 29 ++++++++++--------- .../references/foundation-plan-023.md | 25 +++++++++++----- 9 files changed, 68 insertions(+), 30 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a0788b9..bb05dba 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -29,6 +29,34 @@ 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: State Machine events First Draft cannot generate are stubs + +**Skills — released in plugin 0.8.14.** Once the First Draft service deploys the matching Compiler, a required, +unconditional State Machine alone on its Entity stays in AASM when an event has effects the Compiler cannot generate. +Before, one such event made the whole machine storage only: a string column with no events, AASM initialization, or +direct-assignment guard, even for the events the Compiler could generate. Now the `aasm` block lists the generated +events, and each other event is emitted as three methods, `!`, ``, and `may_?`, under the names AASM would +give it, each raising `NotImplementedError` until it is written. An event is generated when it is effect-free or sets +one optional, mutable, non-derived `datetime` Field on the same record to the current time or `nil`. An event with +two effects, or one that sets a Field on another Entity, a required Field, or a `date` Field, is a stub. A Ticket +`status` machine whose `assign` event sets `assigned_at` to the current time, and whose `resolve` event sets +`desk.last_resolved_at`, emits `event :assign` in the `aasm :status` block and `Ticket#resolve!`, `#resolve`, and +`#may_resolve?` stubs. The README's Behavior to write lists each stubbed event with its states, effects, and Plan +pointer, and says to add the event to the `aasm` block and delete its three stubs. The State Machine spec's allowed +and rejected examples for that event fail through the stub until then. No generated page fires an event, so no page +raises. Seeds and spec setup never fire a stub: a development record whose state needs one, such as a `resolved` +Ticket, is left out of `db/seeds/development.rb`, and an example whose setup needs one is listed under Test coverage +to complete. The GapSet keeps each stubbed event's `foundation_plan.gap.state_transition.effects_not_generated` +record, whose consequence now names the stubs, and drops the machine's +`foundation_plan.gap.state_machine.partially_generated` record and the generated events' records. Optional, +conditional, and multiple-per-Entity machines still keep only storage. + +The Skill's rule for a failing spec now says a stubbed scope or event raises `NotImplementedError`, as the generated +`AGENTS.md` does. The Plan reference describes the event stub instead of a storage-only machine. + +Until the service deploys that Compiler, such a machine is still storage only, as in plugin 0.8.13, and no README +lists an event under Behavior to write. + ## 2026-10-12: Policies First Draft cannot lower are stubs **Skills — released in plugin 0.8.13.** Once the First Draft service deploys the matching Compiler, a Policy whose diff --git a/package-lock.json b/package-lock.json index 5fa7864..d7aeecd 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@firstdraft/skills", - "version": "0.8.13", + "version": "0.8.14", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@firstdraft/skills", - "version": "0.8.13", + "version": "0.8.14", "license": "MIT", "devDependencies": { "ajv": "8.20.0" diff --git a/package.json b/package.json index 1fd9ade..ca2bf0e 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@firstdraft/skills", - "version": "0.8.13", + "version": "0.8.14", "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 952bed7..a34cc9f 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.13", + "version": "0.8.14", "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 ca26bc2..5def2e5 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.13", + "version": "0.8.14", "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 2781297..64b58be 100644 --- a/release/compatibility.json +++ b/release/compatibility.json @@ -1,10 +1,10 @@ { "format": "firstdraft.release-compatibility/1", "component": "skills", - "version": "0.8.13", + "version": "0.8.14", "plugin_source": { "package": "@firstdraft.com/claude-code", - "tarball_sha256": "1b57b73ab21d0bb5b51e41d650ce3761994a4412998e2924406643dbb4e1a6cb" + "tarball_sha256": "9a00700f9c83c1fc919dac8c7f209a5904754b5910f9a4eeb9d086ca20920964" }, "requires": { "api_contract": [ diff --git a/skills/create-full-stack-app/SKILL.md b/skills/create-full-stack-app/SKILL.md index a078375..6398aef 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.13, CLI 0.8.2, API 0.7, Plan 0.23; catalog selection is separate. +Targets plugin 0.8.14, 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 512583c..89d7aea 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.13/script/cli-contract/config.mjs) +[the CLI contract configuration](https://github.com/firstdraft/skills/blob/claude-v0.8.14/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. @@ -317,12 +317,12 @@ First Draft defect: report it and ask before installing anything. With the prere is a defect. A spec that fails through behavior the README's Behavior to write lists is work to do: First Draft did not write -that behavior. Write it until its specs pass. A stubbed scope raises `NotImplementedError`, a stubbed Policy rule -returns `false`, and a deletion outcome First Draft left out fails its spec with `RecordNotDestroyed`. Write every -item that section lists, including a denying rule no failing spec names. Any other failure is a First Draft defect. -Report RSpec's summary line and each such failure's rerun line (`rspec ./spec/...:LINE`) and error; the user decides -how to proceed. Never edit or delete a generated spec, skip examples, or change the commands to make the run pass. -Change a generated spec only when the owner changes the behavior it pins. +that behavior. Write it until its specs pass. A stubbed scope or event raises `NotImplementedError`, a stubbed Policy +rule returns `false`, and a deletion outcome First Draft left out fails its spec with `RecordNotDestroyed`. Write +every item that section lists, including a denying rule no failing spec names. Any other failure is a First Draft +defect. Report RSpec's summary line and each such failure's rerun line (`rspec ./spec/...:LINE`) and error; the user +decides how to proceed. Never edit or delete a generated spec, skip examples, or change the commands to make the run +pass. Change a generated spec only when the owner changes the behavior it pins. When the run passes and every item Behavior to write lists is written, continue to the preview. Otherwise ask before the preview, even one requested earlier. If the user still wants it, first name the pages or flows the failing specs @@ -384,13 +384,14 @@ server Publication carried the notes. During continuation, read the app's `AGENTS.md`, README, and retained implementation notes before selecting work. Run the specs before other work. A spec that fails through behavior the README's Behavior to write lists is work to -do: write that behavior until its specs pass. A stubbed scope raises `NotImplementedError`, a stubbed Policy rule -returns `false`, and a deletion outcome First Draft left out fails its spec with `RecordNotDestroyed`. Write every -item that section lists, including a denying rule no failing spec names. Report any other failure as a First Draft -defect. Never edit or delete a generated spec to make it pass; change one only when the owner changes the behavior -it pins. Then implement the agreed behavior and its acceptance examples in ordinary source; keep unresolved product -questions open. Existing app feature work does not require another Plan submission, Compile, or CLI capability -probe. Update the notes to reflect remaining behavior without making app setup, runtime, or tests depend on them. +do: write that behavior until its specs pass. A stubbed scope or event raises `NotImplementedError`, a stubbed Policy +rule returns `false`, and a deletion outcome First Draft left out fails its spec with `RecordNotDestroyed`. Write +every item that section lists, including a denying rule no failing spec names. Report any other failure as a First +Draft defect. Never edit or delete a generated spec to make it pass; change one only when the owner changes the +behavior it pins. Then implement the agreed behavior and its acceptance examples in ordinary source; keep unresolved +product questions open. Existing app feature work does not require another Plan submission, Compile, or CLI +capability probe. Update the notes to reflect remaining behavior without making app setup, runtime, or tests depend +on them. #### UI continuation 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 ce2bd2a..93a5aee 100644 --- a/skills/create-full-stack-app/references/foundation-plan-023.md +++ b/skills/create-full-stack-app/references/foundation-plan-023.md @@ -88,7 +88,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.13/script/cli-contract/config.mjs) +[the CLI contract configuration](https://github.com/firstdraft/skills/blob/claude-v0.8.14/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`, @@ -381,14 +381,23 @@ or otherwise unsupported Association descriptors remain exact gaps. A Field may 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, with effect-free transitions or one bounded local -datetime `set_field` effect per transition. AASM owns initialization, validation, named events, state scopes, and the -admitted effects; behavior-emitted state columns keep `NOT NULL` without a SQL default. Native naming options -resolve supported helper collisions. Required storage-only states retain the initial-state SQL default and +State Machine per Entity when the AASM helper surface is safe. 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 +time or `nil`. Any other event, such as one with two effects or one that sets a Field on another Entity, a required +Field, or a `date` Field, is emitted as a stub: its bang form, plain form, and `may_` predicate keep the names AASM +would give it and raise `NotImplementedError` until it is written. The machine keeps its AASM states, initialization, +validation, and direct-assignment guard even when every event is a stub. The README's Behavior to write lists each +stubbed event, and its State Machine examples fail until then; no generated page fires an event. Development seeds +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, guarded, cross-Entity, multi-effect, unresolved helper collisions, and broader effect shapes -retain storage 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. +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 +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 state; no generated workaround or namespace-based scope suppression is supplied. With `no_direct_assignment: true`,