refactor(spec/005): polish cluster — 4 follow-on beads#1028
Merged
Conversation
added 4 commits
May 14, 2026 15:17
Spec 005 §Snapshot shape documents the user-facing :state / :data / :tags
/ :meta contract. The runtime additionally stamps a closed set of :rf/*
slots inside the snapshot (some at the snapshot root, some inside :data,
one inside :meta) for per-machine bookkeeping. Conventions has had this
catalogue since rf2-c1l4d; 005 only cross-referenced it.
Promote 005 to authoritative source — the spec owns the snapshot shape
contract per Ownership, so the catalogue belongs here. New subsection
§Reserved snapshot-internal keys lists each slot with its location
(snapshot root vs :data vs :meta), value shape, and read/write rules:
Snapshot root · :rf/spawn-counter · :rf/bootstrap-pending? · :rf/finished?
Inside :data · :rf/after-epoch · :rf/after-epoch-by-region
· :rf/self-id · :rf/parent-id · :rf/invoke-id
· :rf/invoke-all-id · :rf/invoke-all-child-id
Inside :meta · :rf/snapshot-version
Plus a sibling-vocabulary list for the runtime-stamped machine-spec keys
(:rf/frame, :rf/platform, :rf/parent-id, :rf/region, :rf/transition-pure)
which are NOT snapshot-internal — reconstructed at handler-call time, do
not persist. Persistence posture rolls up the transient vs persisting
split (:rf/bootstrap-pending? and :rf/finished? are transient; the rest
ride pr-str / read-string and survive SSR + Tool-Pair epoch replay).
The Conventions catalogue is left intact and now cross-references back
to 005 for the canonical definition.
Refs rf2-33y0y. Round-1 audit S6; round-2 audit confirms (rf2-2ukxv).
The spawn surface is composite — :on-spawn, :rf.machine/spawn, the synthetic [:rf.machine/spawned], :start, and the spawned actor's initial-:entry cascade all participate. Each piece is spec'd in its own subsection (rf2-t07u for the spawn-registry, rf2-ijm7 for the synthetic event and the runtime-stamped :data keys, rf2-6vmw for :invoke-all, rf2-0z73 for initial-entry firing on bootstrap), but the strict ordering between them was scattered. Round-2 audit (rf2-2ukxv) flagged the gap. Add §Spawn lifecycle — ordering under §Spawning, enumerating the 8 steps from "parent enters :invoke-bearing state" through "child processes its first event": Parent's drain Child's drain 1 enter state 5 spawn fx → snapshot + handler 2 :on-spawn (data) 6 first event (:start or synthetic) 3 :fx accumulates 7 initial-:entry cascade 4 commit 8 first :on resolution Distinguishes the two "spawn" surfaces (the advisory :on-spawn callback that runs against the parent's :data inside the transition reducer, vs the synthetic :rf.machine/spawned event that reaches the child as its first event) — easy to conflate from skim-reads, the source of multiple round-2 audit confusions. Carries a worked walkthrough (login flow invoking auth-flow) and a trace table mapping each step to its externally-observable trace event, plus a §Why this matters note covering the two consequences that fall out of the ordering (initial-:entry can read :rf/parent-id; :start vs initial- :entry as the canonical kick-off shape). Refs rf2-k0qb3. Round-2 audit S-r2-7; parent rf2-2ukxv.
The parallel-regions / initial-entry machinery synthesises a placeholder event vector when running the bootstrap entry cascade — there is no user-dispatched event to thread, but action fns receive a 2-arity (data, event) (or 3-arity (data, event, ctx) for ^:rf.machine/wants-ctx slots), so the event slot must be non-nil. The implementation has been using [:rf.machine/bootstrap] for this since parallel.cljc:178 and lifecycle_fx/registration.cljc:267, but the contract was undocumented — authors writing introspection-capable :entry actions (the 3-arity opt-in surface per §Dispatch rule — metadata opt-in) had no way to know what vector to expect. Round-2 audit (rf2-2ukxv · S-r2-6) flagged the gap. Adds a new H5 subsection under §Initial-state :entry fires on machine bootstrap noting that the bootstrap event vector is [:rf.machine/bootstrap], shows an introspection-capable :entry action reading the event head to decide what to do (distinguishing bootstrap from any user-dispatched entry), and pins the reserved-namespace rule: user code MUST NOT register a handler for or dispatch [:rf.machine/bootstrap] directly — it's purely synthetic, never reaches a handler's :on map, exists solely so the :entry action's event argument is non-nil. Refs rf2-pexjc. Round-2 audit S-r2-6; parent rf2-2ukxv.
Round-1 audit S3 / S4 / S5 — three under-specified surfaces — round-2
(rf2-2ukxv) carries over.
S3 · :rf.machine/spawned — the runtime emits this synthetic event when
:rf.machine/spawn args omit :start (per the existing §Synthetic on spawn
subsection). Conventions has no parallel §Reserved events catalogue, so
the name appears in spec only as narrative inside the existing rf2-ijm7
subsection — not as a closed-set entry.
S4 · :rf.machine.timer/after-elapsed — the wire format between
schedule-after-timer! and pick-after-transition (per §Epoch-based stale
detection). Canonical in the implementation (machines/timer.cljc and
parallel.cljc) but the spec described it as "implementation-internal" —
both the JVM pure-fn tests and the http/managed conformance corpus
dispatch this vector directly, so the carrier shape is contractual.
S5 · pure-call no-op shim — intercept-invoke-all-event (join.cljc:128–
130) returns {:db db :fx []} when no :rf/spawned slot is seeded, so
pure-fn test corpora driving an :invoke-all machine via machine-
transition don't error. Invisible behaviour the spec didn't promise.
Add a new §Reserved synthetic events subsection under §Snapshot shape
that catalogues all three synthetic vectors with source / target / wire
shape. The bootstrap entry from rf2-pexjc is included for completeness.
Carrier rows pin the closed-set rule: user code MUST NOT register
handlers or hand-dispatch these names. Conformance harnesses that
dispatch them are emulating the runtime, not registering new handlers.
Add a sibling §Pure-call no-op shim subsection describing the contract
on intercept-invoke-all-event under the pure-call path — the :rf/
transition-pure sentinel keys it (cross-ref to the snapshot-internal
catalogue's sibling-vocabulary list).
Refs rf2-cl7oi. Round-1 S3/S4/S5 (rf2-ra1he); parent rf2-2ukxv.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Spec-only polish cluster on
spec/005-StateMachines.md— the fourfollow-on beads from the round-2 machines audit (rf2-2ukxv). Sequenced
content-add-only to avoid intra-PR rebase pain; no doc-gate regressions.
Beads (in commit order):
:rf/*keyscatalogue. Promotes 005 to authoritative source for the closed-set
catalogue of runtime-stamped slots inside a machine snapshot (the
user-facing
{:state :data :tags? :meta?}shape is what 005 owns perOwnership.md; Conventions has carried a mirror table since rf2-c1l4dand now cross-references back).
subsection. Enumerates the 8-step ordering from "parent enters
:invoke-bearing state" through "child processes its first event" —the two surfaces (
:on-spawndata-update inside the transitionreducer, vs
[:rf.machine/spawned]synthetic event reaching thechild) are easy to conflate, the source of multiple round-2 audit
confusions.
[:rf.machine/bootstrap]synthetic event.Documents the placeholder event vector the runtime threads through
the bootstrap entry cascade (impl has used it since
parallel.cljc/
lifecycle_fx/registration.cljcbut the contract was undocumented— authors writing
^:rf.machine/wants-ctxintrospection:entryactions had no way to know what vector to expect).
no-op shim. New §Reserved synthetic events table catalogues
[:rf.machine/spawned],[:rf.machine/bootstrap], and the[:rf.machine.timer/after-elapsed <delay-key> <epoch>]timercarrier (canonical in impl, previously labelled
"implementation-internal" in spec). New §Pure-call no-op shim
subsection pins the
intercept-invoke-all-eventno-op contractunder the
:rf/transition-puresentinel path.LoC delta: +153 / -0.
Test plan
python scripts/check_doc_slugs.pyexits 0python -m mkdocs buildsucceeds; warning count unchangedfrom
origin/main(119, all pre-existing guide/ + skills/links to
spec/which mkdocs doesn't include)origin/main(no overlappingcommits touched 005)