{"id":51080472,"url":"https://github.com/elares/ironbus","last_synced_at":"2026-07-04T23:00:38.326Z","repository":{"id":362796208,"uuid":"1260067551","full_name":"ELares/IronBus","owner":"ELares","description":"IronBus: a simple, resilient, hyperscale, edge-first, cross-platform single-topic message queue in Rust.","archived":false,"fork":false,"pushed_at":"2026-07-01T05:02:56.000Z","size":7624,"stargazers_count":0,"open_issues_count":138,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-07-01T05:08:40.405Z","etag":null,"topics":["backpressure","crash-recovery","durability","edge-computing","embedded-database","event-streaming","iot","message-broker","message-queue","nats","pubsub","rust","single-binary","wal"],"latest_commit_sha":null,"homepage":"","language":"Rust","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/ELares.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":"CONTRIBUTING.md","funding":null,"license":"LICENSE-APACHE","code_of_conduct":"CODE_OF_CONDUCT.md","threat_model":null,"audit":null,"citation":null,"codeowners":".github/CODEOWNERS","security":"SECURITY.md","support":null,"governance":"GOVERNANCE.md","roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-06-05T06:02:11.000Z","updated_at":"2026-07-01T04:55:54.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/ELares/IronBus","commit_stats":null,"previous_names":["elares/ironbus"],"tags_count":191,"template":false,"template_full_name":null,"purl":"pkg:github/ELares/IronBus","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ELares%2FIronBus","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ELares%2FIronBus/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ELares%2FIronBus/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ELares%2FIronBus/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/ELares","download_url":"https://codeload.github.com/ELares/IronBus/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ELares%2FIronBus/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":35138078,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-07-04T02:00:05.987Z","response_time":113,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["backpressure","crash-recovery","durability","edge-computing","embedded-database","event-streaming","iot","message-broker","message-queue","nats","pubsub","rust","single-binary","wal"],"created_at":"2026-06-23T17:30:32.835Z","updated_at":"2026-07-04T23:00:38.310Z","avatar_url":"https://github.com/ELares.png","language":"Rust","funding_links":[],"categories":[],"sub_categories":[],"readme":"\u003cp align=\"center\"\u003e\n  \u003cimg src=\"docs/assets/ironbus-logo.png\" alt=\"IronBus logo\" width=\"360\"\u003e\n\u003c/p\u003e\n\n# IronBus\n\n**A single durable, crash-safe message queue for the edge, in one static Rust binary.**\n\n\u003e Status: active development, with most of the mission shipped. The single-node broker, multi-stream + subjects + wildcards, a real multi-node cluster (C1–C7), idempotent + transactional produce, three-mechanism auth, and the observability + recovery tooling are all built, wired, and CI-gated. The remaining gaps are tracked per item: partitions through the engine ([#693](https://github.com/ELares/IronBus/issues/693)), the TLS transport ([#766](https://github.com/ELares/IronBus/issues/766)), and routing richness ([#553](https://github.com/ELares/IronBus/issues/553), [#555](https://github.com/ELares/IronBus/issues/555), [#764](https://github.com/ELares/IronBus/issues/764)). Code lands one small, reviewed, CI-gated PR at a time. Start at the [vision EPIC (#1)](https://github.com/ELares/IronBus/issues/1).\n\u003e\n\u003e **Where this is going:** the v2 mission is for IronBus to be feature-rich and decisively better than NATS on every front, **single node or clustered**, with clustering a first-class goal (not post-1.0) — and most of it has shipped. Read **[docs/MISSION.md](docs/MISSION.md)** — it is scrupulously explicit, in three tiers, about what is measured today, what is shipped and wired (with scope edges), and what is still target.\n\nIronBus is one durable, ordered queue (think a single AWS SQS queue) that lives on the device, survives power loss and corrupt files on its own, and fans out to many consumers. It ships as a single static binary you can drop onto a Raspberry Pi. It takes the best small, composable ideas from MQTT, NATS, Kafka, Pulsar, Redpanda, RocksDB, Redis Streams, and SQS, and leaves behind the operational weight and the silent durability footguns that do not survive a battery-less edge node. The single-node durable broker, multi-stream + subjects + wildcards, and a real multi-node cluster all run today; the remaining v2 items (partitions, TLS, routing richness) are tracked with a per-capability status in [docs/MISSION.md](docs/MISSION.md). A KV store and an object store are non-goals: IronBus stays a pure message bus.\n\n---\n\n## Why IronBus exists\n\nEvery existing broker is wrong for a resilient durable-log edge workload in a different way, and each wrongness maps to one of our tenets:\n\n- **Kafka** defaults to NOT calling `fsync` per write and leans on replication for durability. On an edge box that loses power, the page-cache loss window is real, and replicas usually share the same power rail, so the independent-failure assumption is false. It also drags in a JVM.\n- **NATS Core** is beautifully simple but has no persistence. JetStream adds durability but a heavier surface.\n- **MQTT** is edge-friendly and simple, but it is a protocol, not a durable, replayable log.\n- **SQS** is the delivery model we want (visibility-timeout leases, dead-letter queues, dedup), but it is a managed cloud service, the opposite of embeddable and edge-first.\n- **RocksDB, Pulsar, Redpanda, Redis Streams** each solved one piece beautifully (a checksummed log, segment-centric storage, a single self-contained binary, lease-based consumer groups), but none is the whole thing.\n\nNone of them is a single static cross-platform binary that self-heals against corrupt files with bounded, reported loss. IronBus exists to be exactly that intersection.\n\n---\n\n## The five tenets\n\nWe rank the tenets, and when two conflict we resolve in this order: **Resilient \u003e Simple \u003e Edge First \u003e HyperScale \u003e Cross Platform.**\n\n| Tenet | What it means in practice |\n| --- | --- |\n| **Simple** | One logical queue, one binary, one config file with safe defaults, a tiny length-framed binary wire protocol whose stored records you can decode with the built-in `ironbus peek` and `ironbus dump` commands. Install to first message in under a minute (see the [Quick start](#quick-start-from-install-to-many-producers-and-consumers)). No ZooKeeper, no JVM, no external dependencies. |\n| **Resilient** | Every acknowledged durable write survives power loss. Startup always recovers a consistent prefix. A torn tail or a poison record or segment is skipped, never fatal, with loss bounded and reported as a number. |\n| **HyperScale** | High per-core throughput on edge hardware (not horizontal scale-out): a bounded ring-buffer core with structural backpressure, group-commit `fdatasync`, and zero-copy fan-out. Measured head-to-head against NATS, Kafka, and Redpanda in the [Benchmarks](#benchmarks) section — not asserted. |\n| **Edge First** | RAM ceilings, flash-wear budgets, and brownout behavior are first-class configuration, not afterthoughts. The queue spills to disk and sheds load rather than blocking producers or running out of memory. |\n| **Cross Platform** | One static musl binary per architecture (aarch64, armv7, x86_64), kernel-only dependency, reproducible builds, embedded SBOM. |\n\n---\n\n## What IronBus is, and is not\n\n**IronBus v1 IS:**\n\n- A single durable, totally ordered, append-only log per instance (one queue), consumed by many consumers.\n- At-least-once delivery with SQS-style visibility-timeout leases, redelivery, a max-deliver limit, and a dead-letter queue.\n- Local-first and embeddable, durable on one node by calling `fdatasync` before it acknowledges a write.\n- Self-healing: it detects corruption, skips poison records and quarantines unreadable segments, resynchronizes to the next valid record, and reports exactly what was lost.\n- A single static binary that is both the broker and the CLI.\n\n**Most of the v2 mission has shipped; the remaining items are the v2 roadmap, all tracked with an\nexplicit status in [docs/MISSION.md](docs/MISSION.md) §2.** The mission is for IronBus to be feature-rich\nand decisively better than NATS on every front, single node **or clustered**. Single node is the\nzero-config default the cluster degrades to, not the ceiling:\n\n- **Multi-stream + subjects + wildcards ship today; partitions are the remaining gap.** Named streams,\n  subject routing, and `*` / `\u003e` wildcards are wired end-to-end (an unbound subject is a typed fail-closed\n  reject); partitions are built in storage but not yet wired through the engine (V2-M2, #693). A KV store\n  and an object store are non-goals: IronBus stays a pure message bus.\n- **A real multi-node cluster ships today; clustering is first-class.** Beyond the zero-config single node\n  (`fdatasync` before ack), a genuine cluster runs behind `--cluster-id / --cluster-peer`: a metadata Raft\n  group, per-partition ISR pull replication, a `fsync`'d-on-a-quorum ack default, leader-epoch fencing, and\n  footer / CRC divergence self-heal that **preserve** every durability / recovery / edge / flash-wear\n  guarantee below; the cluster recovery invariants (CI1–CI4) extend the single-node\n  bounded-and-reported-loss discipline across replicas. Scope edge: replication covers the default log\n  today (multi-partition awaits #693) and the cluster benchmarks are not yet CI-gated (#636).\n\n**IronBus is explicitly NOT (these are durable non-goals):**\n\n- Not exactly-once. At-least-once is the contract, with an optional fire-and-forget fast path. No exactly-once handshake. Effectively-once is available via the opt-in [idempotent producer](#idempotent-producer-effectively-once).\n- Not a key/value store. A KV store (the NATS JetStream KV analog) is a different product category; IronBus stays a pure message bus.\n- Not an object store. Chunked object storage is out of scope. (Tiered storage of cold log segments to an object-storage backend is core log infrastructure and stays on the roadmap, which is distinct from being an object store.)\n- Not a Kafka wire-protocol clone, and not a Windows product in v1 (Windows fsync and path semantics differ enough to threaten the durability guarantee).\n\n---\n\n## Benchmarks\n\nPerformance ([#19](https://github.com/ELares/IronBus/issues/19)) is measured, not asserted. The numbers below are the **measured 3-run medians** from the July 2026 cross-broker study ([#1023](https://github.com/ELares/IronBus/issues/1023)): IronBus (current main) vs **Kafka 4.3.1** (KRaft, Temurin 21) vs **Redpanda v26.1.12** vs **NATS 2.14.3** (JetStream and Core), single node each, on one quiet Apple M4 Pro (14 cores, 48 GB, macOS 26.5). Every row pins each broker to a **matched durability tier** and labels it; the full methodology, the pinned per-broker configs, every cell, and the reproducible harness live in **[docs/benchmarks/CROSS_BROKER_2026_07.md](docs/benchmarks/CROSS_BROKER_2026_07.md)**.\n\n### Throughput (msg/s, single node, medians of 3)\n\n| Row (durability label) | Size | IronBus | NATS JetStream | Kafka | Redpanda (VM)\\* |\n| --- | --- | ---: | ---: | ---: | ---: |\n| **P1** sync-per-message produce (fsync before **every** ack) | 128 B | 250 | 253 | 242 † | *(2,547)* |\n| | 1 KiB | 250 | 250 | 221 † | *(2,322)* |\n| **P2** group-commit produce (fsync-before-ack, coalesced) | 128 B | 64,209 § | — ‡ | 352,089 † | *(1,085,634)* |\n| | 1 KiB | 15,262 | — ‡ | 193,669 † | *(196,834)* |\n| **P3** relaxed produce (page-cache ack, NOT power-loss-safe) | 128 B | 1.60 M | 285 k | 2.34 M | *(1.73 M)* |\n| | 1 KiB | **1.04 M** | 229 k | 582 k | *(296 k)* |\n| **P4** in-memory produce (ephemeral) | 128 B | **1.89 M** | 409 k | n/a | n/a |\n| | 1 KiB | **1.20 M** | 372 k | n/a | n/a |\n| **C1** consume / replay (single-consumer drain, durable log) | 128 B | 1.65 M | 394 k | 6.24 M | *(2.75 M)* |\n| | 1 KiB | 1.17 M | 371 k | 2.02 M | *(428 k)* |\n\n### Produce→ack latency (µs, single in-flight, medians of 3)\n\n| Row (durability label) | Size | IronBus p50 / p99 | NATS JetStream p50 / p99 | Kafka p50 / p99 † | Redpanda (VM) p50 / p99 \\* |\n| --- | --- | --- | --- | --- | --- |\n| **L1** durable ack (fsync before ack) | 128 B | 4,002 / 4,994 | 3,996 / 5,045 | 5,000 / 13,000 | *(3,000 / 9,000)* |\n| | 1 KiB | 3,998 / 4,185 | 4,001 / 4,987 | 5,000 / 13,000 | *(3,000 / 9,000)* |\n| **L2** in-memory ack (no fsync) | 128 B | **20.6 / 28.1** | 32.8 / 69.1 | n/a | n/a |\n| | 1 KiB | **22.4 / 28.9** | 33.3 / 69.8 | n/a | n/a |\n\nWhat the table says, factually:\n\n- **IronBus beats or ties NATS on every row.** The P1/L1 durable rows are a tie inside noise — IronBus and NATS `sync_interval=always` are the only native entrants whose ack means the bytes survived power loss on this host, and both sit at the ~4 ms macOS `F_FULLFSYNC` wall (~250 msg/s), with IronBus carrying the tighter p99 (4,994 µs vs 5,045 µs). Every other row is an outright win: relaxed produce 5.6x / 4.6x, in-memory produce 4.6x / 3.2x, consume 4.2x / 3.1x.\n- **The L2 latency row flipped.** Before [#1032](https://github.com/ELares/IronBus/issues/1032) IronBus lost this row ~5x (163 µs vs 33 µs p50 — a double cross-thread wake-up chain). The spin-assisted produce-reply handoff turned it into a win: **21 µs p50 / 28 µs p99** vs NATS JetStream memory 33 / 69 — and it also beats NATS Core request-reply on its home turf (61 / 106 µs, an extra non-durable datapoint; see the methodology doc).\n- **P3 at 1 KiB and P4 at both sizes are the fastest entrants outright** (P4 has only two honest entrants: Kafka and Redpanda have no true in-memory mode).\n- **P2 is a durability-label mismatch, not a like-for-like loss.** Kafka's `flush.messages` issues `fsync(2)`, which macOS does **not** translate into a drive-cache flush (`F_FULLFSYNC` is a separate, ~4 ms-per-call fcntl that IronBus and NATS-sync pay). IronBus's 64 k/15 k is the only native P2 number whose ack means bytes-on-durable-media on this host. NATS JetStream has no ack-after-fsync group-commit mode at all (its honest durable peer number is the P1 row).\n- **§ The P2/128 B single-connection number regressed here since the study, by design.** The pipelined sync tier ([#1040](https://github.com/ELares/IronBus/issues/1040)) optimizes **multi-connection** group commit — where IronBus now beats every peer including Redpanda's peak (see the [matched-conditions study](docs/benchmarks/REDPANDA_MATCHED_2026_07.md), which also removes the Redpanda-in-VM caveat) — at a **single-connection** cost on platforms where the `fdatasync` is wall-dominant. On macOS `F_FULLFSYNC` (~4 ms), retiring the fixed group-commit window for the self-clocking drain coalesces fewer records per barrier when only one connection feeds it, so single-connection P2/128 B moved from ~88 k to ~64 k (measured A/B on the same host; P2/1 KiB and every non-fsync-tier row are unchanged, and on Linux — cheap `fdatasync` — single-connection P2 is unchanged). The single-connection ceiling is the session's per-connection drain, tracked as [#1045](https://github.com/ELares/IronBus/issues/1045); multi-connection durable produce is a large win on every substrate measured.\n- **Two acknowledged architecture-class gaps vs Kafka**, each with a filed design issue: the consumer drain (C1: 1.65 M vs 6.24 M @128 B — Kafka's consumer rides `sendfile(2)` zero-copy; [#1034](https://github.com/ELares/IronBus/issues/1034)) and small-record page-cache produce (P3 @128 B: 1.60 M vs 2.34 M — Kafka pipelines 5 in-flight 128 KiB batch requests; [#1035](https://github.com/ELares/IronBus/issues/1035)).\n\n\u003e **Disclosures — read before quoting any number.**\n\u003e - One machine, single-node brokers, loopback TCP, serial execution (one broker at a time), fresh data dirs per cell, pilot-run→frozen-count→3-timed-runs→median protocol, Kafka given an extra unrecorded JVM warm-up run per cell.\n\u003e - \\* **Redpanda ran inside a lima VM** (it is Linux-only; vz VM, production mode, `write_caching=false` on the durable tiers). A guest `fsync` through a virtual disk is **not power-loss-comparable** to a host `F_FULLFSYNC`, and the VM adds a user-space port-forward hop — so Redpanda's numbers are shown *(parenthesized)* as an appendix datapoint and excluded from the native rankings, in both directions: its wins and losses here prove nothing about bare-metal Redpanda.\n\u003e - † **Kafka's \"fsync\" rows are not power-loss-comparable on macOS**: `log.flush.interval.messages` calls `fsync(2)`, which macOS satisfies from the drive cache; it does not issue the `F_FULLFSYNC` barrier IronBus and NATS-sync pay. Kafka's L1 latencies are additionally quantized by its tool's whole-millisecond resolution.\n\u003e - ‡ NATS JetStream has no group-commit (ack-after-coalesced-fsync) mode — a structural gap, marked rather than faked.\n\u003e - IronBus ran its **shipped defaults, including lz4 compression on by default** (`--compression lz4`) with realistic (compressible) payloads; the peers ran their own defaults with `compression.type=none` producer configs. IronBus's codec is part of its shipped configuration and is disclosed rather than disabled.\n\u003e - C1 measures a drain of a pre-filled durable log (throughput only; per-fetch latencies are not comparable across tools and are omitted).\n\u003e - These are single-node numbers. The **cluster** benchmarks are not yet CI-gated ([#636](https://github.com/ELares/IronBus/issues/636)) — no cluster performance claim is made here.\n\n### Matched-conditions: IronBus vs Redpanda, both inside one Linux VM\n\nThe table above shows Redpanda *(in parentheses)* because it is Linux-only and ran in a VM while the other brokers ran natively — a guest `fsync` through a virtual disk is not comparable to a host `F_FULLFSYNC`. To make Redpanda rankable, a companion study puts **both brokers inside the same Linux VM** — same kernel, same ext4-on-virtio disk, same guest loopback, same real `fdatasync` (~200–300 µs) — so the VM is the identical substrate for both. Full methodology, every cell, and the harness: **[docs/benchmarks/REDPANDA_MATCHED_2026_07.md](docs/benchmarks/REDPANDA_MATCHED_2026_07.md)**.\n\nThe single-connection matrix there has IronBus winning P3/1 KiB, both consume rows, and both durable-latency rows (L1 ~7×, real sub-millisecond fsync-ack vs Redpanda's tool-quantized 2 ms), tying P1, and trailing on **P2 group-commit produce** — the gap that motivated the **pipelined sync tier** ([#1040](https://github.com/ELares/IronBus/issues/1040): `fdatasync` decoupled from and overlapped with the append path, self-clocking group commit). That single-connection P2 gap is the IronBus **client**, not its broker: the session drains its parked-produce window each pass, so one connection cannot fill the pipeline — while Redpanda's Kafka client already pipelines 5-in-flight on one connection.\n\nGiven each broker the client concurrency that saturates it (IronBus `bench --producers N`; Redpanda N parallel perf clients), the durable-produce result inverts:\n\n| P2 durable produce | IronBus peak | Redpanda peak | IronBus advantage |\n| --- | ---: | ---: | :-- |\n| 128 B | **1.68 M** (8 conns) | 1.51 M (4 clients) | **1.11×** |\n| 1 KiB | **657 k** (8 conns) | 300 k (4 clients) | **2.19×** |\n\n**Redpanda does not scale with more clients** (its single raft group is saturated by one 5-in-flight client — throughput peaks at 4 and drops at 8); **IronBus scales up and passes Redpanda's peak on both sizes** (1.44×/2.46× at matched 8 clients). The single-connection row remains the honest exception, addressed by the session-side per-connection reorder ring ([#1045](https://github.com/ELares/IronBus/issues/1045)) — reported here unhidden.\n\nThe same study was **re-run on real hardware** (a single AWS **t4g.large** Graviton node, both brokers writing an **EBS gp3** volume — a genuine network-attached durable disk whose `fdatasync` measured ~2.8 ms, ~10× the VM's virtio flush). The result **reproduces and sharpens**: on the expensive real sync, Redpanda peaks at *one* client and falls monotonically, while IronBus climbs to **256 k @128 B / 62 k @1 KiB** — beating Redpanda's peak **1.21× / 1.67×** (2.25× / 2.46× at matched 8 clients). The pricier the durability barrier, the wider IronBus's group-commit margin. See §7 of the study doc.\n\nA subsequent configurable **io-mode** ([#1054](https://github.com/ELares/IronBus/issues/1054); `serve --io-mode auto`, which auto-detects EBS as network-durable) closes the *single-connection* rows too. On EBS the residual `fdatasync` cost is pure extent-metadata journaling, so an `O_DIRECT` write plus a now-free barrier (~1 µs, the full fsync guarantee kept) turns single-connection sync-per-message from a loss into a **1.7–2.5× win**, single-connection group-commit at 1 KiB into a **3.7× win** (128 B lands at par), and drops durable-ack **median** latency to ~1 ms so IronBus now leads L1 at the median as well as the ~100× tail. `buffered` stays the default; `auto`/`direct` are the network-durable opt-in. With it, IronBus **wins or ties every measured row** against Redpanda on real Graviton/EBS — the medians are in §7.\n\n### Performance targets\n\nThe measured table above does not retire the SLO discipline: the provisional marquee **target** (a floor, not a measurement) remains 256-byte messages, a single consumer, durable group-commit `fdatasync`, sustaining at least 60,000 messages per second with p99 latency under 6 ms **on a Raspberry Pi 4**. Every published SLO is a measured floor (the on-device p99 minus a 20 percent margin), recorded with an HdrHistogram against a single monotonic clock, and gated against regression on a rolling baseline. See [docs/SLO.md](docs/SLO.md) and [docs/PERF_LEDGER.md](docs/PERF_LEDGER.md) for the on-device (t4g / edge) measured history, including the ~80x-NATS durable-produce and ~6–8x consume results that predate this cross-broker study.\n\n---\n\n## Quick start: from install to many producers and consumers\n\nIronBus is one static binary that is **both the broker and the CLI**. Below is the whole loop: install it, start the broker on your edge device, then point producers and consumers at it. The local examples use the default address `127.0.0.1:7777`, so you can drop `--addr` when everything runs on the same box.\n\n\u003e Security heads-up: the wire protocol is **not yet encrypted** — TLS is designed but not implemented ([#766](https://github.com/ELares/IronBus/issues/766)), so the wire is plaintext. Authentication **is** implemented (bearer token or Argon2id password, with publish / subscribe / admin scopes), but until TLS lands keep the broker bound to loopback or a trusted LAN behind a firewall or an SSH / WireGuard tunnel. Do not expose it to the open internet.\n\n### 1. Install\n\n**The seamless path (recommended).** One line auto-detects your CPU arch, downloads the matching static `musl` binary from the latest release, and verifies its checksum before installing (fail-closed, no skip-verify override):\n\n```sh\ncurl -fsSL https://raw.githubusercontent.com/ELares/IronBus/main/scripts/install.sh | sh\n```\n\nPrefer to grab the binary yourself? Download the static `musl` binary for your CPU from the [latest release](https://github.com/ELares/IronBus/releases/latest), `chmod +x`, and run it (no runtime dependencies, not even a libc to install):\n\n| Edge CPU | Asset |\n| --- | --- |\n| arm64 / Raspberry Pi 4 / 5 (64-bit) | `ironbus-linux-arm64` |\n| armv7 / Raspberry Pi (32-bit) | `ironbus-linux-armv7` |\n| x86_64 / amd64 | `ironbus-linux-amd64` |\n\nEvery push to main publishes a fresh `YYYY.MMDD.N` build (calendar-versioned, the three static binaries plus a consolidated `SHA256SUMS` and a Sigstore provenance attestation), so `releases/latest` and the installer always resolve to the newest build. See [docs/DISTRIBUTION.md](docs/DISTRIBUTION.md) for every channel.\n\n**Prefer a container?** Every build also publishes a multi-arch (amd64 / arm64 / armv7) distroless image to `ghcr.io/elares/ironbus`, so you can pull and run without installing anything (mind the loopback / security note above):\n\n```sh\ndocker pull ghcr.io/elares/ironbus:latest\ndocker run --rm -v ironbus-data:/var/lib/ironbus -p 127.0.0.1:7777:7777 \\\n  ghcr.io/elares/ironbus:latest serve --data-dir /var/lib/ironbus\n```\n\n**Build from source** (the developer / alternative path, on any host with a Rust toolchain):\n\n```sh\ngit clone https://github.com/ELares/IronBus.git\ncd IronBus\ncargo build --release\n# the single binary is now at target/release/ironbus\n```\n\nFor an **edge device** without network access to the release, cross-compile the one static `musl` binary and copy it over:\n\n```sh\nrustup target add aarch64-unknown-linux-musl   # or armv7-unknown-linux-musleabihf, x86_64-unknown-linux-musl\ncargo build --release --target aarch64-unknown-linux-musl\nscp target/aarch64-unknown-linux-musl/release/ironbus pi@edge-device:/usr/local/bin/ironbus\n```\n\n### 2. Start the broker on the edge\n\nThe only required flag is `--data-dir` (the durable log, the consumer cursors, and the dead-letter sink all live there). Use the `edge-tiny` profile for a small-RAM, flash-gentle node:\n\n```sh\nironbus serve --data-dir /var/lib/ironbus --profile edge-tiny\n```\n\n- `--profile edge-tiny` selects the small-RAM preset (8 MiB segments, tiny credits, 32 connections) plus a **64 MiB RAM ceiling that refuses to boot if the configured caps cannot fit**, so the broker can never surprise you by growing past its budget. `balanced` (the default) and `throughput` are the other two presets; any env var or flag still overrides an individual knob, and `--config \u003cfile\u003e` slots a strictly-validated TOML file into the precedence chain (see the [config feature](#config-file-profiles-and-hot-reload) and [docs/CONFIG.md](docs/CONFIG.md)).\n- By default the broker binds **loopback only** (`127.0.0.1:7777`) and acknowledges a write **only after `fdatasync`**, so a power cut loses zero acknowledged messages. To let producers and consumers on **other machines** reach it, bind the device's address (mind the security note above):\n\n  ```sh\n  ironbus serve --data-dir /var/lib/ironbus --profile edge-tiny --addr 0.0.0.0:7777\n  ```\n\n- Optional health and metrics: add `--health-addr 127.0.0.1:9090` to expose `GET /healthz`, `/readyz`, and `/metrics`.\n- `Ctrl-C` (or `SIGINT` / `SIGTERM`) stops gracefully: it flushes every consumer cursor and exits cleanly, and a restart resumes from the durable log. `SIGHUP` (or `systemctl reload`) re-reads `--config` and applies the live-reloadable subset (the consumer-safe retention bounds + the disk-full policy) without stopping the broker (#380); a change to a restart-required key is reported but needs a restart. Mind that the unit ships `Restart=on-failure`: a clean stop (`SIGTERM`) stays down until you `systemctl start ironbus` again. For an always-on node, run it under systemd (the `.deb` ships a ready unit, so `sudo systemctl enable --now ironbus` is all you need once it is installed).\n\n### 3. Producers: one, or many\n\nEach stream is **one durable, totally ordered log** (a single-stream broker is the default). Any number of producers append to it; the order is the order the broker fsynced them.\n\n```sh\n# Publish one message. It prints the durable offset once the record is fsynced\n# (a printed offset means the message is on disk).\nironbus pub 'hello edge'\n\n# Attach a key (keys drive key-shared ordering on the consumer side).\nironbus pub --key sensor-12 '{\"temp\":21.4}'\n\n# Take the payload from a pipeline (stdin) instead of an argument.\nread_sensor | ironbus pub --key sensor-12\n```\n\n**Many producers** is just running `ironbus pub` from as many processes or hosts as you like; each opens its own connection and the broker serializes them all into the single ordered log. A quick local burst:\n\n```sh\nfor i in $(seq 1 1000); do ironbus pub \"event-$i\"; done\n```\n\n(For a long-lived, high-rate producer, link the [`ironbus-client` Rust crate](#client-sdks) or the [Go SDK](#go) instead of forking a process per message.)\n\n### 4. Consumers: one, or many\n\nA consumer joins a named **work-group**, fetches messages, and disposes of each: `--ack` (commit, never redelivered), `--nack` (redeliver later), or `--term` (drop). Delivery is at-least-once, so an un-acked message redelivers after its visibility timeout.\n\n```sh\n# Read up to 10 from the \"orders\" group and commit them.\nironbus sub --group orders --max 10 --ack\n```\n\nEach message prints as `#\u003coffset\u003e gen=\u003ctoken\u003e key=\u003ckey\u003e payload=\u003cpayload\u003e`, followed by `fetched \u003cn\u003e message(s)`. Omit the disposition to **peek** (print without committing; the messages redeliver after the timeout):\n\n```sh\nironbus sub --group orders --max 5\n```\n\n**Many consumers** is where the work-group model matters — see [Work-groups](#work-groups-competing--key-shared--broadcast) in the feature tour for the three patterns (competing, key-shared, broadcast).\n\n### 5. Inspect the data directly (no running broker)\n\nBecause the durable log is just files, you can decode it with the broker stopped:\n\n```sh\nironbus peek  --data-dir /var/lib/ironbus   # a bounded window of durable records\nironbus dump  --data-dir /var/lib/ironbus   # every durable record\nironbus scrub --data-dir /var/lib/ironbus   # read-only integrity scan that reports any corruption\n```\n\nFor every flag, default, and exit code, see the [CLI reference (docs/CLI.md)](docs/CLI.md); for a longer narrative walkthrough see [docs/USAGE.md](docs/USAGE.md).\n\n---\n\n## Feature tour\n\nEverything below is **shipped and wired** — every command in this tour was verified against the current binary. Each subsection names its scope edge where one exists. (What is *not* built is consolidated in [What is NOT shipped](#what-is-not-shipped-the-roadmap-and-the-non-goals).)\n\n### The durable ordered log\n\nOne crash-safe append-only log per instance — `fdatasync` before ack, so a printed offset means the record is on disk — consumed by many consumers at-least-once.\n\n```sh\nironbus serve --data-dir /var/lib/ironbus\nironbus pub 'hello edge'\nironbus sub --group orders --max 10 --ack\n```\n\n### Durability levels\n\nThe operator picks the ack/durability tradeoff per broker. `sync` (fsync-before-ack, zero acked loss) is the default; the relaxed levels (`interval`, `async`, `none`) are strictly opt-in and gated behind an explicit `--async-loss-ack` consent flag so a config typo can never silently weaken the contract. See [docs/DURABILITY.md](docs/DURABILITY.md).\n\n```sh\nironbus serve --data-dir /var/lib/ironbus --durability-level sync\n```\n\n### Multi-stream, subjects, and wildcards\n\nN named independent logs plus subject routing with `*` / `\u003e` wildcards, wired end-to-end (engine → wire frames → CLI). An unbound subject is a typed, fail-closed reject — never a silent drop. Scope edge: named-stream consume parity (DLQ / key-shared / Tier-S / metrics on named streams) is in progress ([#681](https://github.com/ELares/IronBus/issues/681)).\n\n```sh\nironbus stream create events --data-dir /var/lib/ironbus\nironbus stream bind 'sensors.\u003e' events --data-dir /var/lib/ironbus\nironbus stream ls --data-dir /var/lib/ironbus\n```\n\n### Work-groups: competing / key-shared / broadcast\n\nSQS-style competing consumers by default, per-key ordered fan-out (`--key-shared-group`), or a group-of-one broadcast tap (`--broadcast-group`) — all three over one ordered log with durable cursors, pickable per group at serve time.\n\n```sh\nironbus serve --data-dir /var/lib/ironbus --key-shared-group orders --broadcast-group audit\n# competing (default): run N of these and each gets a disjoint slice\nironbus sub --group workers --max 100 --ack\n# broadcast: observe everything, then commit the cursor in one move\nironbus sub --group audit --max 100\nironbus cumulative-ack --group audit --up-to 5000\n```\n\n### Tier-S streaming consume (consumer-managed offsets)\n\nThe Kafka-style high-throughput consume tier: zero-copy `DeliverBatch` delivery over a resident sparse byte index, with consumer-managed offsets and cumulative commits — the 1.65 M msg/s row in the [benchmarks](#benchmarks) (4.2x NATS). The lease-rich work-queue tier (Tier-W) stays the default for per-message semantics.\n\n```sh\nironbus bench --count 100000 --mode subscribe --consume-tier streaming\n```\n\n### Idempotent producer (effectively-once)\n\nPID + epoch + sequence dedup (`Fresh` / `Duplicate` / `Fenced` / `OutOfOrder`) that survives a broker restart and an arbitrarily long offline gap. Off by default; opt-in per producer.\n\n```sh\nironbus serve --data-dir /var/lib/ironbus --dedup-window-ms 120000 --dedup-max-ids 100000\n```\n\n```rust\n// Rust: produce_dedup returns the typed ack (Fresh / Duplicate / Fenced / OutOfOrder).\nlet ack = client.produce_dedup(\u0026message)?;\n```\n\n### Transactional half-messages\n\nRocketMQ-style two-phase produce: prepare a half message (invisible to consumers), run your local transaction, then commit or roll back — with a crash-safe broker back-check (`TxnCheck`) so a producer that dies mid-transaction never wedges the log. Client-driven via the Rust API (`prepare` / `commit` / `rollback` / `transact`, plus `register_transaction_listener` for the back-check).\n\n```rust\nlet txn = client.prepare(\"events\", \u0026message)?;\n// ... local work ...\nlet offset = client.commit(\u0026txn)?;   // or client.rollback(\u0026txn)?\n```\n\n### Fire-and-forget fast path\n\nAn optional un-acked produce path with its own rate/byte budget, for telemetry-style throughput where at-most-once is acceptable.\n\n```sh\nironbus bench --count 100000 --mode publish --fire-and-forget\n```\n\n### Multi-node clustering and replication\n\nSee the dedicated [Clustering](#clustering) section — a metadata Raft group, ISR pull replication, quorum-fsync acks, and divergence self-heal, behind two flags.\n\n### Security: authentication, pre-auth DoS defenses, graceful drain\n\nSee the dedicated [Security](#security) section.\n\n### Observability: Prometheus, health, live views\n\nBuilt-in `/metrics` (no sidecar — NATS needs one), split `/healthz` + `/readyz` liveness/readiness, latency histograms and recovery counters, a live TUI, operator reports, and an optional read-only `/admin` snapshot.\n\n```sh\nironbus serve --data-dir /var/lib/ironbus --health-addr 127.0.0.1:9090 --enable-admin\nironbus top --health-addr 127.0.0.1:9090\nironbus report groups --health-addr 127.0.0.1:9090\nironbus server check --health-addr 127.0.0.1:9090   # Nagios-style one-liner, frozen exit codes\n```\n\nScope edge: OpenTelemetry tracing is the one observability piece not built ([#770](https://github.com/ELares/IronBus/issues/770)).\n\n### Recovery as a feature: verify / repair / backup / restore\n\nThe recovery tooling NATS lacks: an offline fsck (`verify`), an explicit mutating repair (quarantine-by-copy + longest-valid-prefix truncate, under the exclusive data-dir lock, `--apply --force` required), and point-consistent backup/restore with a CRC'd manifest and fail-closed validation.\n\n```sh\nironbus verify  --data-dir /var/lib/ironbus\nironbus repair  --data-dir /var/lib/ironbus                 # read-only plan\nironbus repair  --data-dir /var/lib/ironbus --apply --force # the actual repair\nironbus backup  --data-dir /var/lib/ironbus --out /backups/ib-2026-07-01\nironbus restore --from /backups/ib-2026-07-01 --data-dir /var/lib/ironbus\n```\n\n### Bounded, reported, fail-closed corruption recovery\n\nLongest-valid-prefix startup recovery, stop-at-first-bad-frame, per-event loss caps (≤1 segment or 64 MiB, ≤1% of durable bytes), quarantine-by-copy, and a typed LossReport (log line + report file + Prometheus counter). Exceeding a cap freezes the log read-only rather than guessing. See [Resilience](#resilience-designed-for-failure-first).\n\n```sh\nironbus scrub --data-dir /var/lib/ironbus   # read-only: report what recovery would do\n```\n\n### Dead-letter queue + crash-safe redrive\n\nPoison messages past `--max-deliver` (default 5) are dead-lettered into a durable DLQ sink you can inspect and idempotently re-inject onto the main log — a feature NATS lacks.\n\n```sh\nironbus dlq ls --data-dir /var/lib/ironbus\nironbus dlq peek --limit 20 --data-dir /var/lib/ironbus\nironbus dlq redrive --data-dir /var/lib/ironbus\n```\n\n### Backpressure and credit-based flow control\n\nAn auto-tuning per-connection credit window (message count + byte budget), per-group max-in-flight, CoDel sojourn control, and spill-to-disk-then-shed — producers are never blocked by default. See [docs/BACKPRESSURE.md](docs/BACKPRESSURE.md).\n\n```sh\nironbus serve --data-dir /var/lib/ironbus --consumer-credit 2048 --consumer-credit-bytes 8388608 --max-in-flight 1024\n```\n\n### Retention: size / age / count\n\nThree composable, consumer-safe bounds (each 0 = off) reap whole old **fully-consumed sealed** segments — never below the slowest consumer's committed offset, never the active segment. (Flag values are raw numbers; the config file additionally accepts `KiB`/`MiB`/`GiB` units.)\n\n```sh\nironbus serve --data-dir /var/lib/ironbus \\\n  --max-retained-bytes 1073741824 --max-age-ms 604800000 --max-messages 1000000\n```\n\n### Compression: lz4 by default, zstd opt-in\n\nPer-record, self-describing lz4 compression on the default path (pure Rust `lz4_flex`), transparent to every client — deliveries are decompressed before hand-off. zstd + trained dictionaries are an **opt-in build feature** (`--features zstd`) only, never on the default path (ADR-0003).\n\n```sh\nironbus serve --data-dir /var/lib/ironbus --compression lz4   # the default, shown explicitly\n```\n\n### In-memory (ephemeral) storage backend\n\nThe same engine over an in-memory filesystem — no files, no fsync, explicitly no restart durability — for hot-path fan-out and test rigs. It refuses to boot without the explicit loss consent **and** a RAM cap (`0 = unlimited` would OOM the device, so it is rejected).\n\n```sh\nironbus serve --storage memory --ephemeral-loss-ack --max-total-bytes 268435456\n```\n\n### Checkpoints\n\nCrash-safe dual-slot consumer-cursor checkpointing at a configurable interval, so work-groups resume exactly where they committed after a restart or power cut.\n\n```sh\nironbus serve --data-dir /var/lib/ironbus --checkpoint-interval 1000\n```\n\n### Offline data inspection: peek / dump\n\nDecode the durable log and the DLQ straight from the data directory, with no broker running, bounded to one segment of memory at a time. `dump --raw` shows the on-disk frame, compression descriptor included.\n\n```sh\nironbus peek --data-dir /var/lib/ironbus --limit 20\nironbus dump --data-dir /var/lib/ironbus --json     # NDJSON, one record per line\nironbus dump --data-dir /var/lib/ironbus --dlq      # the dead-letter sink\n```\n\n### Group and stream management + offline consumer reset\n\nOffline management verbs over a stopped broker's data dir (they take the same exclusive lock `serve` holds, so they can never race a live writer): list/inspect/reset/purge/rm work-groups and streams, rewrite a durable cursor, redrive the DLQ. Destructive verbs refuse without `--force`.\n\n```sh\nironbus group ls --data-dir /var/lib/ironbus\nironbus group reset orders --data-dir /var/lib/ironbus --to earliest\nironbus admin consumer-reset --data-dir /var/lib/ironbus --group orders --to latest\n```\n\n### Config file, profiles, and hot reload\n\nA strictly-validated TOML config (`--config`; an unknown key is a **fatal error with a did-you-mean suggestion**), versioned `edge-tiny` / `balanced` / `throughput` profiles, `flag \u003e env \u003e file \u003e default` precedence, and SIGHUP live-reload of the consumer-safe subset. Durations and byte sizes in the file carry required units (`8MiB`, `500ms`). See [docs/CONFIG.md](docs/CONFIG.md).\n\n```sh\nironbus serve --data-dir /var/lib/ironbus --config /etc/ironbus/ironbus.toml --profile edge-tiny\n```\n\n### CLI ergonomics: contexts, JSON envelope, completion, cheat sheet\n\nNamed connection profiles (address + token + data-dir; the token file must be `chmod 0600` — a group- or world-readable secret is refused), a **frozen** machine-readable `ironbus.cli.v1` `--json` envelope with frozen exit codes (0 clean, 1 usage, 2 not found, 3 handled corruption, 4 corrupt data, 5 broker unreachable, 70 internal), shell completion, and a cheat sheet.\n\n```sh\nironbus context create edge --addr 10.0.0.1:7777 --token-file /etc/ironbus/token --use\nironbus --json verify --data-dir /var/lib/ironbus\nironbus completion zsh\nironbus cheat\n```\n\n### Zero-downtime upgrade / rollback / migrate\n\nAn atomic staged binary swap (a power cut mid-upgrade leaves the old or the new binary, never a truncated one) with one-command rollback, a systemd-consulted failed-start counter, and a never-silent on-disk format migration gate.\n\n```sh\nironbus upgrade --new-binary ./ironbus-new --dest /usr/local/bin/ironbus\nironbus rollback --dest /usr/local/bin/ironbus\nironbus migrate --data-dir /var/lib/ironbus\n```\n\n### Load generator: bench\n\nA production-safe benchmark: by default it spawns its own **isolated** broker over a fresh throwaway data dir (it refuses to target a live broker without an explicit consent flag) and reports throughput, p50/p99/p999 latency, produce-ack RTT percentiles, and fsync cost. This is the driver the [cross-broker study](docs/benchmarks/CROSS_BROKER_2026_07.md) used.\n\n```sh\nironbus bench --duration 30 --mode round-trip --payload-bytes 256 --pubwindow 64 --json\n```\n\n---\n\n## Client SDKs\n\nThe wire protocol is a small, versioned, length-framed binary protocol ([docs/TRANSPORT.md](docs/TRANSPORT.md)); the official clients speak it natively — no HTTP, no protobuf.\n\n**New to the clients?** [docs/CLIENT_SDKS.md](docs/CLIENT_SDKS.md) is the full getting-started walkthrough for both languages — connect, every produce/consume shape, transactions, subjects, auth, cluster redirects, and a capability matrix across the Rust (sync/async) and Go clients.\n\n### Rust\n\nTwo first-party crates in this workspace, sharing every wire codec and type: [`ironbus-client`](crates/ironbus-client/) (blocking, minimal, edge-first) and [`ironbus-client-async`](crates/ironbus-client-async/) (its tokio twin — same API surface, `.await`ed). Broker-side lz4 compression is transparent in both; auth rides `ClientConfig::credential` (`AuthCredential` bearer/password). Runnable examples live in [`crates/ironbus-client/examples/`](https://github.com/ELares/IronBus/tree/main/crates/ironbus-client/examples).\n\n```rust\nuse ironbus_client::Client;\nuse ironbus_proto::message::PubBody;\n\nlet mut client = Client::connect(\"127.0.0.1:7777\")?;\n\n// Produce: the returned offset means the record is fsync'd durable.\nlet offset = client.produce(\u0026PubBody {\n    flags: 0,\n    timestamp_ms: 0,\n    key: b\"sensor-12\",\n    headers: b\"\",\n    dedup: None,\n    fire_and_forget: false,\n    payload: br#\"{\"temp\":21.4}\"#,\n})?;\n\n// Consume: join a work-group, fetch a batch, ack each message.\nclient.subscribe(\"workers\")?;\nlet fetched = client.fetch(10)?;\nfor m in \u0026fetched.messages {\n    client.ack(m.offset, m.generation)?;\n}\n\n// Named streams + Tier-S streaming consume (consumer-managed offsets).\nclient.declare_stream(\"events\")?;\nlet mut consumer = client.streaming_consumer(\"replay\");\nlet batch = consumer.next_batch()?;\n```\n\nThe async twin is the same call surface with `.await`:\n\n```rust\nuse ironbus_client_async::AsyncClient;\nuse ironbus_client_async::proto::PubBody;\n\nlet mut client = AsyncClient::connect(\"127.0.0.1:7777\").await?;\nlet offset = client\n    .produce(\u0026PubBody {\n        flags: 0,\n        timestamp_ms: 0,\n        key: b\"key\",\n        headers: b\"\",\n        dedup: None,\n        fire_and_forget: false,\n        payload: b\"hello\",\n    })\n    .await?;\nclient.subscribe(\"workers\").await?;\nlet fetched = client.fetch(10).await?;\nfor m in \u0026fetched.messages {\n    client.ack(m.offset, m.generation).await?;\n}\n```\n\nBoth clients are one-connection, request-response FIFO: drive concurrency with one client per thread/task. `produce_to_leader` handles the cluster `NotLeader` redirect transparently.\n\n### Go\n\nThe official Go SDK lives at [`sdk/go`](https://github.com/ELares/IronBus/tree/main/sdk/go) (module `github.com/ELares/IronBus/sdk/go`, [#1021](https://github.com/ELares/IronBus/issues/1021)): the same versioned wire, byte-identical framing, transparent lz4 deliveries, bearer/password auth, contexts on every call. Like the Rust clients, a `*Client` owns one connection and is used from one goroutine. Runnable examples live in [`sdk/go/examples/`](https://github.com/ELares/IronBus/tree/main/sdk/go/examples).\n\n```go\nimport (\n\t\"context\"\n\n\tironbus \"github.com/ELares/IronBus/sdk/go\"\n)\n\nctx := context.Background()\nclient, err := ironbus.Connect(ctx, ironbus.Config{Addr: \"127.0.0.1:7777\"})\nif err != nil { /* ... */ }\ndefer client.Close()\n\n// Produce: the returned offset means the record is fsync'd durable (server ack).\noffset, err := client.Produce(ctx, \u0026ironbus.Message{\n\tKey:     []byte(\"sensor-12\"),\n\tPayload: []byte(`{\"temp\":21.4}`),\n})\n_ = offset\n\n// Consume: join a work-group, fetch a batch, ack each by (offset, generation).\nif err := client.Subscribe(ctx, \"workers\"); err != nil { /* ... */ }\nres, err := client.Fetch(ctx, ironbus.FetchOptions{MaxRecords: 10})\nfor _, m := range res.Messages {\n\t// handle m.Payload ...\n\t_, _ = client.Ack(ctx, m.Offset, m.Generation)\n}\n```\n\n---\n\n## Clustering\n\nA real multi-node cluster ships today, behind two flags — and it degrades to the zero-config single node at n=1:\n\n```sh\n# on each of three nodes (adjust --cluster-id and the addresses):\nironbus serve --data-dir /var/lib/ironbus --cluster-id 1 \\\n  --cluster-peer 1=10.0.0.1:7900 --cluster-peer 2=10.0.0.2:7900 --cluster-peer 3=10.0.0.3:7900\n```\n\nWhat runs behind those flags (V2-C1 through V2-C7, all merged — see [docs/MISSION.md](docs/MISSION.md) §2b):\n\n- **One KRaft-style metadata Raft group** (tikv/raft-rs) owns membership, placement, leadership epochs, and config — and nothing on the hot data path. This is explicitly **not** Raft-per-asset: no per-stream heartbeat storm, no ~2000-asset ceiling, no meta single point of failure.\n- **Kafka-ISR-style per-partition pull replication** of the existing CRC-framed log: followers fetch the leader's already-framed segment byte ranges and re-validate every frame.\n- **The default cluster ack is `C2-fsync`**: the wire `PubAck` is withheld until the record is `fdatasync`'d on a **quorum** — strictly stronger than NATS R3 (quorum page-cache) and Kafka `acks=all` (which trades fsync for replication), affordable because IronBus group-commits one `fdatasync` per batch on leader and followers alike.\n- **Leader-epoch fencing + footer/CRC divergence self-heal**: silent replica drift and minority-corruption faults become bounded, reported, automatically repaired events (the cluster recovery invariants CI1–CI4 extend the single-node I1–I3 discipline). A produce to a non-leader returns a typed `NotLeader` redirect with a leader hint; the clients follow it.\n- **Follower reads** (consume scales with replicas) and **async geo**: mirror/source links, leaf-spoke edge links, gateway federation.\n\nScope edges, stated plainly: replication covers the single default log today (multi-partition replication awaits the #693 partition wiring), and the cluster benchmarks are not yet CI-gated ([#636](https://github.com/ELares/IronBus/issues/636)) — the [benchmark table](#benchmarks) above is single-node only.\n\n---\n\n## Security\n\nSecurity ([#18](https://github.com/ELares/IronBus/issues/18)) is shaped for devices on untrusted networks. What ships today, and what does not:\n\n- **Authentication ships and is enforced**: three mechanisms (bearer token, username + Argon2id password, and mTLS — the latter wired but **inert/fails-closed** until the TLS transport lands) × three independent scopes (**publish, subscribe, admin**), configured via `--auth-config` and managed with `ironbus passwd`:\n\n  ```sh\n  ironbus passwd --auth-config /etc/ironbus/auth.toml --user alice --scopes publish,subscribe\n  ironbus serve --data-dir /var/lib/ironbus --auth-config /etc/ironbus/auth.toml \\\n    --addr 0.0.0.0:7777 --insecure-plaintext-wire\n  ```\n\n- **TLS 1.3 is the designed transport, but it is NOT yet implemented ([#766](https://github.com/ELares/IronBus/issues/766)): the wire is plaintext today.** No TLS stack is linked, and any `--tls-*` material is reserved and refused at startup (the broker will not boot with it set). A **non-loopback bind is therefore an explicit decision**: `--insecure-plaintext-wire` is the loud opt-in for running auth-on-plaintext beyond loopback — for TLS-terminated-upstream, service-mesh, or VPN/WireGuard patterns. Without it, keep the broker on loopback or behind a tunnel.\n- **Encryption at rest is designed but NOT yet built** ([#780](https://github.com/ELares/IronBus/issues/780)) — no AEAD provider is linked; the on-disk format reserves a nonce and an at-rest flag for AES-256-GCM / ChaCha20-Poly1305, but data on disk is unencrypted today.\n- **Fail-closed secret handling**: IronBus refuses to start (and `passwd`/`context` refuse to read) if a secret-bearing file is group- or world-readable — `chmod 0600` your token and password files.\n- **Pre-auth DoS defenses**: half-open connection caps, per-source connection rate limits, and failed-auth lockout, so a handshake flood cannot exhaust a small device; plus graceful drain (SIGTERM flips readiness, lets in-flight work finish) and a secret-free audit stream.\n\n---\n\n## Resilience: designed for failure first\n\nResilience is the top tenet, so failure is planned, not patched. Every subsystem carries a failure-mode and mitigation matrix, aggregated into a [consolidated FMEA (#129)](https://github.com/ELares/IronBus/issues/129); the invariants every subsystem must uphold are tracked in [shared invariants and glossary (#131)](https://github.com/ELares/IronBus/issues/131):\n\n- No acknowledged write is ever lost below its configured durability level.\n- Recovery never reads past a torn or partially written tail record.\n- Loss from a corruption skip is bounded (at most one segment or 64 MiB per event, at most 1 percent of durable bytes per recovery) and is always reported, never silent and never partial within a record.\n- The log preserves a single total durable order.\n\nConcretely, IronBus treats a failed `fsync` as fatal and freezes the writer read-only (the PostgreSQL fsyncgate lesson), checksums every record so a flipped bit on an SD card is caught on read, quarantines unreadable segments by copy rather than move into a capped store, and resynchronizes to the next valid record boundary so one bad region does not poison the rest of the log. And recovery is not only automatic — it is a **first-class operator feature**: `verify` (offline fsck), `repair` (explicit, locked, plan-first), `backup` / `restore` (point-consistent, manifest-validated), and `scrub` (read-only scan) make every recovery path inspectable and drivable on demand (see the [feature tour](#recovery-as-a-feature-verify--repair--backup--restore)).\n\nThese claims are not taken on faith. Verification ([#21](https://github.com/ELares/IronBus/issues/21)) is built around a bespoke, in-tree deterministic simulation (a single seeded PRNG threaded through every IO, clock, and scheduling decision) so a power cut can be replayed bit for bit. Five crash classes are hard release gates: `kill -9`, simulated power cut with write reordering, a one-shot `fsync` error, and block-layer fault injection for dropped writes and per-block read errors. Every pull request runs a 256-seed sweep, the record and segment parsers are continuously fuzzed, a tiered corpus of deliberately corrupted files is asserted on, and a sim-versus-real conformance gate on a reference edge device keeps the simulation honest.\n\n---\n\n## The CLI\n\nThe same binary that runs the broker is the CLI, in the spirit of the NATS CLI but with a real view into the stored data. The shipped verbs, grouped:\n\n- **Interact**: `pub`, `sub`, `cumulative-ack`, and `bench` (the production-safe load generator).\n- **Observe**: `top` (live TUI or offline file-derived view), `report groups|streams|storage|recovery|connections`, `server info|healthz|ready|check`, and the read-only `admin` snapshot.\n- **Recover**: `verify`, `repair`, `backup`, `restore`, `scrub` — the [recovery-as-a-feature](#recovery-as-a-feature-verify--repair--backup--restore) set.\n- **Inspect offline**: `peek`, `dump` (`--json`, `--dlq`, `--raw`), straight from the data directory with no broker running.\n- **Manage**: `group ls|info|reset|purge|rm`, `stream ls|info|create|bind|purge|rm`, `dlq ls|peek|redrive`, `admin consumer-reset|dlq-redrive`, `passwd`.\n- **Operate**: `upgrade`, `rollback`, `record-start`, `migrate`.\n- **Ergonomics**: `context` (named connection profiles), `completion`, `cheat`, and the global `--json` flag — one frozen `ironbus.cli.v1` envelope per command, with frozen exit codes, safe to script against forever.\n\nEvery command speaks human-readable output by default and `--json` for machines. The full contract is [docs/CLI.md](docs/CLI.md) and [docs/CLI_CONTRACT.md](docs/CLI_CONTRACT.md).\n\n---\n\n## How it works\n\nThe data path is deliberately short. A producer sends a record. A single append actor frames and checksums it, appends it to the active log segment, group-commits an `fdatasync`, and only then acknowledges. The active segment **is** the write-ahead log: there is no separate WAL file to keep in sync. Sealed segments are served to many consumers through a derived offset index that is rebuilt from the log on startup. Every record on disk carries a CRC32C, so corruption is always caught, and every recovery path is bounded and reported.\n\n```\nproducer ─▶ wire protocol ─▶ ring buffer + credit-based backpressure\n                                   │  single append actor, monotonic u64 offsets\n                                   ▼\n              active log segment, CRC32C framed  (this IS the WAL)\n                                   │  group-commit fdatasync, then ack\n                                   ▼\n              sealed segments  +  derived offset / time index\n                                   │\n   many consumers ◀─ leases, acks, redelivery, DLQ ─▶ dead-letter queue\n                                   │\n   corruption found ─▶ skip record / quarantine segment ─▶ bounded, reported loss\n```\n\n### Subsystems (each is a design issue)\n\n| Area | Issue | What it covers |\n| --- | --- | --- |\n| Queue semantics | [#3](https://github.com/ELares/IronBus/issues/3) | Single ordered log, many consumers, at-least-once, ordering guarantees, opt-in dedup |\n| Storage engine | [#4](https://github.com/ELares/IronBus/issues/4) | Append-only segmented log (the active segment is the WAL), derived indexes, directory layout |\n| Record format | [#5](https://github.com/ELares/IronBus/issues/5) | On-disk byte framing, CRC32C, record-aligned layout, torn-write detection, versioning |\n| Durability | [#6](https://github.com/ELares/IronBus/issues/6) | `fsync` strategy, group commit, ack contract, power-loss guarantees |\n| Crash recovery | [#7](https://github.com/ELares/IronBus/issues/7) | Startup replay, torn-tail truncation, index rebuild, longest-valid-prefix |\n| Corruption skip | [#8](https://github.com/ELares/IronBus/issues/8) | Detect, skip, quarantine, resync, bounded and reported loss |\n| Consumer model | [#9](https://github.com/ELares/IronBus/issues/9) | Cursors, groups, acks, redelivery, visibility timeout, dead-letter queue |\n| Backpressure | [#10](https://github.com/ELares/IronBus/issues/10) | Credit-based flow control, spill-to-disk, overflow policy, load shedding |\n| Wire protocol | [#11](https://github.com/ELares/IronBus/issues/11) | Length-framed binary protocol, verbs, capability negotiation |\n| Compression | [#12](https://github.com/ELares/IronBus/issues/12) | lz4_flex default (pure Rust), per-record self-describing descriptor; zstd and trained dictionaries opt-in behind the `zstd` feature, never on the default path (#139, ADR-0003) |\n| Retention | [#13](https://github.com/ELares/IronBus/issues/13) | Time, size, and count retention, whole-segment deletion, lifecycle |\n| Configuration | [#14](https://github.com/ELares/IronBus/issues/14) | Layered config, hot reload, profiles, safe zero-config defaults |\n| CLI | [#15](https://github.com/ELares/IronBus/issues/15) | pub, sub, bench, offline data inspection, scrub, live TUI, management verbs |\n| Observability | [#16](https://github.com/ELares/IronBus/issues/16) | Prometheus metrics, tracing, health, structured introspection |\n| Build and distribution | [#17](https://github.com/ELares/IronBus/issues/17) | Single static binary, cross-compilation, packaging, supply chain |\n| Security | [#18](https://github.com/ELares/IronBus/issues/18) | AuthN and authZ, TLS, encryption at rest, edge threat model |\n| Performance | [#19](https://github.com/ELares/IronBus/issues/19) | SLO targets, benchmark methodology, regression gating |\n| Edge constraints | [#20](https://github.com/ELares/IronBus/issues/20) | Flash wear, RAM ceilings, fsync cost, brownout behavior |\n| Verification | [#21](https://github.com/ELares/IronBus/issues/21) | Crash injection, fuzzing, property tests, deterministic simulation |\n| Governance | [#22](https://github.com/ELares/IronBus/issues/22) | License, repo structure, RFC process, versioning |\n\n---\n\n## Key decisions already committed\n\nA fresh-eyes second pass over every issue resolved over one hundred design questions across the 22 subsystem issues. The headline decisions that define the product:\n\n| Question | Decision |\n| --- | --- |\n| Logical scope | Multi-stream + subjects + wildcards over one node today; partitions are the remaining v2 item (#693, see [docs/MISSION.md](docs/MISSION.md)). A KV store and an object store are non-goals (IronBus stays a pure message bus). |\n| Delivery contract | At-least-once, pull-based in v1. SQS-style visibility-timeout leases (default 30s, hard cap 5 minutes), persisted redelivery count, default max-deliver 5, then dead-letter queue. |\n| Ordering | Total durable order of the log. Per-group at-least-once, not per-group strict in-order delivery. Exactly-once is a non-goal. |\n| Storage model | Log-is-WAL: a publish is one framed, checksummed, record-aligned append to the active segment, and that append is the durable record. No separate WAL file. The offset index is derived and rebuildable. |\n| Durability default | Group-committed `fdatasync` of the active log before ack. The commit thread syncs whatever appends arrived during the previous sync (cap 1 MiB, no proactive linger by default). Levels (`--durability-level`): `sync` (default, ack-after-`fdatasync`, I2, zero acked loss), `interval` (bounded by the flush window), `async`/`none` (relaxed, gated behind `--async-loss-ack`). |\n| Checksum | CRC32C (Castagnoli) on every record, using the hardware instruction with a software fallback. Payloads over 64 KiB carry a second independent xxh3-64 checksum. CRC32C gates resync. |\n| Record and segment sizes | Default max record 16 MiB (hard cap, configurable up to 1 GiB), 64 MiB segments (8 MiB on the edge profile). A record never spans two segments. |\n| Backpressure | Credit-based pull (auto-tuning window, default ceiling 2048 messages / 8 MiB in-flight per connection). Durable topics spill to disk then shed (drop_new past the spill cap, always reported); telemetry topics drop_oldest. `block` is opt-in only, never a default. CoDel sojourn control plus a hard depth backstop. |\n| Dedup | Off by default. Opt-in per-producer window (100,000 ids or 2 minutes). An optional stable producer-id and epoch persists the high-watermark so dedup survives a restart and an arbitrarily long offline gap. |\n| Bounded loss report | After any skip, report (records_lost, bytes_lost, segments_affected) plus the offset range and a reason enum, via a log line, a recovery report file, and a Prometheus counter. Loss is capped at one segment or 64 MiB per event and 1 percent of durable bytes per recovery; exceeding either freezes the log read-only and alerts. |\n| Runtime | tokio (multi-threaded), with the durability commit on a dedicated thread. io_uring is a deferred, feature-flagged, Linux 5.10 and newer optimization, never the foundation, to protect the Cross Platform tenet. |\n| Targets | First-class: aarch64, x86_64, armv7 musl static binaries, kernel floor Linux 4.19. Best-effort, CI-built: macOS. Windows is a non-goal for v1. |\n| Replication | A real multi-node cluster ships today (metadata Raft + leader/follower ISR pull replication, `fsync`'d-on-a-quorum ack default, leader-epoch fencing, divergence self-heal) behind `--cluster-id / --cluster-peer`, preserving the single-node durability/recovery/edge guarantees. Default-log replication today; multi-partition awaits #693 and the cluster benchmarks are not yet CI-gated (#636) — see [docs/MISSION.md](docs/MISSION.md). |\n| License | Dual `MIT OR Apache-2.0` across the whole workspace. |\n| MSRV | Rust 1.78, may rise only in a minor release, new floor always at least 6 months old. |\n\nThe full, immutable record of these decisions lives in the [ADR index (#130)](https://github.com/ELares/IronBus/issues/130) and as ADR files as they are ratified.\n\n---\n\n## What is NOT shipped: the roadmap, and the non-goals\n\nThe honesty section, mirroring [docs/MISSION.md](docs/MISSION.md) §2c — everything here is genuinely not built and is marked as such everywhere it appears:\n\n| Not built yet | Tracking | Note |\n| --- | --- | --- |\n| Partitions through the engine | [#693](https://github.com/ELares/IronBus/issues/693) | Partition math exists in storage only; not wired through engine/wire/CLI. Cluster replication covers the single default log until this lands. |\n| TLS 1.3 / mTLS transport | [#766](https://github.com/ELares/IronBus/issues/766) | The wire is plaintext today; `--tls-*` material is refused at startup; the mTLS auth mechanism is inert (fails closed) until this lands. |\n| Encryption at rest | [#780](https://github.com/ELares/IronBus/issues/780) | No AEAD provider linked; the on-disk format reserves the fields. |\n| Priorities / delayed messages / request-reply RPC | [#553](https://github.com/ELares/IronBus/issues/553) / [#555](https://github.com/ELares/IronBus/issues/555) / [#764](https://github.com/ELares/IronBus/issues/764) | The V2-M4 routing-richness set. (Per-message TTL enforcement exists in the engine; its operator surface is open, [#710](https://github.com/ELares/IronBus/issues/710).) |\n| OpenTelemetry tracing | [#770](https://github.com/ELares/IronBus/issues/770) | The one observability gap — metrics, health, histograms, and recovery counters did ship. |\n| Named-stream consume parity | [#681](https://github.com/ELares/IronBus/issues/681) | DLQ / key-shared / Tier-S / metrics on **named** streams — in progress; the default log has it all. |\n| Tiered storage (post-1.0) | [#643](https://github.com/ELares/IronBus/issues/643) | Offloading cold sealed segments to object storage — core log infrastructure, distinct from an object store. |\n| CI-gated cluster benchmarks | [#636](https://github.com/ELares/IronBus/issues/636) | The cluster code ships; the head-to-head cluster benches and the 3-node edge-fit run are open. |\n\nAnd the **permanent non-goals**, deliberately never built: exactly-once (at-least-once is the contract; effectively-once via the idempotent producer), a KV store, an object store, a Kafka wire-protocol clone, and Windows in v1. zstd stays an opt-in build feature, never the default codec.\n\n---\n\n## How this repository is organized\n\nThe design came first and still anchors the code: issues [#3](https://github.com/ELares/IronBus/issues/3)–[#22](https://github.com/ELares/IronBus/issues/22) are the 20 vetted subsystem designs, [#1](https://github.com/ELares/IronBus/issues/1) is the vision EPIC and index, and [#2](https://github.com/ELares/IronBus/issues/2) is the comparative prior-art analysis. The implementation now lives in a small Rust workspace built from those designs:\n\n- `crates/ironbus-core` — I/O-free types and logic; `crates/ironbus-storage` — the segmented log; `crates/ironbus-proto` — the frozen wire codecs; `crates/ironbus-server` — the broker; `crates/ironbus-client` / `crates/ironbus-client-async` — the Rust clients; `crates/ironbus-cli` — the CLI; `crates/ironbus-bench` — bench internals; `sdk/go` — the Go SDK.\n- `docs/` — the contracts (durability, recovery, invariants, config, CLI, transport, mission) plus [docs/benchmarks/](docs/benchmarks/) for the measured studies; `fuzz/` — the fuzz targets; `scripts/ci/` — the CI gates (including the docs link-integrity checks).\n- Meta issues tie it together: [consolidated FMEA (#129)](https://github.com/ELares/IronBus/issues/129), [ADR index (#130)](https://github.com/ELares/IronBus/issues/130), [invariants and glossary (#131)](https://github.com/ELares/IronBus/issues/131), [compatibility and versioning policy (#132)](https://github.com/ELares/IronBus/issues/132), and the [golden-path acceptance scenario (#133)](https://github.com/ELares/IronBus/issues/133).\n\nBrowse by [milestone](https://github.com/ELares/IronBus/milestones) or by [label](https://github.com/ELares/IronBus/labels) (for example `area:storage`, `area:recovery`, `area:backpressure`, or `sub-issue`).\n\n---\n\n## Project status and how to get involved\n\nThe architecture was vetted in the design issues before code began, and the implementation is now broad: the durable single-node broker, multi-stream + subjects + wildcards, the multi-node cluster, idempotent + transactional produce, auth, and the full observability/recovery tooling are merged, each landed as a small, reviewed, CI-gated pull request. The best ways to help right now: challenge the design decisions (every decision states the alternative it rejected and why, so disagreement is easy to ground), reproduce the [benchmarks](#benchmarks) (the harness is in the repo), and kick the tires on the [feature tour](#feature-tour) against your own edge hardware.\n\nReleases are reproducible, signed (cosign keyless plus an offline signature), and shipped with an embedded SBOM and a fail-closed verifying installer. Contribution, security, and code-of-conduct policies are defined in the [governance issue (#22)](https://github.com/ELares/IronBus/issues/22) and [CONTRIBUTING.md](CONTRIBUTING.md), including a Developer Certificate of Origin sign-off, a Contributor Covenant code of conduct, and private security disclosure through GitHub Security Advisories.\n\n---\n\n## License\n\nIronBus is dual-licensed under your choice of [MIT](https://opensource.org/license/mit) or [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0), as decided in the [governance issue (#22)](https://github.com/ELares/IronBus/issues/22). See [LICENSE-MIT](LICENSE-MIT) and [LICENSE-APACHE](LICENSE-APACHE).\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Felares%2Fironbus","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Felares%2Fironbus","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Felares%2Fironbus/lists"}