Docs/SDKs & toolsv0.0.1-preview

tulactl

The command-line client — scripting, inspection, health checks, and cluster operations from a shell.

On this page
  1. Connection
  2. Everyday ledger work
  3. Ledger administration
  4. Health probes
  5. Cluster operations
  6. Joins, upgrades and features
  7. Diagnostics and planning

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-status reads 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 exits 0 when no join is in force and HISTORY_OK is not reported unset, 1 while a join (or the metadata step) is still running or HISTORY_OK is unset, and 2 on a usage error or an unreadable journal — so it works as a wait-until-done probe.
  • upgrade-status shows each node’s build, wire and control version windows, and supported versus active cluster features; name every voter and learner. It exits 1 if a node doesn’t answer.
  • upgrade-finalize is 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-expiry and snapshot-cluster-config are always on (naming one answers OK and changes nothing), and history-purge activates itself on a leader with history_retention_hours set — so this command is never required in normal operation. ledger-registry-enforce is 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. Every activate-feature needs the cluster-admin role, 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-cut captures order-independent digests of this node’s replicated state — balances, holds, idempotency, history, registries, retention, chain head — at exactly log position S. Ask every node for the same S with load stopped and compare: equal digests mean equal replicated state. Exit 0 when captured, 3 when the node was already past S, never reached it, or couldn’t capture (the reason is printed). Requires cluster-admin.
  • autotune-plan is 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-clear is 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.