Docs/SDKs & toolsv0.0.1-preview
tulactl
The command-line client — scripting, inspection, health checks, and cluster operations from a shell.
tulactl is the TulaDB command-line client: quick inspection during development, health probes for orchestration, and the operator surface for cluster membership changes.
Connection
tulactl --host 127.0.0.1 --port 7700 ping
Global flags (each with an environment fallback): --host (TULADB_CLIENT_HOST, default 127.0.0.1), --port (TULADB_CLIENT_PORT, default 7700), --timeout SECONDS (TULADB_CLIENT_TIMEOUT, 1–3600); TLS via --tls --tls-ca-file --tls-cert-file --tls-key-file --tls-server-name (--tls-no-verify exists for local testing only); credential auth via --api-key, --token, or --token-file. Status commands (health, ready, topology) accept --json for machine-readable output.
Everyday ledger work
The canonical walkthrough — control account, customer accounts, seed, transfer, hold:
tulactl create-account --id 1000 --ledger 1 --currency 840 --control --credit-normal
tulactl create-account --id 1 --ledger 1 --currency 840
tulactl create-account --id 2 --ledger 1 --currency 840
tulactl seed --control 1000 --id 1 --amount 100000 --ledger 1 --currency 840 --source 1001 --txn demo-seed-1
tulactl transfer --from 1 --to 2 --amount 500 --ledger 1 --currency 840 --source 1001 --txn demo-transfer-1
# ✓ OK phase: POSTED
tulactl reserve --from 1 --to 2 --amount 250 --ledger 1 --currency 840 --source 1001 --txn demo-hold-1
# ✓ OK phase: PENDING
tulactl balance --id 2 --ledger 1 --currency 840
tulactl balances --id 2 --ledger 1 # every currency account 2 holds in ledger 1
An account is named by (ledger, currency, id), so balance and history require --ledger and --currency alongside --id. balance prints raw integer minor units unless you pass --scaled. seed --amount goes the other way: it is a decimal in the currency’s major units, scaled by the registered currency’s scale (--amount 10.50 in a scale-2 currency posts 1,050 minor units); a currency that isn’t registered has scale 0, so the number is taken as minor units.
create-account fills in what you omit: without --currency the currency is ledger_id × 100 — a legacy default that names no real currency for most ledgers, so always pass it — and an account created with no flags is DEBIT_NORMAL with limit_low = 0 (it can’t go below zero). A --control account with neither limit set gets no limits at all. Where --ledger/--currency are optional on a write (transfer, reserve, seed), tulactl infers them from the named accounts and refuses an account set that spans ledgers or currencies.
Re-run the transfer verbatim and the idempotency contract answers:
↺ OK_DUPLICATE phase: POSTED
Also available: commit, rollback, tiered-transfer, tiered-reserve, tiered-commit, tiered-rollback (resolve by --transfer-id, or by --source --txn plus the currency or ledger), get --transfer-id ID | --source S --txn TXN --currency C, history --id ID --ledger L --currency C [--limit N] [--after-seq N] [--after-index N], pending, rejected, and create-account flag switches for every account flag (--control, --settlement, --allow-negative, --no-debit, --no-credit, …). Every write tulactl sends is a one-item batch on the wire — the same single write path every SDK uses.
Ledger administration
tulactl ledger-create --ledger 1 --currency 840 [--ttl-seconds S] [--mode default|position-settlement]
tulactl ledger-set-max-amount --ledger 1 --max 1000000 [--yes] # --max 0 clears the cap
tulactl ledger-set-expiry --ledger 1 --ttl-seconds 900 [--yes] # 0 disables reservation expiry
ledger-create is idempotent for a matching id; ledger-scoped admin operations refuse an unregistered ledger. These require the cluster-admin role.
Health probes
Two commands are built for orchestration, with meaningful exit codes:
tulactl health [--verbose] [--json] # exit 0 = healthy, 1 = unresponsive, 2 = unreachable
tulactl ready [--max-lag N] # exit 0 = ready, 1 = recovering, 2 = unreachable
ready is the right probe for load balancers and Kubernetes readiness: it answers “does this node have a leader and is it caught up”, not merely “is the port open”.
Cluster operations
Membership and leadership changes go through tulactl, not through config edits — after a cluster’s first committed configuration, the replicated config is authoritative:
tulactl version
tulactl topology show --data-dir /var/lib/tuladb [--json]
tulactl topology check --data-dir /var/lib/tuladb --policy single_region_ha
tulactl reconfig --data-dir /var/lib/tuladb --show-only # inspect
tulactl reconfig --data-dir /var/lib/tuladb --promote 4 [--yes] # learner → voter
tulactl reconfig --data-dir /var/lib/tuladb --demote 2 [--yes]
tulactl reconfig promote-standby --group dr-east
tulactl transfer-leadership --target 2 [--yes]
reconfig --show-only and reconfig --force-single-voter NODE_ID are offline (the daemon must be stopped; they read or rewrite the committed cluster config directly). The other reconfig forms connect to the leader and acknowledge only after the change is quorum-committed and applied. One --promote/--demote is a single-server change; two or more submit a target the leader reaches through joint consensus. Mutating forms prompt on an interactive terminal unless you pass --yes, and require cluster-admin.
Promote new nodes from learner to voter one at a time and let replication catch up in between. A learner is promotable only once it has a complete copy — its idempotency set, any migration, and its retained history (see Joining a node); see Replication for how membership changes stay safe under the hood.
Joins, upgrades and features
tulactl join-status --data-dir /var/lib/tuladb [--snapshot-dir DIR] [--metrics FILE] [--json]
tulactl upgrade-status --nodes n1:7700,n2:7700,n3:7700 [--json]
tulactl upgrade-finalize --nodes n1:7700,n2:7700,n3:7700 [--yes]
tulactl features show --data-dir /var/lib/tuladb [--json]
tulactl activate-feature --feature history-purge [--yes]
tulactl activate-feature --feature ledger-registry-enforce --yes
join-statusreads the node’s full-copy join journal and its runtime metrics: the history range being synced, partitions adopted,HISTORY_OK, and the account-metadata-history step. It exits0when no join is in force andHISTORY_OKis not reported unset,1while a join (or the metadata step) is still running orHISTORY_OKis unset, and2on a usage error or an unreadable journal — so it works as a wait-until-done probe.upgrade-statusshows each node’s build, wire and control version windows, and supported versus active cluster features; name every voter and learner. It exits1if a node doesn’t answer.upgrade-finalizeis a rolling upgrade’s point of no return: it activates the upgrade gates every node supports, and is refused while any node is unreachable or still on the previous release. It is irreversible. A cluster formed fresh on this release starts with its gates already active and never needs it.activate-feature:reserve-expiryandsnapshot-cluster-configare always on (naming one answers OK and changes nothing), andhistory-purgeactivates itself on a leader withhistory_retention_hoursset — so this command is never required in normal operation.ledger-registry-enforceis the exception you choose deliberately: it makes ledger registration mandatory, refusing transfers and account creates on any unregistered ledger cluster-wide. It cannot be turned off, it is refused while any account still sits on an unregistered ledger, and it always needs an explicit--yes. Everyactivate-featureneeds thecluster-adminrole, and every voter must support the feature first.
Diagnostics and planning
tulactl digest-at-cut --seq 1250000 [--timeout-ms N] [--no-cold] [--json]
tulactl autotune-plan --nodes n1:7700,n2:7700,n3:7700 --config tuladb.toml
tulactl history-floor-clear --data-dir DIR --segments-dir DIR --snapshot-dir DIR
digest-at-cutcaptures order-independent digests of this node’s replicated state — balances, holds, idempotency, history, registries, retention, chain head — at exactly log positionS. Ask every node for the sameSwith load stopped and compare: equal digests mean equal replicated state. Exit0when captured,3when the node was already pastS, never reached it, or couldn’t capture (the reason is printed). Requirescluster-admin.autotune-planis read-only: from every voter’s usable memory and the cluster rating it prints a cluster-uniform config snippet (max_accounts, dedup limits) to apply to every node.history-floor-clearis an offline repair (node stopped) for one documented boot refusal around the history-purge floor; use it only when the runbook for that refusal says to.