Crucible State Machine — Evolution Guide #6
joshua-temple
started this conversation in
State Machine
Replies: 0 comments
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
-
Treat the machine as a schema
A machine definition is a schema. The invariants that govern database-schema and shared-message changes apply here: silent renames break downstream consumers, removals break replay, and additive changes are safe as long as defaults hold.
The crucial difference from ordinary Go refactoring: violations surface as
ErrInvalidTransitionat runtime, not at compile time, because machines are bound to persisted entity state. An entity persisted under an old state name cannot be loaded once that name is gone.Treat any machine change the way you would treat a migration of a shared message contract or a database table.
Additive-only evolution / reserved drop-in surface
This is the design tenet that makes the kernel safe to grow: every deferred capability has its IR field and API hook reserved in v1, so landing it later is purely additive — never a breaking change. It is the forward-looking counterpart to the schema discipline below. Schema discipline governs how your machine changes; the reserved drop-in surface governs how the kernel grows under you.
The contract has two halves, and a kernel feature only counts as "reserved" if it satisfies both:
historyType: "none",invoke: [], an unused effect contract), so older JSON and newer JSON share one schema. No schema-version bump when the feature lands.historypseudo-state kind (historyTypeenum)invokeblock (named-ref + params) on statesinvokenames against a services registryFirebecomes a message sendafterscheduler (runtime)ScheduleAftereffect contract (theafterrepresentation already ships)Trace.MicrostepsBecause these fields already serialize as inert defaults, a machine authored today round-trips unchanged through a kernel that later activates them — the same lossless-round-trip guarantee, projected forward in time. A reserved field that ever needs to be renamed or re-typed to land its feature was not properly reserved; that is the review bar for any "reserved" claim.
Allowed changes (safe without a deprecation window)
Firecalls are unaffected.Fire; safe if every entity currently in that state satisfies the new guard.OwnedBymetadataWaitModeon a transitionCaveat on guard additions: adding a guard that some currently-in-that-state entities do not satisfy is a breaking change disguised as an addition. Audit your data before shipping it.
Disallowed changes (require migration)
ErrInvalidTransitionon everyFire.ErrNoPath.PlanPathcallers; migrate affected entities.Versioning & deprecation pattern
Add freely; never rename or repurpose without a Deprecated -> Removed lifecycle.
stateDiagram-v2 [*] --> Active : Initial declaration Active --> Active : Add states (additive) Active --> Active : Add transitions Active --> Deprecated : Deprecated marker added Deprecated --> Deprecated : Migrate callers (multiple PRs) Deprecated --> Removed : All callers migrated; item deleted Removed --> [*] Deprecated --> Active : Revert (rare)// Deprecated:comment and/or aDeprecated()DSL marker).The deprecation window is the gap between steps 1 and 5; keep it at least one full release cycle.
Worked example — renaming a state
Suppose
Assignedis renamed toAcceptedin the UX:Do not combine the two changes. The split is the gap that allows zero-downtime migration without coordinating every consumer that reads entity state.
Semver guidance
The machine definition has no independent semantic version. Instead:
machine.jsonartifact is the versioned snapshot.machine.jsondiff — reviewers treat that diff as a schema-migration review.Hot-reload is versioning, not mutation.
Quenchfreezes a definition into an immutable*Machine; you never mutate a live one. To change behavior at runtime you roll a new version of the machine (a new*Machinefrom new IR) and route new instances to it while in-flight instances drain on the old one. This is why hot-reload is a non-goal at the kernel level — versioned, frozen machines give you the same capability with none of the live-mutation hazards.CI enforcement
machine.jsonis committed and regenerated viago generate. Any change that modifies the machine definition must produce amachine.jsondiff — CI fails if the committed file is stale. Reviewers get a machine-readable, human-readable diff of structural graph changes alongside the Go code change.Linking from machine READMEs
Each machine package should link to this guide from its
README.mdso engineers planning a change have the discipline in front of them before they open a PR.Crucible State Machine series: Overview & Roadmap · Kernel Core · HSM · Path Planning · JSON / Mermaid / DOT · Evolution Guide · Conformance · Phase 2
Beta Was this translation helpful? Give feedback.
All reactions