Evidence

Every claim, with the mechanism and the check behind it.

This page lists what TulaDB claims, how the code enforces it, and the test or tool that would fail if it stopped being true. Each row was read against the source, not against our own docs — the material a risk committee, an auditor or a third-party-risk review asks for before a new system enters the payment path.

798test targets in the engine’s build; 623 run in its main gate
502C test programs, plus 226 Python cluster and integration suites
133fault-injection points, compiled out of release builds
20 × 2deterministic-simulation seeds per change, each run twice for identical traces
156Prometheus alert rules (146 alerts at warning and critical tiers, 64 paging), each with a runbook link; available to design partners, not in the 0.0.1-preview downloads
6 / 209Grafana dashboards / panels, provisioned with the observability stack
1,374tests across the Iceberg exporter and the operator console

Counts taken from the repositories’ main branches on 2026-10-29. Test totals count targets and test functions; they are a measure of coverage effort, not a proof of correctness.

Under fault, on real fleets

Money conserved in every run that produced a verdict.

Beyond the test suites, the full platform runs for an hour at a time on cloud fleets while the harness kills leaders and followers, partitions the network, freezes processes and fills disks. Every run ends with a money oracle and a check that all replicas agree.

3 November 2026, the 0.0.1-preview release gateOn the release build itself (core 2b2f8b96), 75 minutes of faults at 50,001 transfers per second offered, three Arm nodes in us-east4, the Iceberg exporter on: CHAOS_CLEAN=YES. Money oracle and final agreement passed (balances, history, holds, idempotency, registry); every leader loss under 1,000 ms of unavailability; steady-state p99 7.3 ms; 194.5 million transfers accepted. The verdict →
October 2026, Google CloudFive fault-injected runs on development builds (four at 50,001 transfers per second offered, one at 7,500): the money oracle passed in all five. One scored CHAOS_CLEAN=YES; four scored NO on other gates — recovery time and export measurement — and one of those became YES on an offline re-score, kept beside the original. Three further fault-free multi-region runs also passed the money oracle. Every run →
August 2026, AWSTwo independently provisioned fleets, 72 scenario gates, zero failures, money conserved to the unit (31,915,520 and 31,897,568). The campaign →
A missing measurement is a failAn oracle that can’t measure reports NOT_MEASURED, and that run is not counted as clean. Gates report what they could check, never what they assume.
Release status: the 0.0.1-preview release criteria are one 50K-TPS chaos run with faults, then a 6-hour multi-region soak, both on the release build. The chaos gate passed on 3 November 2026; the soak has not run yet, and its verdict will be published here the same way, whatever it says. The other runs above were on development builds.

Money

What a posting can and cannot do.

Claim
How it’s enforced
What checks it
Value moves in balanced pairs.
One transfer debits one account and credits another by the same amount, under both accounts’ locks; ledger, currency and both accounts must agree before anything changes.
tsim: conservation checked on every live node after every event
Balances cannot overflow.
Arithmetic runs in signed 128-bit and narrows to 64-bit only at storage and API edges. A transfer that would overflow is declined, never wrapped.
ERR_LIMIT on overflow; checked add at the apply site
A failed step never leaves money half-moved.
If a step after a balance change fails, the posting is undone. If the undo itself fails, the engine halts rather than run on an unbalanced state.
engine halt reason posted_undo_failed
Reads never show money that hasn’t committed.
The leader stages writes before the log and quorum confirm them; every balance read subtracts the still-in-flight effect under the account lock.
test-cluster-dirty-read, test-committed-balance-reads
Who declined is provable.
A refusal you record is an explicit reject: status 41 with the REJECT flag and your reason code verbatim. Every other durable decline is the engine’s own verdict with the engine’s status (for example ERR_LIMIT); the per-transaction cap decline also stamps the reserved reason 0xFFFF002A. Reason codes from 0xFFFF0000 up are reserved, and a caller that sends one is refused.
TXN_FLAG_REJECT on the record; reserved-range refusal at admission

Retries

