fix(deps): update rust crate distributed_cli to v3 - #72
Open
renovate[bot] wants to merge 1 commit into
Open
Conversation
renovate
Bot
force-pushed
the
renovate/distributed_cli-3.x
branch
6 times, most recently
from
July 11, 2026 10:51
de2a862 to
62b8634
Compare
renovate
Bot
force-pushed
the
renovate/distributed_cli-3.x
branch
from
July 20, 2026 16:58
62b8634 to
b3bdc6a
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
This PR contains the following updates:
1.6→3.0Release Notes
patrickleet/distributed (distributed_cli)
v3.3.4Compare 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.3Compare 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:
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 neverfor the PR body. Headline is TOTAL percentage; full tableis 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.2Compare Source
What's changed in v3.3.2
docs: teach model-first TDD workflow (#121) (by @patrickleet)
Implements [[tasks/model-first-tdd-guidance-1]]
Implements [[tasks/model-first-tdd-guidance-1]]
See full diff: v3.3.1...v3.3.2
v3.3.1Compare 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.0Compare Source
v3.2.0Compare Source
What's changed in v3.2.0
feat(cli): dctl skills init — extract embedded agent skills into .distributed/ (#113) (by @patrickleet)
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]]
Implements [[feat/cli-skills-init]]
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]]
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]]
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]]
#[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.0Compare 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]]
Implements [[codex/opentelemetry-tracing-compatibility]]
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
families, linted with promtool check metrics (PROMTOOL env gate)
asserts distributed.microsvc.dispatch arrives parented to the incoming
W3C traceparent (endpoint/file env gates)
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]]
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
message's existing traceparent (headers win only with a traceparent)
activity-driven with no timer, so any skipped pass froze the gauges
at stale values after the final drain
of paging the whole outbox; count saturates, oldest stays exact
transport receive now uses the same conditional parenting as
dispatch/outbox, documented on the helper
matching Message accessors and OTel span-parent extraction
Implements [[tasks/observability-prs-rebase]]
Implements [[codex/prometheus-metrics-endpoint]]
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.1Compare 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.0Compare 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.
Committabletrait (zero callers; pre-CommitBatch fossil).HandlerBuildertype alias (useRouteBuilder).Eventtrait; moveLocalEventinto
src/emitter/behind the feature (nowdistributed::emitter::LocalEvent).HashMapRepository/HashMapOutboxStore->InMemoryRepository/InMemoryOutboxStore, modulehashmap_repo->in_memory_repo, andtests/hashmap_repository_conformance->tests/in_memory_repository_conformance,matching every other in-memory default. Includes the CLI scaffold template.
src/bus/rabbit_bus.rs->src/bus/rabbitmq_bus.rsto match therabbitmqfeature andrabbitmq.rssibling.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::*. KeptRelationalReadModelIncludesat root(the ReadModel derive expands to it). Left #109's
table::root surfaceas-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.5Compare 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
claimstays per-backend (postgres SKIP LOCKED CTE vs sqlite scan-loop).
Squashed from 14 commits for a single rebase reconciliation onto main (after
align null-bind error message with postgres (includes field_name).
vs SQL snapshot-identity drift).
deletes the width-conversion helpers.
a GetStream default method.
Merge-reconciliation notes:
renamed types via local aliases (TableColumn as ColumnDef, TableStoreError
as ReadModelError) to keep the collapsed bodies unchanged.
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-injectiontest that pinned the OLD 57P01 (mis)classification is flipped here to
assert!(err.is_retryable())to match this PR's classification fix; #110'sshared 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.4Compare Source
What's changed in v2.3.4
test: dedup suites and add failure-path coverage (#110) (by @patrickleet)
Squashed #110 (9 commits) for a single rebase reconciliation onto main.
See PR #110 description for the original commit breakdown.
Claude-Session: https://claude.ai/code/session_01DzYSVLas93c7LbgHJWsW7n
Co-authored-by: Claude Fable 5 noreply@anthropic.com
See full diff: v2.3.3...v2.3.4
v2.3.3Compare Source
What's changed in v2.3.3
See full diff: v2.3.2...v2.3.3
v2.3.2Compare 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:
(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.
corruption surfaced through decode_error and the failure policy, instead
of being silently defaulted (which could ack a garbled message).
permanent via is_sqlx_transient instead of always retryable, so
deterministic failures reach the failure policy instead of redelivering
forever.
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.1Compare 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)
dispatch_message no longer clones the whole message (payload included)
per dispatch.
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.0Compare Source
What's changed in v2.3.0
feat: outbox dedup + perf cleanup (remove OutboxWorker stack, batch settlement) (#106) (by @patrickleet)
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
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
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
dispatch_claimed settled strictly serially: one publish round trip plus
one complete UPDATE round trip per message.
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.
(buffer_unordered) and settles all successes in one complete_many call;
publish failures remain settled individually via record_failure.
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.
flight and are re-published after lease expiry — the same at-least-once
window a crash between publish and settle always leaves.
buffer_unordered; the runtime-agnostic core stays executor-free.
against hashmap, Postgres, and SQLite.
Co-Authored-By: Claude Fable 5 noreply@anthropic.com
Claude-Session: https://claude.ai/code/session_01DzYSVLas93c7LbgHJWsW7n
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
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
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.4Compare 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.3Compare Source
What's changed in v2.2.3
test: compile scaffold output e2e; fix route-bundle template drift (#103) (by @patrickleet)
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
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.
--bus, and --gitops-promote variant, plus error paths (non-empty output
dir without/with --force, describe with a missing manifest).
Co-Authored-By: Claude Fable 5 noreply@anthropic.com
Claude-Session: https://claude.ai/code/session_01DzYSVLas93c7LbgHJWsW7n
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.2Compare Source
What's changed in v2.2.2
fix: harden distributed_macros diagnostics and expand compile-fail suite (#102) (by @patrickleet)
event_variant_identPascalCases only the last.-segment of the eventname, so
#[event("user.completed")]and#[event("admin.completed")]inone #[sourced] impl both derived the variant
Completed. The existingduplicate 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_eventsto 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
extract_param_names/extract_params_with_typessilently filter_mappedaway 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_typesthatemits 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
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
Extends the trybuild suite beyond attribute macros:
unknown readmodel field attribute key
and tuple-struct rejection
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.1Compare 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.
(env-gated broker/db tests self-skip without services)
catch missing cfg fences (only no-features and all-features
combos were compiled before)
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.0Compare 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
emitterfeature, while the macro compile-fail fixture uses local dummy types sodistributed_macrosdoes 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.0Compare Source
What's changed in v2.1.0
feat: Implement typed microsvc route bundles (#95) (by @patrickleet)
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.0Compare 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
Asyncprefixes from the public API now that the crate is async-first.Renames:
AsyncMessageSource->MessageSourceAsyncMessagePublisher->MessagePublisherAsyncMessageHandler->MessageHandlerAsyncLock->LockAsyncLockManager->LockManagerInMemoryAsyncLock->InMemoryLockInMemoryAsyncLockFuture->InMemoryLockFutureInMemoryAsyncLockManager->InMemoryLockManagerAlso renames the lock implementation modules from
async_*to neutral module names and moves the transport guide fromdocs/async-transports.mdtodocs/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)
🚦 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.
This PR was generated by Mend Renovate. View the repository job log.