Skip to content

refactor(#730): restate the checkpoint submitter as an xstate statechart - #743

Merged
zgeoff merged 8 commits into
mainfrom
730-submitter-statechart
Jul 21, 2026
Merged

refactor(#730): restate the checkpoint submitter as an xstate statechart#743
zgeoff merged 8 commits into
mainfrom
730-submitter-statechart

Conversation

@zgeoff

@zgeoff zgeoff commented Jul 21, 2026

Copy link
Copy Markdown
Owner

Description

Closes #730

Replace the checkpoint submitter's per-activity mutable flag records, hand-scheduled timers, and p-retry loop with declared XState v5 machines — a parent spawning one child actor per activity — behind the unchanged CheckpointSubmitter interface, so the existing behavioral suite passes unmodified.

  • The flush attempt is one invoked actor internalizing the oRPC call, queue settlement, and callbacks, resolving a discriminated outcome the transitions route on.
  • scheduled waits on an external flush-due event, keeping the injected scheduleFlush test closures awaitable and superseded-safe.
  • The CHECKPOINT_INVALID tombstone is a retained ordinary state; the parent's evictedActivityIDs answers isEvicted after a child stops.
  • buildMachineTypes<T>() owns the one phantom-type assertion setup() requires.
  • Accepted edge: flushNow during an in-flight registration seed is now a no-op (previously flushed the partially-seeded cursor).

Testing

  • bun run typecheck passes
  • bun run test passes
  • bun run lint passes
  • New tests added for new functionality

zgeoff added 2 commits July 21, 2026 22:34
Replace the per-activity mutable flag records, hand-scheduled flush
timers, and p-retry backoff loop with declared xstate v5 machines: a
parent spawning one child actor per activity, a child owning the
idle/scheduled/flushing/retrying/invalid/evicted states, and a thin
adapter preserving the exact CheckpointSubmitter interface so the
existing behavioral suite runs unmodified.

- the flush attempt is one invoked fromPromise internalizing the oRPC
  call, queue/cursor settlement, and callbacks, resolving a
  discriminated outcome the transitions route on
- backoff parity with the p-retry config (factor 2, 10s..300s, no
  jitter, first retry waits a full window); retryTimings parameterizes
  the delay function via machine input
- scheduled waits on an external flush-due event so the injected
  scheduleFlush test closures keep their awaitable, superseded-safe
  semantics
- the CHECKPOINT_INVALID tombstone is a retained ordinary state; the
  parent's evictedActivityIDs answers isEvicted after a child stops
- buildMachineTypes<T>() owns the single phantom-type assertion xstate's
  setup() requires, keeping machine definitions cast-free
- allow xstate's AnyActorRef in the readonly-parameter-types allow list
  (live actor handle, no readonly form)

Known accepted edge: flushNow during an in-flight registration seed is
a no-op where it previously flushed on the partially-seeded cursor.

Closes #730

Claude-Session: https://claude.ai/code/session_01CmZyk7wqzP8KtwQBVZg4ha
- reset the backoff attempt counter on success and conflict outcomes so
  a later outage starts at the base window, with a regression test
- carry the settled appended head into the callback-failed outcome so a
  throwing ack callback no longer strands a stale expected head
- fire onHeld on the fold-in branches that re-flush without passing
  through retrying, matching the per-held-attempt cadence
- rename listenForShutdownAbort/makeScheduleProgressFlush/findChild to
  taxonomy verbs; reword a comment that named other declarations

Claude-Session: https://claude.ai/code/session_01CmZyk7wqzP8KtwQBVZg4ha
@socket-security

socket-security Bot commented Jul 21, 2026

Copy link
Copy Markdown

Review the following changes in direct dependencies. Learn more about Socket for GitHub.

Diff Package Supply Chain
Security
Vulnerability Quality Maintenance License
Addedxstate@​5.32.410010010098100

View full report

@coderabbitai

coderabbitai Bot commented Jul 21, 2026

Copy link
Copy Markdown

Review Change Stack

Important

Review skipped

Auto incremental reviews are disabled on this repository.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 7607f615-1431-418c-9c83-21f3a570792a

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

The checkpoint submitter is refactored from local imperative state into XState parent and per-activity child machines. The adapter forwards queue, flush, retry, terminal, abort, and eviction events, with comprehensive tests covering state transitions and failure handling.

Changes

Checkpoint submission statecharts

Layer / File(s) Summary
Activity flush state machine
libs/game/idle-client/src/submission/checkpoint-activity-machine.ts, libs/game/idle-client/src/submission/build-machine-types.ts, package.json, libs/game/idle-client/package.json, .oxlintrc.json
Adds the XState activity machine, typed inputs and outcomes, checkpoint delivery, retry backoff, abort handling, terminal states, and required dependency/configuration support.
Parent actor and child eviction lifecycle
libs/game/idle-client/src/submission/checkpoint-submitter-machine.ts
Adds the parent machine that registers per-activity children, handles settlement, stops children, and tracks evicted activity IDs.
Submitter adapter integration
libs/game/idle-client/src/submission/create-checkpoint-submitter.ts
Rewires registration, durable cursors, queueing, flushing, terminal submission, held-batch draining, and eviction helpers through the XState actors.
State machine behavior validation
libs/game/idle-client/src/submission/checkpoint-activity-machine.test.ts
Tests scheduling, terminal draining, retries, flush overrides, abort shutdown, invalid checkpoints, callback failures, and retry counter resets.

Estimated code review effort: 4 (Complex) | ~60 minutes

Possibly related PRs

  • zgeoff/vers#613: Updates the checkpoint progress mock behavior used by the new activity machine tests.
  • zgeoff/vers#643: Also changes immediate-flush behavior in the checkpoint submission path.
  • zgeoff/vers#655: Overlaps with eviction and tombstone handling in the checkpoint submitter.
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Linked Issues check ✅ Passed The submitter was converted to XState with parent/child actors, declared states, guarded transitions, and preserved interface; resync was explicitly out of scope.
Out of Scope Changes check ✅ Passed The diff stays focused on the checkpoint submitter refactor and supporting lint/dependency updates; no unrelated features are introduced.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Title check ✅ Passed The title clearly summarizes the main change: refactoring the checkpoint submitter into an XState statechart.
Description check ✅ Passed The description is directly aligned with the changeset and explains the XState refactor, behavior, and testing.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch 730-submitter-statechart

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@libs/game/idle-client/src/submission/checkpoint-activity-machine.ts`:
- Around line 273-281: Update emitSettlementToParent to avoid accessing the
internal args.self._parent handle; use XState’s supported sendParent mechanism
or pass an explicit parentRef and send through sendTo, while preserving the
existing CHILD_SETTLED payload and settlement behavior.

In `@libs/game/idle-client/src/submission/create-checkpoint-submitter.ts`:
- Around line 164-169: Update the WriteCursor interface to add one inline
readonly-exception directive explaining that nextVersion, prevHash, and
previousNextSeed are intentionally mutated in submit; keep startChainIndex
readonly and avoid adding separate directives for each field.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: ccb20374-3554-4f27-a28d-bd5fd92d7c38

📥 Commits

Reviewing files that changed from the base of the PR and between b28eb2a and e1a944d.

⛔ Files ignored due to path filters (1)
  • bun.lock is excluded by !**/*.lock, !bun.lock
📒 Files selected for processing (8)
  • .oxlintrc.json
  • libs/game/idle-client/package.json
  • libs/game/idle-client/src/submission/build-machine-types.ts
  • libs/game/idle-client/src/submission/checkpoint-activity-machine.test.ts
  • libs/game/idle-client/src/submission/checkpoint-activity-machine.ts
  • libs/game/idle-client/src/submission/checkpoint-submitter-machine.ts
  • libs/game/idle-client/src/submission/create-checkpoint-submitter.ts
  • package.json

Comment thread libs/game/idle-client/src/submission/checkpoint-activity-machine.ts Outdated
Comment thread libs/game/idle-client/src/submission/create-checkpoint-submitter.ts
zgeoff added 6 commits July 21, 2026 22:53
Replace the child's private _parent access with the documented pattern:
the submitter machine hands its own ref to each spawned child via input,
and settlement is sent to that ref — a machine started without one
reports settlement nowhere. Also note WriteCursor's deliberate in-place
chain-field mutation in its doc.

Claude-Session: https://claude.ai/code/session_01CmZyk7wqzP8KtwQBVZg4ha
Every property of xstate's SetupTypes is optional, so an empty object is
assignable to Partial<T> with no cast and inference through the optional
properties still names the machine shapes exactly.
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.

restate the checkpoint submitter and resync flow as statecharts

1 participant