Why a redelivered request is the same operation, not a new one.

Claim
How it’s enforced
What checks it
One identity, one effect.
The identity is SHA3-256 over (source, ledger, currency, txn_id). The txn_id is stored byte-for-byte: no trimming, no case folding.
test-cross-sdk-vectors (the same identity from every SDK)
A decline never turns into a success on retry.
A retry gets the original decline code, OK_DUPLICATE if the original posted, or WRONG_PHASE while it is still in flight. The answer is a function of replicated state, so leader, follower, restarted node and snapshot all agree.
test-declined-retry, test-dup-response-contract
A burst can’t poison an identity.
Transient refusals (capacity, disk, quorum timeout) write no log frame and no dedup entry, so the same txn_id is admitted fresh once the condition clears.
test-reject-not-durable
Duplicates inside one batch are caught.
The first copy’s verdict wins; later copies are answered without entering the frame.
test-batch-same-identity
Identities last as long as history.
Forever by default. With a retention window set, identities retire with the history they belong to, in every tier.
test-idem-retire-at-purge

Durability and recovery

What “acknowledged” means, and what happens when the disk misbehaves.

Claim
How it’s enforced
What checks it
Acknowledged means fsynced.
The client is answered after fdatasync (group commit coalesces up to 1,024 frames per sync). An asynchronous single-voter config that could lose acknowledged writes is refused at startup unless explicitly signed off.
startup config validation; test-cluster-sync-quorum-matrix
An fsync failure stops writes. It is never retried as if it worked.
The WAL latches unhealthy, NACKs the failed group and everything after it, truncates to the last durable prefix, and readiness drops.
test-fault-injection, test-cluster-disk-fault, test-cluster-dst-storage-fault
A torn tail is not bit-rot.
A damaged frame with valid frames after it is mid-log corruption: the node refuses to truncate committed writes and fails closed.
test-p0l-wal-first-frame-damage, test-wal-recovery-gaps
It refuses to boot over a missing log segment.
At boot the WAL proves holes from segment ids and sequence numbers. A single node with committed frames missing refuses to start and names the range; a cluster node latches until a snapshot install covers it.
test-wal-boot-hole
Restarts replay only the tail.
A graceful stop hands leadership off, then writes a final checkpoint inside a 5-second budget; if the budget runs out, the previous checkpoint stands.
test-shutdown-final-checkpoint
A full disk is refused, not crashed into.
Three stages of free-space reserve: refuse new transfers, step back from leadership, then lag as a follower instead of dying.
test-disk-guard; the chaos harness fills a follower’s disk to 92%

Replication

How replicas prove they hold the same ledger.

Claim
How it’s enforced
What checks it
Followers prove their logs match before their ack counts.
Each ack carries a 32-byte chain head. One that differs from the leader’s at the same position earns no quorum credit and raises a critical signal.
test-cluster-chain-divergence
No stale reads after a failover.
A new leader serves no read until an entry from its own term commits (the Raft §6.4 gap). Linearizable balance reads are on by default.
test-cluster-linearizability-failover, test-cluster-read-index
A partitioned node can’t disrupt the cluster.
Pre-vote is always on, and a leader that loses its majority stops taking writes and steps down on its own.
test-cluster-pre-vote
Membership changes can’t strand a quorum.
Joint consensus, plus a strict check that refuses a change whose new voter set lacks a live, caught-up majority.
test-cluster-joint-consensus, test-cluster-strict-reconfig
A new node can’t lead until it holds the whole history.
After a snapshot install it pulls retained history partition by partition, trusting a digest only when two voters agree on it. Promotion requires the full copy on every path.
test-cluster-join-full-sync, test-cluster-join-full-sync-faults, test-cluster-join-full-voter, test-cluster-join-full-upgrade
Replicas can prove they are identical.
Every node digests its full replicated state at one log position — 14 components plus account fingerprints — and the digests must match.
test-digest-at-cut; tulactl digest-at-cut

Iceberg export

Getting committed history into the lake exactly once.

