Skip to content

fix(deps): update rust crate distributed_cli to v3 - #72

Open
renovate[bot] wants to merge 1 commit into
mainfrom
renovate/distributed_cli-3.x
Open

fix(deps): update rust crate distributed_cli to v3#72
renovate[bot] wants to merge 1 commit into
mainfrom
renovate/distributed_cli-3.x

Conversation

@renovate

@renovate renovate Bot commented Jul 4, 2026

Copy link
Copy Markdown
Contributor

This PR contains the following updates:

Package Type Update Change
distributed_cli dependencies major 1.63.0

Release Notes

patrickleet/distributed (distributed_cli)

v3.3.4

Compare Source

What's changed in v3.3.4
  • refactor: make Session identity claims gateway-agnostic (by @​patrickleet)

    Hasura was only an example query layer, but convenience helpers and docs
    treated x-hasura-* as the protocol. Use neutral x-user-id/x-role keys,
    document Session as an opaque claim map, and keep gateway-specific names
    readable via Session::get.

See full diff: v3.3.3...v3.3.4

v3.3.3

Compare Source

What's changed in v3.3.3
  • ci: add reusable quality and github-release workflows (by @​patrickleet)

    Introduce Distributed-owned shared Actions for library/domain crates:

    • quality.yaml: fmt + clippy -Dwarnings + build + test (no runs-on.com)
    • github-release.yaml: tag → GitHub Release only (no crates.io)

    Wire this repo's PR and main entry workflows to the new quality gate.
    Keep vnext-tag and crates.io publish helpers as-is for framework releases.

    Document the domain-crate recipe in .github/workflows/README.md. Callers
    can pin @​feat/shared-workflows until a release tag is cut.

  • ci: drop custom github-release; use workflow-simple-release (by @​patrickleet)

    Domain crates and docs should create GitHub Releases via
    unbounded-tech/workflow-simple-release. Shared surface in this repo
    stays quality (and framework-specific integration/publish entrypoints).

  • ci(quality): measure coverage with llvm-cov and sticky PR comment (by @​patrickleet)

    When cargo_coverage is true (default), run tests under cargo-llvm-cov,
    upload lcov, write a job summary, and post a sticky PR comment with the
    coverage summary (requires pull-requests: write on the caller job).

    Callers should grant pull-requests: write. Framework PR/main entrypoints
    pass --workspace for multi-crate coverage.

  • ci(quality): post clean TOTAL coverage % on PRs (by @​patrickleet)

    Stop teeing cargo llvm-cov compile/test logs into the sticky comment.
    Run tests + lcov first, then a quiet cargo llvm-cov report --summary-only --color never for the PR body. Headline is TOTAL percentage; full table
    is collapsible; link to the workflow run for the lcov artifact.

  • ci: keep framework entry pipelines off consumer quality.yaml (by @​patrickleet)

    quality.yaml is the reusable contract for Distributed domain crates and
    other consumers (fmt/clippy/test/coverage). This monorepo's on-pr-quality
    and on-push-main stay on unbounded-tech quality plus integration jobs —
    dogfooding the consumer workflow here was never the intent and fails on
    the framework workspace shape.

See full diff: v3.3.2...v3.3.3

v3.3.2

Compare Source

