Batter is the public facade for a native Tokio operational foundation.
batter-core owns process lifecycle, startup, finite commands, operation
budgets, retry, admission, health/readiness, cleanup, and telemetry. Optional
adapters own Axum request/server boundaries, PostgreSQL transaction and snapshot
scopes, Runledger lifecycle and atomic enqueue integration, and Runlimit
quota-before-work. Application futures, domain errors, SQLx queries and pools,
and routers remain native.
It is for Tokio services that need owned shutdown, deadlines, and cleanup. It is not a web framework, DI container, or a replacement for application types.
Batter's integration APIs are designed for autonomous coding agents. Prefer the canonical, library-driven path so operational invariants follow from ownership, constrained interfaces, validated configuration, and executable checks. Repeated caller obligations to coordinate cancellation, joining, cleanup, deadline relationships, registration, or error retention are design debt, even when documented. Keep application-specific protocols in the application or a supported adapter; agent-only consumption does not call for an opaque DSL, extra abstraction layers, or claims that types prove arbitrary remote effects.
On that canonical path, locally expressible invalid operational states must be unrepresentable through the public API. A documented ordering, nesting, paired call, phase transition, nonempty-input rule, cleanup sequence or exhaustive outcome obligation is not enough when Rust ownership, types or library-owned assembly can enforce it. Every new or materially changed public API receives the ADR-010 invalid-state review.
Examples are consumer contracts. When a lower-level escape hatch is necessary, its documentation must state the obligations it leaves with the caller and must not present it as equivalent to the protected path. Assess proposed changes with independent failure scenarios and fresh-agent implementation or modification tasks; clean reviews or test volume alone do not establish agent usability. Those evaluations are proposed and unexecuted unless this repository records specific evidence. See ADR-010, architecture, and usage.
Recurring invariant failures during example review require the implementation agent to assess the consumed API and report the design concern before continuing dependent repairs.
The Batter packages are version 0.0.1 publication candidates. Their manifests
target crates.io, but the source tree alone does not claim that an upload or
registry-name reservation has completed.
Linux x86_64 and macOS arm64 have execution evidence on Rust 1.94.0 and 1.98.1.
Hosted Linux verification and focused macOS jobs passed on both supported
toolchains in GitHub Actions run 35580602864
for commit 56814038f2a9cf6a34688ee39cd9f0e433487a1e. The documentation,
package-description, and rustdoc refresh in this checkout postdates that run.
The full workspace verification now requires Docker for native Runledger
PostgreSQL 18 tests. Batter adapter/reference live PostgreSQL evidence remains
scoped to the separately recorded runs. See
current status.
Batter targets Unix backends, including Linux and macOS. Windows is unsupported, and there are no plans to support it. This applies to all workspace packages, examples, tests and tooling. There are no Windows implementation branches or CI targets. Other Unix targets remain unverified. See ADR-007.
Network access is required to download dependencies on the first run.
Runledger's five native packages are part of this workspace, imported from
master 46b5cd085d011e597de9552dfebbed4c19416453. Local dependencies select one
SQLx foundation without sibling checkouts or dependency patches. Runlimit's five
native packages are also local, imported from master
12e035dac504a1d348c2058ee7ade8e61f2e7974. See consumer configuration
and native Runledger / Runlimit provenance.
Runledger remains optional for facade consumers and is absent from the default
foundation graph. Releasable packages are prepared for crates.io; the two
example packages remain unpublished.
cargo run -p batter --features axum --example http_service
# In a second terminal:
curl -i http://127.0.0.1:3000/live
curl -i http://127.0.0.1:3000/ready
curl -i http://127.0.0.1:3000/workBATTER_BIND defaults to 127.0.0.1:3000; BATTER_REQUEST_TIMEOUT_MS defaults to
2000; BATTER_BULKHEAD_CAPACITY defaults to 32. BATTER_ENV_FILE may name one
literal dotenv file; there is no default .env search. Invalid values fail before
bind. See operations. This is an
integration example, not a business API: /work simulates a 25 ms read under a
concurrency bound, and /fail uses the same application error envelope as
middleware failures. SIGINT and SIGTERM trigger shutdown through native Unix
signal listeners.
The service awaits running.wait_checked().await? after startup handoff, so a
failed task, incomplete cleanup, or coordinator error reaches its exit boundary.
Successful completion retains a report for application policy and diagnostics.
use batter::operation::OperationContext;
use std::time::Duration;
async fn read_count() -> Result<u64, std::io::Error> {
Ok(42) // Replace with your dependency; preserve its concrete error type.
}
async fn example() -> Result<(), Box<dyn std::error::Error>> {
let request = batter::operation::OperationOwner::new(Duration::from_secs(2))?.into_context();
let count = request.run("accounts.count", |_scope| read_count()).await?;
assert_eq!(count, 42);
Ok(())
}run receives a factory, creates a child context, and cancels that child when
execution completes or its future is dropped. It does not join tasks spawned by
the callback. Compose request-local futures normally; do not turn them into
unowned tokio::spawn calls. See usage for retries and ownership.
cargo run -p batter --example worker
cargo run -p batter --example process_owned
cargo run -p batter --example operation_budget
cargo run -p batter --example finite_command
DATABASE_URL='postgres://user:password@localhost/database' \
cargo run -p batter --features sqlx --example owned_pool
cargo run -p batter --features runlimit-memory,runlimit-axum --example quota_service
DATABASE_URL='postgres://user:password@localhost/database' \
cargo run -p batter-example-postgres-lifecycle --bin postgres_lifecycleThe postgres_lifecycle example connects to an existing database, probes it
with SELECT 1, shows partial-startup cleanup, and registers native pool
closure. It does not create/drop databases or migrate a Runledger schema. Every
DATABASE_URL command above must target a local test database; never commit
real connection secrets.
finite_command uses command::Command to own one native loopback operation and
retain cleanup independently of its waiter.
Service startup is a different ownership path; see
usage and the
executable Startup example.
The eighth facade example, verification, requires an explicit migration
manifest and authority policy; see the
SQLx verifier guide.
Until the coordinated registry release is visible, use one immutable Git revision
or local paths. The TOML below assumes this repository is checked out as batter
beside the consumer:
[dependencies]
# Public facade; its default graph contains only the native foundation.
batter = { path = "../batter/crates/batter" }
# Select optional toolkit namespaces explicitly, for example:
# batter = { path = "../batter/crates/batter", features = ["axum"] }
# Standalone Rust 1.94 encryption/MAC leaf, also available through feature
# `at-rest` as `batter::at_rest` with identical types.
batter-at-rest = { path = "../batter/crates/batter-at-rest" }
# Direct foundation implementation, when an adapter or focused consumer needs it.
batter-core = { path = "../batter/crates/batter-core" }
# Add this dependency for the HTTP adapter.
batter-axum = { path = "../batter/crates/batter-axum" }
# Add for owned PostgreSQL transaction/snapshot scopes, verification, and
# explicit low-level connection disposition.
batter-sqlx = { path = "../batter/crates/batter-sqlx" }
[dev-dependencies]
batter-test-support = { path = "../batter/crates/batter-test-support" }The default batter graph does not bring in encryption, Axum, SQLx, or test utilities.
Adapter APIs are also available from their direct packages. The facade exposes
batter::at_rest, batter::axum, batter::sqlx, batter::runledger, batter::runlimit, and
batter::test_support through additive opt-in features; runlimit-memory,
runlimit-postgres, runlimit-axum, and sqlx-test-support select only their
documented bridges. Each package declares its own version and Rust minimum.
The eight Batter packages are 0.0.1, native Runledger is 0.13.0, and native
Runlimit is 0.4.0. All retain Rust 1.94 as their minimum.
The standalone batter-at-rest leaf verifies that minimum independently. The
default toolchain is 1.98.1. SQLx 0.9.0 sets that floor in the adapter and
examples; extracting it does not establish a lower library minimum.
| Package | Location | Job |
|---|---|---|
batter |
crates/batter | Source-compatible public facade and runnable foundation consumers. |
batter-at-rest |
crates/batter-at-rest | MIT-licensed synchronous standalone envelope encryption and stable MAC keys. |
batter-core |
crates/batter-core | Single native implementation for process ownership, deadlines, retry, admission, cleanup, health/readiness, startup, settings, and telemetry. |
batter-axum |
crates/batter-axum | HTTP adapter: request policy, observation, correlation, readiness, browser credential transport, and native serving. |
batter-sqlx |
crates/batter-sqlx | Owned PostgreSQL transaction and read-only snapshot scopes, schema verification, explicit low-level connection disposition, and opt-in fixtures. |
batter-runledger |
crates/batter-runledger | Native initialization and settlement plus a phase-scoped atomic enqueue runner and schema snapshots built on batter-sqlx. |
batter-runlimit |
crates/batter-runlimit | Optional native atomic quota-before-work execution and protected authenticated HTTP assembly. |
batter-test-support |
crates/batter-test-support | Generic test utilities; independent of the foundation and adapters. |
runlimit-core |
runlimit/runlimit-core | Native validated policies, subject keys and decisions. |
runlimit-memory |
runlimit/runlimit-memory | Native bounded process-local quota and attempt storage. |
runlimit-postgres |
runlimit/runlimit-postgres | Native replica-safe persistence, migrations and maintenance. |
runlimit-http |
runlimit/runlimit-http | Native framework-neutral response metadata. |
runlimit-axum |
runlimit/runlimit-axum | Native caller-controlled admission layer. |
runledger-core |
runledger/runledger-core | Durable job/workflow types and validation. |
runledger-postgres |
runledger/runledger-postgres | Native persistence using the workspace SQLx foundation. |
runledger-runtime |
runledger/runledger-runtime | Native workers, scheduling and descendant supervision. |
runledger-test-support |
runledger/runledger-test-support | Native PostgreSQL 18 container-backed tests. |
runledger-tui |
runledger/runledger-tui | Separate read-only operator binary. |
batter-example-postgres-lifecycle |
examples/postgres-lifecycle | Native SQLx composition; an executable, not a library API. |
batter-example-reference-service |
examples/reference-service | Atomic authenticated delivery command, provider-effect reconciliation, explicit direct-peer/request correlation, pinned compatibility probes, validated constructors, and an explicit live test target. |
PostgreSQL provisioning stays in the external postgres-test-harness repository;
it is not a workspace member. The optional batter-sqlx/test-support feature is
selected by reference tests; the default adapter graph excludes the harness.
The optional batter-runledger adapter owns native initialization and settlement.
The reference uses its intent-to-queue run_atomic API, built on Batter-owned
SQLx scopes, and registers one application-owned provider-effect handler. Its
selected loopback-test protocol uses a stable key, canonical payload matching,
lookup reconciliation and an explicit 24-hour retention boundary; this is not an
exactly-once or arbitrary provider guarantee. batter-runlimit preserves native
quota decisions and consumption certainty under an operation budget; optional
HTTP assembly owns auth/quota/body ordering. Its memory, postgres, and axum
features are independent and off by default. It does not own PostgreSQL
initialization or maintenance.
Ownership boundaries
are in integrations; delivery tasks live in the
Beads backlog. The
compatibility manifest records the reference
package's compiled graph and executed live probes. It is not a complete durable
service.
- A timeout or cancellation drops a future. It does not roll back an external effect or prove a write failed. Nothing here supplies exactly-once effects.
- The supervisor owns registered critical tasks and admitted finite work. A
finite task initiates drain only by returning
Err(Fatal(error));?on a plain application error does not compile there, and expected business rejections belong in the success value. Dropping a receipt does not stop work or release its permit. - Task abortion is not preemption. Joining a server wrapper does not prove detached children stopped. After a panic, requested abort, or unjoined direct task, dependent finalizers are skipped and the report is unsuccessful.
- Cleanup is explicitly awaited. It is not asynchronous
Drop, general cancellation shielding, or a promise to survive SIGKILL. The owned driver continues shutdown when a waiter is cancelled, provided the runtime remains alive. - The Axum boundary ends when a response is constructed. Streaming bodies and WebSockets require a separate lifetime design.
These are API contracts and limitations, not footnotes. Read guarantees before putting side effects behind a boundary.
To use batter: usage, operations, architecture, guarantees, and security. Crate READMEs in the table above own adapter-level detail.
To work in this repository: AGENTS.md for reading order, verification, and change rules; status and testing for coverage; Beads for delivery tasks. The Effect v4 brief and reconciliation record design rationale. Primary references record upstream checks.
bash scripts/verify.sh
# For planned work, reuse passing checks and attach receipts to the plan:
bash scripts/verify.sh --plan-id <id>Both forms use Jig's complete verification profile: tests, both Clippy feature configurations, formatting, rustdoc, five built HTTP smokes and repository policy. Choose one form; a successful profile does not need a second full test run.
Local verification uses the pinned current release once. CI verifies both that
release and exact Rust 1.94.0; a weekly CI run checks floating stable. Update
rust-toolchain.toml and the pinned CI entries together when adopting a new release.
Checks preserve Cargo.lock. Use bash scripts/verify.sh --bootstrap only when
formatting sources and generating a missing lockfile is intended. Repair
compiler, lint, and test failures without weakening the documented contracts.
MIT; see LICENSE. The standalone batter-at-rest package carries a
crate-local copy of the same license so detached Cargo packages include it.
Registry publication remains a separate decision.