Claim
How it’s enforced
What checks it
Committed data only, enforced twice.
The engine serves only committed records, and the exporter refuses any page that reaches past the engine’s committed frontier before writing a byte.
exporter: uncommitted-range refusal
Every commit carries its own receipt.
Each Iceberg snapshot records its ledger range, a SHA-256 of its rows, the row count and amount sums. The same range again is a no-op; a different digest or any overlap stops the exporter.
exporter: 925 unit tests; integration_leader_failover
Crash anywhere, duplicate nothing.
The checkpoint advances only after the Iceberg commit, and is repaired from the table’s own snapshot metadata after a crash between them.
exporter: checkpoint repair path
A gap is a stop, never a silent skip.
If the engine reclaims history the exporter hasn’t read, the exporter names the missing range and exits with a dedicated code. Recovery is an operator decision.
ERR_EXPORT_RANGE_RECLAIMED; exit 87
History purge waits for the lake.
Retention credits only the exporter’s durable acks, sent after its checkpoint is on disk, never its read position.
test-export-lease, test-partitioned-purge-credit

Operating it

What ships so the people on call aren’t guessing.

Claim
How it’s enforced
What checks it
Alerts read metrics the engine actually emits.
156 Prometheus rules, 64 of them critical, every one with a runbook link. A lint checks each metric name against the exporter source.
deploy: check_alerts.sh, promtool rule tests
A money-integrity alert group that should never fire.
12 critical rules: chain divergence, replay and apply holes, a checkpoint over a hole, a stranded quorum fail-stop, a replica refusing to apply.
deploy: tuladb-money-integrity group
Insecure deployments don’t start.
The node refuses to start with its cluster fabric reachable beyond loopback without mTLS, and the Helm chart refuses to render without TLS.
test-fabric-port-requires-mtls; chart render checks
Upgrades have one explicit point of no return.
A feature activates only when every voter supports it; finalise refuses while any node is unreachable or behind, and an older binary can’t vote or boot afterwards.
test-cluster-feature-activation, test-upgrade-status
Operator actions are on the record.
The console writes every mutating request to its audit log by middleware, and can refuse any action it can’t record. Roles are enforced on the server.
dashboard: 313 backend tests

What we don’t claim yet

Current limitations.

Knowing where a system stops is part of the evidence. These are true of 0.0.1-preview today.

No rail adapters yetThe engine does not parse ISO 20022 messages or speak any rail's protocol. Mapping a rail onto holds, postings and refusals is an adapter's job; the Real-Time Rail reference adapter is not built yet.
No liquidity automationFloors, reserved obligations and per-client caps are enforced; alerts, forecasting, sweeps between settlement accounts and payment queueing are not built.
No multi-day soak resultNo 72-hour endurance run covers this release, and we state no duration for the development soaks that were stopped early.
One WAL laneThe multi-lane WAL is experimental and refused unless explicitly unlocked. Every figure we publish is single-lane.
Security is opt-in beyond loopback defaultsThe default profile binds to loopback; TLS, authentication, storage encryption and the audit log are switched on by the secure profile, not by default. The installer’s Kubernetes chart turns mTLS and storage encryption on by default.
Tamper evidence is a modeThe default log chain detects accidental divergence. The SHA3 full-payload chain, which is tamper-evident, is a setting you choose per data directory.
No cloud throughput figure for the exporterIts committed measurements are against a local Parquet sink. Measure against your catalog.
Iceberg onlyThe exporter writes Apache Iceberg. It is tested on AWS S3 Tables, Google BigLake and a local SQL catalog; other catalogs are passed through untested.
No downgradeAfter an upgrade is finalised, the previous binary can’t vote or boot. Going back means restoring a backup.
No SSO in the console yetThe operator console uses local, scrypt-hashed accounts and an API key. There is no OIDC or SAML login.

Check it yourself

Bring the evidence to your review — then test it against your workload.

Ask for evaluation access to see the retry and hold behaviour for yourself, then bring the rows that matter to you to a pilot conversation.