Developers define records for stored state, commands for requested actions and events for business changes. Rust macros generate data-format checks and typed accessors from these definitions. Handlers implement the rules; the engine provides replication, logging and recovery.

Model excerpt · Account / Deposit
#[record(kind = 1, record_len = 64,
         uk(id = 0, fields = [account_id]))]
pub struct Account {
    #[field(index = 1, immutable = true)]
    pub account_id: u64,
    #[field(index = 2)]
    pub total_credit: Decimal<6>,
    #[field(index = 3)]
    pub total_debit: Decimal<6>,
}

#[command(kind = 1)]
pub struct Deposit {
    #[field(index = 1)]
    pub account_id: u64,
    #[field(index = 2)]
    pub amount: Decimal<6>,
}

The model uses fixed-point decimals with six fractional digits and a unique key on account ID. The excerpt is partial and does not compile on its own. Definitions are from the current Bank v1.1 schema.

Deposit as a single business transaction

For a deposit of 100.000000, the handler changes both the customer and platform accounts within the same command transaction:

Each side emits a BalanceChanged event reporting the account’s updated totals; events can be returned with the response, subject to configured size limits. Non-positive amounts, deposits into the platform account and arithmetic overflow are rejected, leaving both accounts unchanged.

Deposit handler excerpt · Business rules and changes
let account_id = command.account_id();
let amount = command.amount();

if account_id == PLATFORM_ACCOUNT_ID {
    return Err(BankV2Error::PlatformDeposit);
}
if amount <= ZERO_AMOUNT {
    return Err(BankV2Error::NonPositiveAmount);
}

// Both changes happen in the same transaction.
// adjust_account uses checked_add internally.
let (account_credit, account_debit) =
    Self::adjust_account(account_id, tx, amount, ZERO_AMOUNT, true)?;
let (platform_credit, platform_debit) =
    Self::adjust_account(
        PLATFORM_ACCOUNT_ID, tx, ZERO_AMOUNT, amount, false,
    )?;

Self::emit_balance_changed(tx, account_id, account_credit, account_debit);
Self::emit_balance_changed(
    tx, PLATFORM_ACCOUNT_ID, platform_credit, platform_debit,
);
Ok(())

Validate structure before executing business logic

Generated validation inspects the entire request: field lengths, allowed values, fixed-width encoding and trailing bytes. Both modes reject malformed requests before execution; handlers enforce business rules such as valid amounts and balances.

Define data and business rules

Developers define the data model, implement handlers and write tests; the engine supplies data access, transaction execution and cluster management. Business-invariant checks are in development.

The same model runs in the real cluster

Bank Deposit runs through the same network and replication path as a deployed cluster. Tests cover three separate node processes, lost responses and recovery after a process crash.

Explicit inputs keep execution replayable

Plugins must execute deterministically. External I/O, unconstrained randomness and machine-local time must not silently influence results. External facts enter the core as replayable inputs.

Record revisions

The example starts with the customer account absent and the platform account already at revision 1992 (credit = 0, debit = 5,000), then applies two successful deposits and one rejected zero-amount deposit. Amounts, IDs and revision numbers are illustrative; the revision rules below are the engine’s actual behavior: new records start at revision 1, each transaction that modifies a record increments its revision once, and a business rejection leaves records unchanged.

Command / OutcomeCustomer Account(42)Platform Account(0)Events returned with the result
Deposit(100)
Success
Absent → revision 1
credit = 100, debit = 0
revision 1992 → revision 1993
credit = 0, debit = 5,100
Two BalanceChanged events
New cumulative values for both accounts
Deposit(25)
Success
revision 1 → revision 2
credit = 125, debit = 0
revision 1993 → revision 1994
credit = 0, debit = 5,125
Two new BalanceChanged events
From this committed, applied execution
Deposit(0)
Business rejection
Still revision 2
credit = 125, debit = 0
Still revision 1994
credit = 0, debit = 5,125
Business rejection
No account-change events

A record revision tracks changes to a single record and is distinct from the schema version, plugin version and global tx_seq. Within a transaction, all edits to one record produce a single revision; records that are not modified keep theirs.

Deposit sequence: simulate → verify → commit → apply → respond

Bank Deposit execution and response with Verify modeThe client submits Deposit. The leader simulates the changes to both accounts and the two events without modifying business state. A voting follower independently checks execution and persists the log before acknowledging replication. After quorum commit, the leader installs the account changes and returns the result with two balance-change events. Event contents are not stored in the replication log.Business clientLeader + Bank pluginVoting followerLeader’s local recordsDeposit(account 42, amount 100)Simulate: new revisions 1 and 1993Two events; business state not installedReplicate command + expected resultsIndependently verify executionRequired persistence; not yet committedConfirm replicationQuorum commitsUpdate both accounts: customer revision 1 + platform revision 1993Record changes installed; actual outcomes readyFinal result + two eventsBoth account changes included in the responseVerify mode with a voting follower. Business event contents are not stored in the replication log or recovery snapshots.
Intermediate acceptance messages, retries and other followers are omitted for clarity. A node reuses its simulation results only when the starting state, inputs and expected results match exactly. Record changes are applied after commit.

Events returned with responses

The first Deposit’s successful response can include both account changes:

  • Account 42: total credit 100, total debit 0.
  • Account 0: total credit 0, total debit 100.

Both events belong to the same transaction. The caller sees the updated totals immediately, without a second query.

Record revisions and events serve different purposes

A revision identifies a version of a record. An event returned with the response describes a business change. BalanceChanged currently contains the account and cumulative amounts but no revision field.

An audit trail should link records, their before and after revisions, commands and plugin versions. Responses are subject to event-count and size limits and may be truncated or lost; complete auditing requires separate archival.

Bank is a reference example, not a complete banking product. Deposit demonstrates the execution path; Withdraw and Transfer handlers are in development. Events returned with responses give immediate feedback; durable messaging and auditing require separate storage.