Skip to content

refactor(spec/005): polish cluster — 4 follow-on beads#1028

Merged
mike-thompson-day8 merged 4 commits into
mainfrom
refactor/rf2-005-spec-batch
May 14, 2026
Merged

refactor(spec/005): polish cluster — 4 follow-on beads#1028
mike-thompson-day8 merged 4 commits into
mainfrom
refactor/rf2-005-spec-batch

Conversation

@mike-thompson-day8
Copy link
Copy Markdown
Contributor

Summary

Spec-only polish cluster on spec/005-StateMachines.md — the four
follow-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):

  • rf2-33y0y · spec(005): Reserved snapshot-internal :rf/* keys
    catalogue.
    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 per
    Ownership.md; Conventions has carried a mirror table since rf2-c1l4d
    and now cross-references back).
  • rf2-k0qb3 · spec(005): unified spawn lifecycle ordering
    subsection.
    Enumerates the 8-step ordering from "parent enters
    :invoke-bearing state" through "child processes its first event" —
    the two surfaces (:on-spawn data-update inside the transition
    reducer, vs [:rf.machine/spawned] synthetic event reaching the
    child) are easy to conflate, the source of multiple round-2 audit
    confusions.
  • rf2-pexjc · spec(005): [: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.cljc but the contract was undocumented
    — authors writing ^:rf.machine/wants-ctx introspection :entry
    actions had no way to know what vector to expect).
  • rf2-cl7oi · spec(005): synthetic events catalogue + pure-call
    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>] timer
    carrier (canonical in impl, previously labelled
    "implementation-internal" in spec). New §Pure-call no-op shim
    subsection pins the intercept-invoke-all-event no-op contract
    under the :rf/transition-pure sentinel path.

LoC delta: +153 / -0.

Test plan

  • python scripts/check_doc_slugs.py exits 0
  • python -m mkdocs build succeeds; warning count unchanged
    from origin/main (119, all pre-existing guide/ + skills/
    links to spec/ which mkdocs doesn't include)
  • Rebased clean onto current origin/main (no overlapping
    commits touched 005)

Mike Thompson 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.
@mike-thompson-day8 mike-thompson-day8 merged commit ac3bcdc into main May 14, 2026
35 checks passed
@mike-thompson-day8 mike-thompson-day8 deleted the refactor/rf2-005-spec-batch branch May 14, 2026 05:40
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant