@hackage / keiki

Pure core for symbolic-register transducer event sourcing.

Latest0.1.0.0

About

Metadata

  • Last updated , by shinzui
  • License BSD-3-Clause
  • Maintained by: nadeem@gmail.com

  • Lottery factor: 1

Links

Installation

Tested Compilers

  1. 9.12.2
  2. 9.12.1

Readme

keiki — 継起

A Haskell library for the pure core of event sourcing, workflow engines, and durable execution, built on one formalism: the symbolic-register finite-state transducer.

keiki keeps the runtime boundary deliberately small. You describe an aggregate or workflow once as a typed transducer; the library derives the event-sourcing facade, replay, acceptors, projections, composition machinery, diagram renderers, and optional symbolic checks from that single declaration.

The name

継起 (keiki) is the Japanese word for "successive occurrence" — events that follow, one after another, each rising out of what came before.

Kanji Reading Meaning
kei to continue, to succeed (as an heir does), to follow on
ki to rise, to occur, to begin, to happen

The choice is literal. An event-sourced aggregate is a sequence of events in succession, each one rising out of the state the previous events left behind. keiki names that pattern directly: 継 (the prior event is succeeded by) 起 (the next one rising). The replay fold — reconstitute :: [Event] → State — is exactly the operation the word describes.

(Disambiguation, since the romanisation is ambiguous: this is 継起, not 景気 keiki "economic conditions" nor 計器 keiki "gauge / instrument". Only the 継起 reading carries the "succession of events" sense.)

What it is

A unified treatment of three problems often solved with separate runtime concepts:

  1. Event sourcing — store the immutable log of events, replay to recover state.
  2. Workflow engines — coordinate long-running multi-step processes.
  3. Durable execution — resume cleanly after a crash, mid-process.

keiki models all three as finite-state transducers with a typed register file and predicate-labelled guards: a practical hybrid of Symbolic Finite Transducers and Streaming String Transducers. From one SymTransducer declaration, the library mechanically derives:

  • the Chassaing-shape Decider (decide/evolve/initialState/isTerminal),
  • input- and output-side Acceptors,
  • per-vertex projections (the "B-presentation" view),
  • composition (compose, alternative, feedback1),
  • profunctor / Category / Strong / Choice instances,
  • Mermaid and Markdown renderers for documentation,
  • single-valuedness checks via SBV + z3 as an opt-in symbolic CI gate.

delta / omega / applyEvent use concrete predicate evaluation — no solver in the per-event hot path. Solver dispatch is reserved for build-time analysis.

Status

Pre-1.0, prepared for the initial 0.1.0.0 Hackage release. The planned v0.1 surface is implemented and validated against in-tree tests plus the downstream jitsurei worked-example package.

The core package is intentionally codec-free. JSON support lives in keiki-codec-json, and downstream codec testing helpers live in keiki-codec-json-test.

Build

Requires GHC 9.12 and (for the symbolic analyses only) z3 on PATH.

nix develop          # provides ghc 9.12, cabal, z3
cabal build all
cabal test all

Pure evaluation (delta, omega, step, reconstitute) does not depend on z3. The symbolic checks in Keiki.Symbolic will fail loudly if z3 is missing when invoked.

A taste

The smallest useful aggregate, in full:

emailDelivery
  :: SymTransducer (HsPred EmailRegs EmailCmd)
                   EmailRegs EmailVertex EmailCmd EmailEvent
emailDelivery = B.buildTransducer EmailPending emptyRegFile
                  (\case EmailSentVertex -> True; _ -> False) do
  B.from EmailPending do
    B.onCmd inCtorSendEmail $ \d -> B.do
      B.slot @"emailRecipient" .= d.recipient
      B.slot @"emailSubject"   .= d.subject
      B.slot @"emailSentAt"    .= d.at
      B.emit wireEmailSent EmailSentTermFields
        { recipient = d.recipient
        , subject   = d.subject
        , at        = d.at
        }
      B.goto EmailSentVertex

Decider, Acceptor, replay, and the per-vertex view are all derived from this one declaration. See the sibling jitsurei/ package for the full worked aggregates the test suite drives.

Repository documentation

The repository includes user guides, design notes, and implementation history:

Folder Audience
docs/foundations/ Onboarding. Reads as a tutorial in ~1 hour; assumes Haskell, no automata-theory background. Start at 00-reading-guide.md.
docs/guide/ Action-oriented. user-guide.md is the canonical authoring walkthrough; topical guides cover composition, profunctors, symbolic CI, AST drop-down, and B-views.
docs/research/ Design notes for the library itself. Assume foundations vocabulary. The current baseline is synthesis-c-foundation-b-presentation-with-worked-examples.md.
docs/adr/ Stable architecture decisions distilled from the plan history.
docs/plans/ and docs/masterplans/ Execution records for the work that produced v0.1.
docs/historical/ Superseded design notes, retained for context only.

For a status snapshot — what's implemented, what's next, and what's intentionally out of scope — see ROADMAP.md.

Recommended shortest path:

  1. docs/foundations/00-reading-guide.md — orientation.
  2. docs/guide/user-guide.md — write your first aggregate.
  3. docs/research/synthesis-c-foundation-b-presentation-with-worked-examples.md — the design baseline.

License

BSD-3-Clause. See keiki.cabal for package metadata.