Skip to content

Decouple application from consensus core via pluggable StateMachine#2

Merged
naveed949 merged 1 commit into
mainfrom
claude/codebase-review-philosophy-uo8vjw
Jun 21, 2026
Merged

Decouple application from consensus core via pluggable StateMachine#2
naveed949 merged 1 commit into
mainfrom
claude/codebase-review-philosophy-uo8vjw

Conversation

@naveed949

Copy link
Copy Markdown
Owner

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.ts now defines the generic StateMachine<C, T> interface (apply / snapshot / restore / optional size) that an application implements.
  • The replicated log carries a flat command union Command<C> = NOOP | CONFIG | C, so domain commands ride the log directly — the wire/persistence format is unchanged and node.submit(domainCommand) stays ergonomic. NOOP and CONFIG are reserved type discriminators.
  • RaftNode<C, T, SM> and ReplicatedStateMachine<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).
  • The book service moves to the example layer (models/book.ts, models/bookStateMachine.ts). Audit and raft HTTP routes are generic; book routes / app.ts are the example's adapter.
  • Added src/index.ts public library surface. Renamed the books_total metric → state_machine_entries and node status booksstateSize.
  • Type inference flows from the supplied state machine (stateMachine: SM & StateMachine<C, T>), so C/T are inferred at the construction site without explicit type args.
  • Casts are localized to the two inherently untyped boundaries (transport entries, persisted log) and commented.

Bring-your-own-state-machine

class CounterStateMachine implements StateMachine<{ type: 'INCR'; by: number }, number> {
  private n = 0;
  apply(cmd) { this.n += cmd.by; return { status: 200, data: this.n }; }
  snapshot() { return this.n; }
  restore(d: unknown) { this.n = (d as number) ?? 0; }
}
const node = new RaftNode({ id: 'node1', peers: [], stateMachine: new CounterStateMachine() }, new HttpTransport());

Verification

  • npx tsc --noEmit clean.
  • All 42 tests pass (consensus, bookApi, platform, snapshot, readBarrier, membership, crashConsistency, logBacktracking).

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

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
naveed949 marked this pull request as ready for review June 21, 2026 16:57
@naveed949
naveed949 merged commit 856c425 into main Jun 21, 2026
1 of 4 checks passed
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.
@naveed949
naveed949 deleted the claude/codebase-review-philosophy-uo8vjw branch June 23, 2026 05:54
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.

2 participants