Decouple application from consensus core via pluggable StateMachine#2
Merged
Merged
Conversation
Turn the consensus layer into a reusable framework (embedded-library use) by making it generic over an application-supplied StateMachine<C, T>, instead of hard-wiring the book domain into the core. - consensus/stateMachine.ts now defines the StateMachine<C, T> interface (apply/snapshot/restore/size) an application implements. - The log carries a flat command union Command<C> = NOOP | CONFIG | C, so domain commands ride the log directly (wire/persistence unchanged) and node.submit(domainCommand) stays ergonomic; NOOP/CONFIG are reserved. - RaftNode<C, T, SM> and ReplicatedStateMachine<C, T> are generic; audit hash-chain, idempotency, snapshotting and observability stay in the substrate and now apply to any state machine. node.app exposes the concrete application; node.stateMachine exposes the substrate wrapper. - Books move to the example layer (models/book.ts, models/bookStateMachine.ts); audit/raft routes are generic, book routes/app.ts are the example adapter. - Add src/index.ts public library surface; rename books_total metric to state_machine_entries and status.books to stateSize. - ADR-0017 records the decision; README/CLAUDE updated, incl. a "bring your own state machine" example. Type-check clean; all 42 tests pass. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0138afSrtkbtH5iDzFWdpv6s
naveed949
marked this pull request as ready for review
June 21, 2026 16:57
naveed949
added a commit
that referenced
this pull request
Jun 21, 2026
…e StateMachine seam (#3) Adds ADR-0018–0021 and a working, reviewed module-runtime prototype (M1–M10) that plugs into the pluggable StateMachine seam (PR #2): module SDK + deterministic runtime, committed-intent effects/outbox, Merkle audit + code-version hash, real Raft wiring via ModuleStateMachine, determinism lint + vm sandbox + resource/step bounds, CQRS read projections, ed25519 signed commands, a pluggable key-oriented state store, and multi-Raft sharding with cross-shard sagas. Consensus core untouched; 160 tests pass; tsc clean.
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.
What & why
First step toward using this project as a framework in real backend systems (embedded-library consumption): make the Raft consensus core domain-agnostic so any application can plug in its own state machine, instead of the book domain being hard-wired into the core.
Decisions confirmed with the owner: consume as an embedded library, and the first phase is the pluggable state machine (decoupling) — recorded in ADR-0017.
The change
consensus/stateMachine.tsnow defines the genericStateMachine<C, T>interface (apply/snapshot/restore/ optionalsize) that an application implements.Command<C> = NOOP | CONFIG | C, so domain commands ride the log directly — the wire/persistence format is unchanged andnode.submit(domainCommand)stays ergonomic.NOOPandCONFIGare reserved type discriminators.RaftNode<C, T, SM>andReplicatedStateMachine<C, T>are generic. The substrate concerns — audit hash-chain, idempotency, snapshotting, observability — stay in the substrate and now work for any state machine.node.app→ the concrete application state machine (domain reads).node.stateMachine→ the substrate wrapper (audit, size, dedup).models/book.ts,models/bookStateMachine.ts). Audit and raft HTTP routes are generic; book routes /app.tsare the example's adapter.src/index.tspublic library surface. Renamed thebooks_totalmetric →state_machine_entriesand node statusbooks→stateSize.stateMachine: SM & StateMachine<C, T>), soC/Tare inferred at the construction site without explicit type args.Bring-your-own-state-machine
Verification
npx tsc --noEmitclean.Not in this PR (follow-ups)
Developer-ergonomics polish (builder DSL, richer public API) and production-hardening (joint-consensus membership, snapshot chunking, follower read leases, pluggable adapters) are deliberately deferred to later phases.
🤖 Generated with Claude Code
Generated by Claude Code