Skip to content

Ironflow v0.31.0

Ironflow v0.31.0 fixes a pub/sub consumer group that never redelivered: ACK_MODE_AUTO resolved to a JetStream policy that tracks no ack state, so max_redeliveries, the redelivery delay and both backpressure modes were quietly inert. Subscriptions can now resume from a stream sequence instead of guessing with a replay count, and the Python SDK gains typed ConnectRPC clients across 44 methods.

  • Existing pub/sub consumer groups are rebuilt on first activation after upgrade. ACK_MODE_AUTO now maps to AckExplicitPolicy, which is what the dispatcher beneath it always assumed. NATS refuses an ack-policy change in place (err_code=10012), so each pre-existing durable is recreated when that error surfaces — once per durable, driven by the error rather than an upfront probe, so a healthy group pays no round-trip. Position is preserved via AckFloor.Stream + 1. Groups that silently dropped failed messages will now redeliver them (#1849, #1853)
  • Resume a fan-out subscription from a stream sequence. SubscribeOptions.start_after_sequence is a position, not a count — replay cannot express “the ones I have not seen”, so reconnecting with it either repeats work or skips events depending on how long the disconnect lasted. Mutually exclusive with replay (INVALID_ARGUMENT rather than a silent merge) and rejected alongside consumer_group, whose position the server already owns. Delivery stays at-least-once (#1848, #1851)
  • Typed ConnectRPC clients in the Python SDKIronflowRPC and AsyncIronflowRPC, 44 capability methods across 8 namespaces, 40 unary and 4 server streams, generated from protobuf by Buf via connect-py. A capability ledger classifies all 83 served RPCs and is gated in CI, so a newly served RPC fails the build until it is classified. The REST client is unchanged. The Python SDK is still source-only — it is not published to PyPI (#1781, #1829)
  • The Python SDK retries unary RPCs the protos mark side-effect-free — 3 attempts on unavailable, on the same backoff schedule the REST client already uses. Everything else is sent exactly once (#1809, #1846)
  • The Python SDK reconnects a subscription you positioned, when and only when the caller set start_after_sequence. Without a position there is nowhere honest to resume from (#1848, #1854)
  • 35 of the 83 served RPCs are annotated NO_SIDE_EFFECTS. The protos, not a hand-maintained list, are now the source of truth for what is safe to retry, and Go, TypeScript and Python all carry the annotation through generation (#1809, #1841)
  • New ironflow-fit agent skill — analyzes a codebase and reports where Ironflow fits, with ranked opportunities and file:line evidence across Java, C#, Python, Rust, Node and Go (#1874)
  • Pub/sub consumer groups never redelivered. ACK_MODE_AUTO is the default for an unset ack_mode, and it mapped to AckNonePolicy — JetStream tracks no ack state for such a consumer, so every Ack() and Nak() in the dispatcher was a no-op whose error was discarded. Inert as a result: max_redeliveries, redeliver_delay_ms, the no-members-available nak, and the redelivery legs of both BUFFER and BLOCK backpressure (#1849, #1853)
  • SubscriptionEvent.sequence was zero on every pub/sub path, for the whole life of the API. The sequence is assigned by the server and arrives on the PubAck, after the payload is already on the wire, so neither construction site could set it — and nothing asserted it (#1848, #1851)
  • ironflow init projects no longer commit their admin key. serve writes .ironflow/ under the cwd, including .ironflow_bootstrap_key.json (an admin API key, mode 0400) and .ironflow_jwt_secret. Neither scaffold template ignored that directory, so ironflow init followed by ironflow serve staged both secrets (#1859)
  • Every published artifact shipped a stale license. Four byte-identical copies of the root LICENSE still carried the [LICENSE_EMAIL] placeholder that was resolved at the root in #1576. The marketing copy is now generated from the root instead of vendored (#1855, #1856, #1857)
  • Browser SDK bearer authentication for KV, config and KV watches. Token credentials with API-key precedence and empty-key fallback across all three REST paths, plus URL-encoded environment and credential parameters on the KV WebSocket watch. Outbox publications now carry event metadata, and WaitForEvent uses one timeout budget while preserving catch-up semantics (#1823)
  • Connect routes are derived from the mounted services rather than tracked in a parallel list that could drift from them (#1821)
  • Two contracts the agent skills had backwards. waitForEvent was documented as needing a try/catch — the step suspends the run by throwing an internal YieldSignal the SDK catches at the handler boundary, so a user catch swallows it and the run “completes” without ever waiting. Separately, four files claimed streams.append starves projections and must be paired with an explicit emit; one append writes two outbox rows in a single transaction, the second on the exact subject the projection durable filters on. The audit scanner enforced the broken advice as a CRITICAL rule; it is now inverted, and covers Go, one-line try blocks and .catch() chains (#1867)
  • The Homebrew formula audit audited nothing. Homebrew disabled brew audit [path ...], and a trailing || echo in the release workflow swallowed the resulting exit 1 — so the brew channel reported green for an unknown number of releases. Both paths that reach the tap now share one audit script (#1820, #1834, #1835, #1840)
  • Typography across the dashboard, docs site and marketing — Figtree as the single sans, a derived JetBrains Mono heading curve, raised type sizes, and shared brand/ tokens instead of per-app values (#1832, #1844, #1876)
  • docs/reference/api/sdk-comparison.md is gated against derived coverage, so the SDK comparison table cannot drift from what the SDKs actually implement (#1826, #1827, #1833)
  • Guidance for teams on stacks with no Ironflow SDK, pointing at a working client path rather than overclaiming REST reachability (#1872, #1873, #1877)
  • make brew-preflight audits the formula a release would generate, in about 30 seconds, before the release rather than during it

Ironflow Desktop ships on its own cadence; its changes are tracked in the Ironflow Desktop changelog posts.