What's changed in v3.3.2
  • docs: teach model-first TDD workflow (#​121) (by @​patrickleet)

    • docs: teach model-first TDD workflow

    Implements [[tasks/model-first-tdd-guidance-1]]

    • docs: retain both Distributed design drivers

    Implements [[tasks/model-first-tdd-guidance-1]]

See full diff: v3.3.1...v3.3.2

v3.3.1

Compare Source

What's changed in v3.3.1
  • fix: build dctl release without lockfile (#​120) (by @​patrickleet)

    Implements [[tasks/distributed-cli-release-artifacts-1]]

See full diff: v3.3.0...v3.3.1

v3.3.0

Compare Source

v3.2.0

Compare Source

What's changed in v3.2.0
  • feat(cli): dctl skills init — extract embedded agent skills into .distributed/ (#​113) (by @​patrickleet)

    • feat(cli): add dctl skills init/list — embedded agent skills

    Materializes agent skills (distributed-usage, distributed-ci,
    distributed-schema) embedded via include_str! into .distributed/skills/,
    with harness wiring adapters: .claude/skills/ copies for Claude Code and
    .agents/skills/ copies + a sentinel-managed AGENTS.md block for Codex,
    Grok, Gemini, Pi, and AGENTS.md-only tools. Pure generation returns
    GeneratedProject; per-file drift semantics (created/unchanged/skipped/
    updated) make re-runs idempotent and never clobber local edits without
    --force.

    Implements [[tasks/cli-skills-init]]

    • fix: clarify skills init upgrade behavior

    Implements [[feat/cli-skills-init]]

    • refactor(cli): wire harness skill locations as symlinks to .distributed

    One on-disk copy: canonical skills stay under /skills/; each
    wired harness location (.claude/skills/, .agents/skills/)
    becomes a relative per-skill symlink to the canonical folder, coexisting
    with user-owned skills. Non-unix platforms fall back to real copies.
    A non-link path at a harness location (stale link or old copy layout)
    is skipped with a warning and converted with --force.

    Implements [[tasks/cli-skills-init]]

    • docs(skills): cover --metrics prometheus and --tracing in distributed-ci

    Now that the observability generators are on main (#​100), the CI skill
    documents ServiceMonitor/PrometheusRule gating and the OTLP env values.

    Implements [[tasks/cli-skills-init]]

    • docs(skills): lead distributed-usage with the models-and-handlers thesis

    The main point of using Distributed: the authored surface is models and
    handlers; the framework and dctl generate the deterministic structure
    around them. Emphasized in the skill body and its trigger description.

    Implements [[tasks/cli-skills-init]]

    • docs(skills): prefer the highest-level macro APIs in distributed-usage

    #[sourced] over #[digest]+aggregate!(), the derives over hand plumbing,
    routes! and with_bus(..).run(..) over manual wiring — dropping a level is
    a deliberate choice, not a default.

    Implements [[tasks/cli-skills-init]]

See full diff: v3.1.0...v3.2.0

v3.1.0

Compare Source

What's changed in v3.1.0
  • feat: Prometheus metrics endpoint and OTel trace propagation (#​100) (by @​patrickleet)

    • Add Prometheus metrics endpoint

    • fix: address prometheus metrics review feedback

    Implements [[codex/prometheus-metrics-endpoint]]

    • Add OpenTelemetry trace context propagation (#​99)

    • feat: add OpenTelemetry trace context propagation

    Implements [[tasks/opentelemetry-tracing-compatibility]]

    • fix: resolve tracing scaffold review findings

    Implements [[codex/opentelemetry-tracing-compatibility]]

    • fix: preserve local span hierarchy when a parent span is active

    When a dispatch or outbox publish runs inside an already-active local span,
    applying the remote traceparent unconditionally re-parents the framework span
    and breaks the local hierarchy. Only extract the remote parent at trace entry
    (no current span). Found by codex review of #​112; lands here because the
    affected code is this PR's.

    Co-Authored-By: Claude Fable 5 noreply@anthropic.com
    Claude-Session: https://claude.ai/code/session_01LRjoCyotvAaK3VbhZRd2gq

    • test: docker-level observability integration suites (#​112)

    • test: docker-level observability integration suites

    • tests/metrics_exposition: real HTTP scrape of /metrics across all framework
      families, linted with promtool check metrics (PROMTOOL env gate)
    • tests/otel_export: real OTLP pipeline -> OpenTelemetry Collector container;
      asserts distributed.microsvc.dispatch arrives parented to the incoming
      W3C traceparent (endpoint/file env gates)
    • integration-observability.yaml: reusable workflow running both suites plus
      helm template + kubeconform validation of scaffolded ServiceMonitor/
      PrometheusRule/OTLP-env output against published CRD schemas; wired into
      PR-quality and push-main pipelines

    Implements [[tasks/observability-integration-tests]]

    • test: cover nested span parenting in the OTLP export e2e

    The library fix (set_span_parent_from_metadata_if_no_current_span) moved to
    #​99 where the affected code lives; this keeps the regression coverage found
    by the codex review.


    Co-authored-by: Claude Fable 5 noreply@anthropic.com

    • fix: address observability code-review findings
    • knative ingress: tracestate-only HTTP headers no longer delete the
      message's existing traceparent (headers win only with a traceparent)
    • backlog gauges: drop the 5s refresh throttle — refreshes are
      activity-driven with no timer, so any skipped pass froze the gauges
      at stale values after the final drain
    • OutboxStore::backlog_stats default: bounded scan (1000 rows) instead
      of paging the whole outbox; count saturates, oldest stays exact
    • span parenting: one rule everywhere — an active local span wins;
      transport receive now uses the same conditional parenting as
      dispatch/outbox, documented on the helper
    • TraceContext::from_metadata: first match wins on duplicate keys,
      matching Message accessors and OTel span-parent extraction
    • tests: serialize unknown_command against the global metrics registry

    Implements [[tasks/observability-prs-rebase]]

    • fix: restrict scaffolded OTLP protocol

    Implements [[codex/prometheus-metrics-endpoint]]

    • chore: prepare telemetry foundation

    Implements [[tasks/distributed-telemetry-foundation-cleanup-1]]


    Co-authored-by: Claude Fable 5 noreply@anthropic.com

See full diff: v3.0.1...v3.1.0

v3.0.1

Compare Source

What's changed in v3.0.1
  • fix(deps): update apache/kafka docker tag to v4.3.1 (#​96) (by @​renovate[bot])

    Co-authored-by: renovate[bot] <29139614+renovate[bot]@​users.noreply.github.com>

See full diff: v3.0.0...v3.0.1

v3.0.0

Compare Source

What's changed in v3.0.0
  • refactor: API cleanup and renames (pre-release) (#​105) (by @​patrickleet)

    BREAKING CHANGE: Re-derived on current main (all other review PRs merged) rather than
    rebasing the stale diff, since these mechanical renames touch symbols the
    other PRs restructured. Squashed from the original 7 commits (see PR #​105
    history). Pre-release: no compat shims left behind.

    • Delete dead Committable trait (zero callers; pre-CommitBatch fossil).
    • Delete HandlerBuilder type alias (use RouteBuilder).
    • Complete emitter opt-in: delete the Event trait; move LocalEvent
      into src/emitter/ behind the feature (now distributed::emitter::LocalEvent).
    • Rename HashMapRepository/HashMapOutboxStore -> InMemoryRepository/
      InMemoryOutboxStore, module hashmap_repo -> in_memory_repo, and
      tests/hashmap_repository_conformance -> tests/in_memory_repository_conformance,
      matching every other in-memory default. Includes the CLI scaffold template.
    • Rename src/bus/rabbit_bus.rs -> src/bus/rabbitmq_bus.rs to match the
      rabbitmq feature and rabbitmq.rs sibling.
    • Prune adapter plumbing from the crate root (quick-start API at root,
      plumbing under module path): outbox adapter constants
      (SOURCED_METADATA_PREFIX, DEFAULT_OUTBOX_SOURCE_*) now only under
      distributed::outbox_worker::*; read_model load-graph/query plumbing
      (ReadModelLoadGraph/Request, ReadModelQueryCapabilities, ReadModelWorkspace,
      ReadModelIncludeRows, ReadModelLoadBuilder) now only under
      distributed::read_model::*. Kept RelationalReadModelIncludes at root
      (the ReadModel derive expands to it). Left #​109's table:: root surface
      as-is (its deliberate decision, out of scope here).
    • Entity::set_replaying -> pub(crate) (ReplayGuard covers internal use).

    Skipped #![warn(missing_docs)]: surfaces 518 warnings (mechanical-pass
    infeasible), as in the original PR.

    Claude-Session: https://claude.ai/code/session_01DzYSVLas93c7LbgHJWsW7n

    Co-authored-by: Claude Fable 5 noreply@anthropic.com

See full diff: v2.3.5...v3.0.0

v2.3.5

Compare Source

What's changed in v2.3.5
  • refactor: collapse postgres/sqlite repositories into shared generic layer (#​111) (by @​patrickleet)

    Extends the existing SqlxReadModelBackend / lock/sqlx_common dialect-trait
    pattern to the event-store/snapshot/outbox/inbox layers. postgres_repo and
    sqlite_repo shrink from ~1700 lines each to ~460-line dialect shims over a
    shared SqlxRepository/SqlxOutboxStore in src/sqlx_repo/repo.rs. Outbox claim
    stays per-backend (postgres SKIP LOCKED CTE vs sqlite scan-loop).

    Squashed from 14 commits for a single rebase reconciliation onto main (after

    • fix: surface malformed sqlite timestamps as errors (was silent UNIX_EPOCH);
      align null-bind error message with postgres (includes field_name).
    • refactor: share commit-batch validation across all backends (fixes hashmap
      vs SQL snapshot-identity drift).
    • feat!: widen postgres integer columns to BIGINT so both backends decode i64;
      deletes the width-conversion helpers.
    • perf: batch snapshot loads for get_all hydration (fixes N+1); get_streams is
      a GetStream default method.
    • perf: batch the commit_batch concurrency pre-check into one query.
    • perf!: borrow events in PreparedEventAppend instead of cloning.
    • feat!: bound outbox status listings (messages_by_status/pending) with a limit.
    • perf!: store EventRecord.payload_codec as Cow<'static, str>.
    • feat: run migrations through sqlx's Migrator with a _sqlx_migrations ledger.
    • fix: chunk postgres event/outbox inserts under the 65535 bind-param cap.
    • fix: classify postgres 57P01/57P02/57P03 and class-08 SQLSTATEs as transient.

    Merge-reconciliation notes:

    • table/ is now the canonical vocabulary (#​109); backend shims import the
      renamed types via local aliases (TableColumn as ColumnDef, TableStoreError
      as ReadModelError) to keep the collapsed bodies unchanged.
    • #​106's batched OutboxStore::complete_many overrides lived in the old backend
      files and were collapsed away; SqlxOutboxStore inherits the serial default
      (correct, conformance-tested). Re-adding a batched override to the shared
      layer is a follow-up (see tasks/outbox-sqlx-batched-complete-many).

    REBASED onto main+#​110 (9f34f0a): #​110 already merged, so its fault-injection
    test that pinned the OLD 57P01 (mis)classification is flipped here to
    assert!(err.is_retryable()) to match this PR's classification fix; #​110's
    shared outbox test helpers are updated for the bounded messages_by_status(limit)
    API. Main stays green on merge.

    Claude-Session: https://claude.ai/code/session_01DzYSVLas93c7LbgHJWsW7n

    Co-authored-by: Claude Fable 5 noreply@anthropic.com

See full diff: v2.3.4...v2.3.5

v2.3.4

Compare Source

What's changed in v2.3.4

See full diff: v2.3.3...v2.3.4

v2.3.3

Compare Source

What's changed in v2.3.3

See full diff: v2.3.2...v2.3.3

v2.3.2

Compare Source

What's changed in v2.3.2
  • fix: backport sqlite bus hardening to PostgresBus (by @​patrickleet)

    Port the hardening SqliteBus received but PostgresBus never got:

    • Claim-token fencing: each queue claim mints a claim_token
      (gen_random_uuid()) and every settlement (delete on ack/dead_letter/park,
      release on nack) is scoped to seq AND claim_token, so a worker whose
      lease expired cannot settle a row that was reclaimed under a new token.
    • Strict row decode: corrupt content_type/metadata/kind are permanent
      corruption surfaced through decode_error and the failure policy, instead
      of being silently defaulted (which could ack a garbled message).
    • Error classification: database errors are classified transient vs
      permanent via is_sqlx_transient instead of always retryable, so
      deterministic failures reach the failure policy instead of redelivering
      forever.
    • Schema CHECK constraints (name/kind/content_type/attempts/claim_token)
      and the claim index including locked_until, mirroring the sqlite schema.

    Tests mirror sqlite_transport: corrupt kind/metadata/content_type rows are
    dead-lettered not silently skipped, the schema rejects unsupported kinds,
    and a stale worker cannot settle an expired reclaimed queue claim.

    Co-Authored-By: Claude Fable 5 noreply@anthropic.com
    Claude-Session: https://claude.ai/code/session_01DzYSVLas93c7LbgHJWsW7n

  • refactor: make Bus::send/publish default trait methods (by @​patrickleet)

    All 7 transports implemented identical send/publish bodies that wrap the
    payload in a Command/Event message and delegate to send_message/
    publish_message. Provide those bodies as trait defaults and delete the 7
    duplicate impls; transports now implement only send_message and
    publish_message.

    Co-Authored-By: Claude Fable 5 noreply@anthropic.com
    Claude-Session: https://claude.ai/code/session_01DzYSVLas93c7LbgHJWsW7n

  • refactor: extract shared SqlBus core for the Postgres/SQLite buses (by @​patrickleet)

    After the hardening backport, postgres_bus and sqlite_bus were near-clones.
    Mirror the proven lock/sqlx_common pattern: a new bus/sql_bus_common with a
    generic SqlBus<B: SqlBusDialect> owning the builders, Bus/BusConsumer
    impls, queue/log sources, row decoding, and claim-token-fenced settlement.
    Each backend now contributes only its dialect (SCHEMA, statements, claim
    and log-read queries — postgres array-bind vs sqlite IN-list).

    PostgresBus/SqliteBus and the *Received types become type aliases of the
    generic types — same public API, no wrapper types.

    Co-Authored-By: Claude Fable 5 noreply@anthropic.com
    Claude-Session: https://claude.ai/code/session_01DzYSVLas93c7LbgHJWsW7n

  • perf: batch SQL bus source reads (by @​patrickleet)

    Both SQL sources fetched one row per query, re-running the claim/offset
    subquery for every message. Claim and log_read now fetch up to 16 rows per
    query into a VecDeque buffer (the OutboxSource pattern).

    Queue rows stay independently settleable: each claimed row carries its own
    claim token (gen_random_uuid()/randomblob are evaluated per row), so nacks
    and lease expiry behave exactly as before.

    Log read-ahead preserves the offset contract: the settle handles report
    forward settlement (ack/dead-letter/park) through a shared seq watermark,
    and when the previously delivered entry was nacked (offset unmoved) the
    source discards its buffer and re-reads from the durable offset, so a
    buffered later entry can never advance the offset past a nacked one. The
    runner settles each message before the next recv, so the check is
    race-free.

    Co-Authored-By: Claude Fable 5 noreply@anthropic.com
    Claude-Session: https://claude.ai/code/session_01DzYSVLas93c7LbgHJWsW7n

  • perf: cache RabbitMQ topology declarations and make queue polling sticky (by @​patrickleet)

    send_message declared its command queue and publish_message its events
    exchange on every call — a broker round-trip per message. Track declared
    names in a per-process set: each queue/exchange is declared once (the
    events exchange eagerly at connect), with later sends/publishes skipping
    the declare entirely. Declarations are idempotent, so the benign
    concurrent-declare race is harmless, and a post-connect namespace() change
    just declares the new names on first use.

    RabbitBusSource::recv polled every queue per message via basic_get. Keep
    basic_get (basic_consume pushes with no broker-side drained signal, which
    would break the Ok(None) drain-to-idle contract) but make polling sticky:
    start each recv at the queue that last yielded, so draining a busy queue
    costs one basic_get per message; a full empty cycle is still required
    before returning None.

    Co-Authored-By: Claude Fable 5 noreply@anthropic.com
    Claude-Session: https://claude.ai/code/session_01DzYSVLas93c7LbgHJWsW7n

  • refactor: share wire-to-Message decoding across broker transports (by @​patrickleet)

    NATS, Kafka, and RabbitMQ each hand-rolled the same decode tail: route the
    id/kind headers into Message::id/Message::kind and everything else into
    metadata. Extract message_from_wire(name, payload, id_key, kind_key,
    headers) in bus/message.rs; each adapter now contributes only its
    header-pair iterator (RabbitMQ passes id_key: None — its id rides in the
    AMQP message_id property — and keeps its content-type override). Encode
    paths stay per-transport: the header types differ.

    Co-Authored-By: Claude Fable 5 noreply@anthropic.com
    Claude-Session: https://claude.ai/code/session_01DzYSVLas93c7LbgHJWsW7n

  • refactor: fold NATS/Kafka listen/subscribe mirrors into one consume path (by @​patrickleet)

    listen and subscribe in NatsBus and KafkaBus were line-for-line mirrors
    differing only in the plan half (commands vs events) and the cmd/evt
    suffix used for subjects/topics, durables, and group ids. Each bus now has
    one private consume(router, options, kind) that both delegate to,
    preserving the empty-plan-check-before-group-resolution ordering asserted
    by the kafka_bus tests. Rabbit's pair stays as is (structurally
    different). KafkaBus's run() helper is absorbed into consume.

    Co-Authored-By: Claude Fable 5 noreply@anthropic.com
    Claude-Session: https://claude.ai/code/session_01DzYSVLas93c7LbgHJWsW7n

  • refactor: generalize RabbitSource to multi-queue, delete RabbitBusSource (by @​patrickleet)

    RabbitBusSource duplicated RabbitSource's basic_get + settle shape, adding
    only multi-queue polling and routing-key prefix stripping. Fold those into
    RabbitSource (with the sticky polling from the previous perf change) and
    delete RabbitBusSource. The single-queue constructor keeps its public
    signature; naming now derives from the delivery's routing key, which under
    the default exchange equals the queue name, so standalone behavior is
    unchanged.

    Co-Authored-By: Claude Fable 5 noreply@anthropic.com
    Claude-Session: https://claude.ai/code/session_01DzYSVLas93c7LbgHJWsW7n

See full diff: v2.3.1...v2.3.2

v2.3.1

Compare Source

What's changed in v2.3.1
  • refactor: dedupe snapshot tail replay into one iterator-based fn (by @​patrickleet)

    replay_filtered and replay_events were near-duplicates; one replay_events
    taking impl Iterator<Item = &EventRecord> now serves both the borrowed
    post-snapshot tail and the upcasted copy, with post_snapshot_tail naming
    the shared filter.

    Co-Authored-By: Claude Fable 5 noreply@anthropic.com
    Claude-Session: https://claude.ai/code/session_01DzYSVLas93c7LbgHJWsW7n

  • perf: skip event cloning when no upcaster applies during hydrate (by @​patrickleet)

    Registering any upcaster used to deep-clone the whole event history on
    every hydrate (and the post-snapshot tail on snapshot loads), even when
    all events were already current. upcast_events_for_replay now pre-scans
    the borrowed view: if no (event_type, from_version) matches, replay runs
    straight off the borrowed events; if some match, only those events are
    cloned (per-event Cow). upcast_one also defers its cycle-tracking HashSet
    until the first transform applies.

    Co-Authored-By: Claude Fable 5 noreply@anthropic.com
    Claude-Session: https://claude.ai/code/session_01DzYSVLas93c7LbgHJWsW7n

  • perf: stop cloning all five HashMapRepository stores per commit (by @​patrickleet)

    commit_batch cloned the entire event/read-model/snapshot/outbox/inbox
    maps on every commit to get all-or-nothing semantics, which grows
    quadratic over a session. Fallible steps now run first — event version
    checks and outbox/inbox duplicate checks read the live maps; read-model
    plans (the only step that can fail mid-application) apply to a staging
    copy bounded to the rows the batch touches (every touched storage key is
    known up front via the mutations' lock keys) — and the live maps are
    mutated only once nothing can fail. Guards are still held throughout so
    the batch stays atomic to readers.

    Co-Authored-By: Claude Fable 5 noreply@anthropic.com
    Claude-Session: https://claude.ai/code/session_01DzYSVLas93c7LbgHJWsW7n

  • perf: evict idle InMemoryLockManager entries on unlock (by @​patrickleet)

    The manager kept one Arc per aggregate id ever locked,
    forever. A manager-owned lock now carries a weak back-reference to the
    manager's map and evicts its entry on unlock when, re-checked under the
    map guard, only the map's Arc and the unlocking handle remain and the
    lock is idle (unlocked, no waiters). Parked waiters hold live Arcs, so
    their entries survive. get_lock documents that handles are per
    acquire/release cycle, matching QueuedRepository's usage.

    Co-Authored-By: Claude Fable 5 noreply@anthropic.com
    Claude-Session: https://claude.ai/code/session_01DzYSVLas93c7LbgHJWsW7n

  • fix: surface JSON parse errors instead of nulling handler input (by @​patrickleet)

    dispatch_message swallowed message_to_json_input failures and dispatched
    with Value::Null input, discarding the parse error. The Null fallback is
    intentional only for non-JSON payloads (bitcode/binary), whose handlers
    read the raw payload; for JSON content types a malformed payload now
    returns HandlerError::DecodeFailed carrying the parse failure.

    Co-Authored-By: Claude Fable 5 noreply@anthropic.com
    Claude-Session: https://claude.ai/code/session_01DzYSVLas93c7LbgHJWsW7n

  • perf: cut per-dispatch allocations in Service and Handlers routing (by @​patrickleet)

    • Context now borrows the Message instead of owning a clone, so
      dispatch_message no longer clones the whole message (payload included)
      per dispatch.
    • Handler maps are keyed HashMap<MessageKind, HashMap<String, _>> in
      Routes, Service, and bus::Handlers, so lookups take &str and no longer
      allocate a (kind, String) key per dispatch/handles check.

    Co-Authored-By: Claude Fable 5 noreply@anthropic.com
    Claude-Session: https://claude.ai/code/session_01DzYSVLas93c7LbgHJWsW7n

  • fix: return an error from Service::run without a bus; assert entity invariant (by @​patrickleet)

    Service::run already returns Result<(), TransportError>, so a missing
    bus is now a permanent TransportError pointing at with_bus instead of a
    panic. Entity::new_events also debug_asserts the
    committed_version >= prefix_version invariant established by
    load_tail_from_history, so a violated invariant fails at the source
    instead of as a wrapped subtraction.

    Co-Authored-By: Claude Fable 5 noreply@anthropic.com
    Claude-Session: https://claude.ai/code/session_01DzYSVLas93c7LbgHJWsW7n

  • refactor: collapse CommitBuilder and StagedCommitBuilder into one builder (by @​patrickleet)

    CommitBuilder and StagedCommitBuilder duplicated read_models/outbox
    staging and CommitBatch construction, and outbox source stamping
    differed by entry path: commit(agg) stamped every message, staged
    commits stamped only an unambiguous source, and commit_many stamped
    nothing. All behavior now lives on CommitBuilder (staging state is the
    stream writes plus StagedOutboxSource); StagedCommitBuilder is a
    transparent naming state that only exists to offer the no-argument
    commit(). One documented stamping rule everywhere: exactly one distinct
    staged aggregate stamps the source, otherwise messages keep what they
    carry. commit(agg) is now sugar for aggregate(agg).commit(). The private
    OutboxSource struct is renamed SourceStamp so it no longer shadows the
    public outbox_worker::OutboxSource.

    Co-Authored-By: Claude Fable 5 noreply@anthropic.com
    Claude-Session: https://claude.ai/code/session_01DzYSVLas93c7LbgHJWsW7n

  • style: use arrays for borrowed event fixtures in upcaster tests (by @​patrickleet)

    Clippy (useless_vec): the new upcast_events_for_replay tests only
    iterate borrowed events, so plain arrays suffice.

    Co-Authored-By: Claude Fable 5 noreply@anthropic.com
    Claude-Session: https://claude.ai/code/session_01DzYSVLas93c7LbgHJWsW7n

  • test: label bitcode outbox payloads as octet-stream in board example (by @​patrickleet)

    The board example's outbox bridge forwarded raw bitcode bytes with
    Message::new's default application/json content type; the projection
    only worked because JSON parse failures were silently nulled. Now that
    malformed JSON surfaces as a decode error, label the binary payload the
    way the real outbox dispatch does (application/octet-stream).

    Co-Authored-By: Claude Fable 5 noreply@anthropic.com
    Claude-Session: https://claude.ai/code/session_01DzYSVLas93c7LbgHJWsW7n

See full diff: v2.3.0...v2.3.1

v2.3.0

Compare Source

What's changed in v2.3.0
  • feat: outbox dedup + perf cleanup (remove OutboxWorker stack, batch settlement) (#​106) (by @​patrickleet)

    • remove vestigial OutboxWorker publish stack

    Delete the parallel outbox publish pipeline (OutboxWorker, DrainResult,
    ProcessOneResult, OutboxPublisher, LogPublisher, LocalEmitterPublisher):
    a complete second claim -> publish -> settle state machine that mutated
    loaded rows in memory without durable settle, duplicating the production
    drain path in OutboxDispatcher::dispatch_claimed with its own degenerate
    message shape (event_type + raw bytes + metadata map instead of the
    canonical bus Message).

    Its only consumers were the todos integration tests, now ported to
    OutboxDispatcher with a recording MessagePublisher. LocalEmitterPublisher
    had no other users (the emitter feature's real path is src/emitter
    enqueue), so it is not recreated. Pre-release: no deprecation shims.

    Co-Authored-By: Claude Fable 5 noreply@anthropic.com
    Claude-Session: https://claude.ai/code/session_01DzYSVLas93c7LbgHJWsW7n

    • perf: convert outbox rows to transport messages by value

    The immediate-publish path cloned each staged row into the publish hook,
    then From<&OutboxMessage> for Message cloned the payload bytes and every
    metadata string a second time. Every dispatch path (dispatch_claimed,
    publish hook, outbox source) owns its row by the time it maps, so the
    conversion is now From (by value) and the payload,
    event type, id, and metadata strings move instead of being re-cloned.

    Co-Authored-By: Claude Fable 5 noreply@anthropic.com
    Claude-Session: https://claude.ai/code/session_01DzYSVLas93c7LbgHJWsW7n

    • refactor: share one publish-then-settle path for dispatcher and hook

    OutboxDispatcher::dispatch_claimed and BusOutboxPublishHook carried the
    same publish -> complete-or-record_failure sequence in two places.
    Extract it as publish_and_settle, used by both. The hook still never
    re-claims: it is handed rows already claimed inside the commit
    transaction, and publish_and_settle only settles claims it is given.

    OutboxPublishHook::publish_claimed now takes the commit's claimed rows
    as one batch (callers looped per row anyway), which also sets up
    batched settlement for the after-commit path.

    Co-Authored-By: Claude Fable 5 noreply@anthropic.com
    Claude-Session: https://claude.ai/code/session_01DzYSVLas93c7LbgHJWsW7n

    • perf: batch outbox settlement and bound publish concurrency

    dispatch_claimed settled strictly serially: one publish round trip plus
    one complete UPDATE round trip per message.

    • Add OutboxStore::complete_many (default: serial loop). Postgres settles
      the batch in one UPDATE ... FROM unnest(ids, workers, attempts)
      statement with the same per-claim predicate as complete, diagnosing
      unapplied claims to the same NotFound/InvalidState errors; SQLite runs
      the per-claim UPDATEs in one transaction (one commit/fsync per batch;
      an IN-list cannot carry the per-claim worker/attempt predicate); the
      hashmap store settles under a single write lock. Additive only in the
      repo backends: new fns, no restructuring.
    • publish_and_settle now publishes with bounded concurrency
      (buffer_unordered) and settles all successes in one complete_many call;
      publish failures remain settled individually via record_failure.
    • OutboxDispatcher::with_publish_concurrency(NonZeroUsize) exposes the
      publish window. It defaults to 1 because outbox claim order is
      created-at order and consumers may rely on it: 1 preserves strict
      ordering on the wire; higher values overlap publish round trips but may
      deliver out of order. The after-commit hook always uses 1 — a commit's
      rows are one aggregate's events, where relative order matters.
    • If the batched complete fails, published-but-unsettled rows stay in
      flight and are re-published after lease expiry — the same at-least-once
      window a crash between publish and settle always leaves.
    • Adds futures-util (no default features) to the core crate for
      buffer_unordered; the runtime-agnostic core stays executor-free.
    • Conformance: worker_completes_claims_in_one_batch runs complete_many
      against hashmap, Postgres, and SQLite.

    Co-Authored-By: Claude Fable 5 noreply@anthropic.com
    Claude-Session: https://claude.ai/code/session_01DzYSVLas93c7LbgHJWsW7n

    • refactor: share claim_order_key between outbox store ordering helpers

    sort_by_claim_order and claim_order_ids each restated the claim ordering
    (created-at, then message id). One claim_order_key fn now defines it and
    both helpers sort by it, so the two orderings cannot drift apart.

    Co-Authored-By: Claude Fable 5 noreply@anthropic.com
    Claude-Session: https://claude.ai/code/session_01DzYSVLas93c7LbgHJWsW7n

    • chore: consolidate outbox_worker test block_on into one test util

    The busy-poll test executor was copy-pasted into three outbox_worker
    test modules (two more copies died with the OutboxWorker stack). One
    #[cfg(test)] testing module now owns it; src/bus copies are untouched.

    Co-Authored-By: Claude Fable 5 noreply@anthropic.com
    Claude-Session: https://claude.ai/code/session_01DzYSVLas93c7LbgHJWsW7n

    • fix: preserve sub-second precision in outbox lease deadlines

    lease_deadline_secs truncated now + lease to whole seconds (.as_secs()),
    and claim() rebuilt the SystemTime from that u64 — so a sub-second lease
    produced a deadline at or before now (expired at birth), and every lease
    lost up to a second. claim() now takes the deadline as a SystemTime and
    lease_deadline computes now + lease at full precision, keeping the
    overflow and before-epoch validation. The stores already persist
    leased_until with sub-second precision (f64 epoch / nanosecond storage),
    so only the in-memory computation was lossy.

    Unit test covers a sub-second lease: deadline is exactly now + lease and
    is not expired until the lease elapses.

    Co-Authored-By: Claude Fable 5 noreply@anthropic.com
    Claude-Session: https://claude.ai/code/session_01DzYSVLas93c7LbgHJWsW7n


    Co-authored-by: Claude Fable 5 noreply@anthropic.com

See full diff: v2.2.4...v2.3.0

v2.2.4

Compare Source

What's changed in v2.2.4
  • refactor: dedupe SQL lock managers into generic SqlxLockManager (#​104) (by @​patrickleet)

    The Postgres and SQLite lock managers were 75% line-identical clones:
    struct, new, the four with_* builders, migrate, sweep_expired, the whole
    cached-Arc get_lock body, and the 3-method Lock delegation differed only
    in pool type, owner prefix, and dialect SQL.

    Extract a generic SqlxLockManager/SqlxLock into sqlx_common,
    driven by a LockDialect trait carrying OWNER_PREFIX, DDL, ACQUIRE_SQL,
    RELEASE_SQL, SWEEP_SQL, a busy_is_contention hook (SQLite maps
    SQLITE_BUSY to contention; Postgres keeps the default), and a
    rows_affected accessor (sqlx has no dialect-neutral one). The sqlx
    bind/decode bounds live exactly once, on the blanket LeaseQueries impl,
    which also owns the single shared split-on-';' migrate loop.

    PostgresLockManager/SqliteLockManager and PostgresLock/SqliteLock are
    now type aliases over the marker dialects, so the public API is
    unchanged; the backend files shrink to dialect SQL consts plus the busy
    hook. This pilots the dialect-trait pattern for the other SQL modules.

    Claude-Session: https://claude.ai/code/session_01DzYSVLas93c7LbgHJWsW7n

    Co-authored-by: Claude Fable 5 noreply@anthropic.com

See full diff: v2.2.3...v2.2.4

v2.2.3

Compare Source

What's changed in v2.2.3
  • test: compile scaffold output e2e; fix route-bundle template drift (#​103) (by @​patrickleet)

    • fix: update scaffold templates to the route-bundle Service API

    The scaffolded service.rs still used the removed register_handlers! macro,
    the pre-route-bundle generic Service, and
    Service::new().with_repo(repo) — none of which exist since the typed
    microsvc route bundles landed — so every scaffolded project failed to
    compile. Rebuild the service around distributed::routes!(
    Routes::new().with_dependencies(repo), ...) and Arc, and pin the
    Knative scaffold's axum to 0.8 to match the Router the distributed crate
    hands to axum::serve.

    Co-Authored-By: Claude Fable 5 noreply@anthropic.com
    Claude-Session: https://claude.ai/code/session_01DzYSVLas93c7LbgHJWsW7n

    • test: compile scaffold output and cover CLI variants and error paths
    • cli_scaffold_compile.rs: scaffold into a temp dir pointing the generated
      distributed dependency at this workspace, then cargo check the output
      (ignored by default like the manifest-harness tests). Covers the
      http+postgres+model+read-models surface and the knative+in-memory branch.
    • cli_scaffold.rs: matrix-lite assertions for every --store, --transport,
      --bus, and --gitops-promote variant, plus error paths (non-empty output
      dir without/with --force, describe with a missing manifest).
    • cli_manifest.rs: schema --dialect sqlite happy path.

    Co-Authored-By: Claude Fable 5 noreply@anthropic.com
    Claude-Session: https://claude.ai/code/session_01DzYSVLas93c7LbgHJWsW7n

    • refactor: extract manifest-harness codegen from cli.rs into its own module

    Pure move: HarnessOptions/HarnessMode, run_manifest_harness, the inline
    harness Cargo.toml/main.rs templates, cargo-metadata package resolution,
    and entrypoint validation now live in src/manifest_harness.rs beside
    generate/, with their unit tests. cli.rs keeps the clap surface and the
    scaffold side effects; the shared path helpers it owns are pub(crate) for
    the harness. No behavior change.

    Co-Authored-By: Claude Fable 5 noreply@anthropic.com
    Claude-Session: https://claude.ai/code/session_01DzYSVLas93c7LbgHJWsW7n


    Co-authored-by: Claude Fable 5 noreply@anthropic.com

See full diff: v2.2.2...v2.2.3

v2.2.2

Compare Source

What's changed in v2.2.2
  • fix: harden distributed_macros diagnostics and expand compile-fail suite (#​102) (by @​patrickleet)

    • fix: reject #[event] names that collide on the derived enum variant

    event_variant_ident PascalCases only the last .-segment of the event
    name, so #[event("user.completed")] and #[event("admin.completed")] in
    one #[sourced] impl both derived the variant Completed. The existing
    duplicate check compared full event-name strings, so the collision slipped
    through and surfaced as a duplicate-variant compile error inside generated
    code. Track the derived variant idents too and emit a spanned error naming
    both event strings at the offending attribute.

    Also narrows seen_events to a HashSet since its span value was unused.

    Co-Authored-By: Claude Fable 5 noreply@anthropic.com
    Claude-Session: https://claude.ai/code/session_01DzYSVLas93c7LbgHJWsW7n

    • fix: error on non-identifier parameter patterns in event macros

    extract_param_names/extract_params_with_types silently filter_mapped
    away any parameter that was not a plain Pat::Ident (tuple patterns,
    wildcards, struct destructuring). The parameter vanished from the recorded
    payload, and the generated replay arm called the method with too few
    arguments — an arity error in generated code, far from the cause.

    Merge the two helpers into one fallible extract_params_with_types that
    emits a spanned 'unsupported parameter pattern in #[event|digest|enqueue]
    method — use a plain identifier' error at the offending pattern.

    Co-Authored-By: Claude Fable 5 noreply@anthropic.com
    Claude-Session: https://claude.ai/code/session_01DzYSVLas93c7LbgHJWsW7n

    • refactor: parse upcaster entries with one shared UpcasterDef parser

    The upcaster grammar ('name', from => to, Src => Tgt, transform_fn) was
    parsed twice — once in AggregateInput::parse and once in parse_sourced_args
    — so the two copies could drift. Extract an impl Parse for UpcasterDef plus
    a parse_upcaster_list helper used by both entry points. Grammar is
    unchanged, including optional trailing commas between entries.

    Co-Authored-By: Claude Fable 5 noreply@anthropic.com
    Claude-Session: https://claude.ai/code/session_01DzYSVLas93c7LbgHJWsW7n

    • test: cover derive and function-like macro misuse in compile-fail suite

    Extends the trybuild suite beyond attribute macros:

    • derive(ReadModel): missing id field / #[readmodel(id)] marker, and an
      unknown readmodel field attribute key
    • derive(Snapshot): missing entity field (with the available-fields hint)
      and tuple-struct rejection
    • aggregate!: unknown keyword argument in place of aggregate_type

    Each fixture pins its .stderr so diagnostic quality regressions are caught.
    All diagnostics fire during attribute parsing, before codegen, so they are
    independent of the schema() codegen changes landing separately.

    Co-Authored-By: Claude Fable 5 noreply@anthropic.com
    Claude-Session: https://claude.ai/code/session_01DzYSVLas93c7LbgHJWsW7n


    Co-authored-by: Claude Fable 5 noreply@anthropic.com

See full diff: v2.2.1...v2.2.2

v2.2.1

Compare Source

What's changed in v2.2.1
  • chore(deps): update unbounded-tech/workflow-vnext-tag action to v1.21.5 (#​93) (by @​renovate[bot])

    Co-authored-by: renovate[bot] <29139614+renovate[bot]@​users.noreply.github.com>

  • chore: rename distributed cli binary to dctl (#​98) (by @​patrickleet)

  • ci: gate releases on all-features tests and close coverage gaps (#​101) (by @​patrickleet)

    The release gate never exercised the sqlite, emitter, http, or grpc
    test suites: the shared quality job runs cargo test with default
    features (= none), so every #![cfg(feature = ...)] test target
    compiled to empty. PostgresLockManager had zero CI execution because
    the postgres integration job omitted the sql_lock_manager target.

    • Add a reusable all-features workflow with two jobs:
      • all-features: cargo test --workspace --all-features --all-targets
        (env-gated broker/db tests self-skip without services)
      • each-feature: cargo hack check --workspace --each-feature to
        catch missing cfg fences (only no-features and all-features
        combos were compiled before)
    • Wire it into the PR quality pipeline and the release gate's needs
    • Add --test sql_lock_manager to the postgres integration job
    • Delete orphaned tests/read_models/ (no main.rs, no #[path] refs;
      cargo silently never compiled it)

    Claude-Session: https://claude.ai/code/session_01DzYSVLas93c7LbgHJWsW7n

    Co-authored-by: Claude Fable 5 noreply@anthropic.com

See full diff: v2.2.0...v2.2.1

v2.2.0

Compare Source

What's changed in v2.2.0
  • feat: Make emitter feature opt-in (#​97) (by @​patrickleet)

    Make emitter opt-in by removing it from the default Cargo feature set.

    Emitter-specific integration coverage is now gated behind the emitter feature, while the macro compile-fail fixture uses local dummy types so distributed_macros does not need to enable root-crate emitter APIs just to run trybuild tests. README feature and test guidance now reflects the opt-in behavior.

See full diff: v2.1.0...v2.2.0

v2.1.0

Compare Source

What's changed in v2.1.0
  • feat: Implement typed microsvc route bundles (#​95) (by @​patrickleet)

    • Implement typed microsvc route bundles
    • Remove legacy register_handlers macro
    • Add routes dependency combination coverage

    This is a breaking change, use routes! macro instead of register_handlers. Services are not sets of route bundles to support multiple aggregates/repos per service.

See full diff: v2.0.0...v2.1.0

v2.0.0

Compare Source

What's changed in v2.0.0
  • chore(deps): update actions/checkout action to v7 (#​92) (by @​renovate[bot])

    Co-authored-by: renovate[bot] <29139614+renovate[bot]@​users.noreply.github.com>

  • chore(docs): sqlitebus (by @​patrickleet)

  • feat: remove redundant async API names (#​94) (by @​patrickleet)

    BREAKING CHANGE: Remove redundant Async prefixes from the public API now that the crate is async-first.

    Renames:

    • AsyncMessageSource -> MessageSource
    • AsyncMessagePublisher -> MessagePublisher
    • AsyncMessageHandler -> MessageHandler
    • AsyncLock -> Lock
    • AsyncLockManager -> LockManager
    • InMemoryAsyncLock -> InMemoryLock
    • InMemoryAsyncLockFuture -> InMemoryLockFuture
    • InMemoryAsyncLockManager -> InMemoryLockManager

    Also renames the lock implementation modules from async_* to neutral module names and moves the transport guide from docs/async-transports.md to docs/transports.md.

    This is a breaking API cleanup; no compatibility aliases are kept.

See full diff: v1.9.0...v2.0.0


Configuration

📅 Schedule: (UTC)

  • Branch creation
    • At any time (no schedule defined)
  • Automerge
    • At any time (no schedule defined)

🚦 Automerge: Disabled by config. Please merge this manually once you are satisfied.

Rebasing: Whenever PR becomes conflicted, or you tick the rebase/retry checkbox.

🔕 Ignore: Close this PR and you won't be reminded about this update again.


  • If you want to rebase/retry this PR, check this box

This PR was generated by Mend Renovate. View the repository job log.

@renovate
renovate Bot force-pushed the renovate/distributed_cli-3.x branch 6 times, most recently from de2a862 to 62b8634 Compare July 11, 2026 10:51
@renovate
renovate Bot force-pushed the renovate/distributed_cli-3.x branch from 62b8634 to b3bdc6a Compare July 20, 2026 16:58
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

0 participants