Release Notes
Every BackWave library shares a single version, so each release below covers every package.
v1.7.0
Latest- AddedLease hand-back on a clean stop. Each pump spends a ShutdownBudget, 5 s by default, clamped to four fifths of HostOptions.ShutdownTimeout, and TimeSpan.Zero skips the hand-back. The jobs return to Scheduled at once with the attempt unchanged, so another node picks them up without waiting for lease expiry. A job already at its attempt ceiling is dead-lettered instead, as an expired lease would be. A fail-stopped group writes nothing more, and its leases lapse as after a crash. The storage contract gains RelinquishLeasesAsync with a no-op default, so an adapter written before 1.7 still compiles. All four first-party adapters and the in-memory store implement it. The hand-back keeps one fifth of the budget for itself, on its own clock, so a handler that ignores cancellation cannot starve it. Log 1205 counts the leases given back, 1206 marks a hand-back that failed, and 1207 marks a drain that ran out of budget. BackWave.Hosting now references Microsoft.Extensions.Hosting to read HostOptions.ShutdownTimeout.
- AddedJob Tag length bounds. MaxTagKeyLength and MaxTagValueLength default to 200, and a longer tag is rejected, never truncated. Enqueue throws ArgumentException. On the storage contract, EnqueueResult and WorkflowEnqueueResult gain TagTooLong, and a store throws JobTagTooLongException for an over-long tag on an outcome. A tag added at outcome time dead-letters that job with Error 2004, and the group keeps running. SQL Server caps the bound at 200 and Oracle at 256, and either one rejects a wider bound when the store is created.
- ChangedThe health check now reports Degraded when some pumps of a group are halted, or when the group runs degraded on transient faults. CAUTION: If a probe treats anything other than Healthy as a failure, update the probe before the upgrade. Such a probe trips on a partial halt after it. Unhealthy is unchanged for a fully halted group. A description carries the group name and the exception type only.
- AddedA public InvariantTrigger enum with twenty named triggers, and a new InvariantViolationException that carries the trigger in its Trigger property. HaltState gains a Trigger init property, and its two-argument constructor is unchanged. A new counter, backwave.invariant.violations, is tagged by trigger and by action, Halt or Degrade. The halt log 2001 now names the trigger, and the new degrade logs 2003 and 1601 record a violation that the group survives. A StaleLease outcome counts as a violation only when the worker believed its lease was live, with a 30 s clock-skew allowance.
- FixedA halt raised inside a handler now sets the process span to Error and records the violation as the span exception. Before, that span closed with no status.
- ChangedAn over-cap Job Output now dead-letters the producing job with Error 2002 instead of halting the group. A store that knows its cap pre-scans a batch and rejects it before any row is written.
- ChangedSchema version skew. A newer schema than the adapter expects is accepted with warning 1303, and an older schema still fail-stops. Migrate first, then roll the nodes. CAUTION: A 1.6 node fail-stops if it restarts against the version 2 schema, because 1.6 checks for an exact match. A 1.6 node that keeps running is not affected. The SQL Server schema is now 2, with a filtered index for lease hand-back. The SQLite schema is now 2, with a high-water mark for transition positions. Postgres stays at 1, and both bumps are additive.
- ChangedThe lease-expiry sweep writes the transition log as one batch. At the default claim batch, a full sweep dropped from 67 round trips to 5 on Postgres and from 68 to 6 on SQL Server. Tests now pin the round-trip budgets on Postgres, SQL Server, SQLite, and Oracle.
- ChangedAddWorkerGroup now also rejects a negative ShutdownBudget, or one above int.MaxValue ms, with InvalidOperationException at registration, next to the existing checks on Name, Pumps, and MaxPollInterval. RetryPolicy.MaxAttempts is bounded to 1 through 1000, the RetryDisposition.MaxAttemptCeiling, and a value outside that range throws ArgumentOutOfRangeException. CAUTION: Lower any policy above 1000 attempts before the upgrade. Such a policy fails at construction after it.
- ChangedThe idle backoff now grows with elapsed idle time, so a burst of empty polls after a drain no longer pushes it to the ceiling at once. Admission is capped at PoolSize, in-flight claims included, so overlapping claim passes never exceed the pool.
- ChangedRetention purges a workflow as a unit. No member is eligible until every member is terminal, and the whole workflow takes the window of its worst member.
- AddedPaged workflow listing. ListWorkflowsAsync(WorkflowListQuery) is on the storage contract and on the Pro monitor. It pages by keyset cursor, newest first by default, and a page is clamped to MaxMonitorPageSize.
- FixedDuplicate parent ids in a job or workflow collapse to the parent set. Before, SQL Server and SQLite failed with a key violation. A workflow whose edges form a cycle is rejected with a named invariant and leaves nothing inserted. A lease-expiry sweep records a parent transition before its cascaded children.
- AddedThe observer delivery report carries a lease fence: BelievedLeaseExpiry, TryReportObserverDeliveriesAsync, and ObserverReportOutcome. A rejected report aborts the delivery without moving the cursor, so the rows redeliver. A store that wraps another store must forward the Try method.
- AddedDebug log 1208 records an outcome that the store fenced out on a lapsed lease. This is an ordinary race and counts no invariant.
- FixedAfter an outage longer than the catch-up window, a schedule mints its next due tick in the first poll instead of after the backlog drains.
- FixedBW0004 now names the payload type instead of the wire name.
BackWave.Oracle
- AddedA storage adapter for Oracle 19c or later, on the ODP.NET managed driver, for net8.0 through net10.0. Register it with UseStore(new OracleJobStore(...)), where the schema is the owning user and AutoMigrate is off by default. Transactional enqueue enlists in an OracleTransaction. Opt-in Wake-Up Hints use DBMS_ALERT: they need an EXECUTE grant and park one session per pump. In a test with a 5 s poll, hints cut pickup to about 138 ms. If the channel fails, the enqueue still succeeds, and Warning 1501 marks the outage once while polling carries the work. The adapter is not Native AOT compatible: the driver loads types by name on its connection paths, and the adapter serializes its batch payloads in reflection mode. It ships under the same free license as the other adapters.
BackWave.Sqlite
- FixedTransition-log positions survive a retention purge. Before, a purge that removed the highest rows let the store reuse a position. An Observer whose cursor sat above the new maximum then never received another transition. DisposeAsync now clears the connection pool, so the database file can be deleted or moved. The lease sweep writes the transition log as one batch, and the per-row transition writer skips its prune under MaxTransitionsPerJob.
BackWave.SqlServer
- FixedThe store makes up to four attempts on deadlock 1205 inside claim, outcome, sweep, and hand-back, and counts each absorbed deadlock in backwave.store.faults. The lease sweep writes the transition log first, which removed the deadlocks a six-sweeper test produced. Heartbeat and hand-back id lists pass as one JSON parameter, so a worker with more than 2,100 leased jobs no longer trips the parameter limit.
BackWave.Pro.Dashboard
- ChangedThe Workflows page reads one page at a time, newest first, with a Next page link that carries the keyset cursor in the query string. Before, it loaded every workflow.
BackWave.Conformance
- ChangedCAUTION: Override the abstract Capabilities property with a ConformanceCapabilities flags value in every suite subclass. The virtual booleans ComputesNextDue and BatchOutcomesAreAtomic are removed, so a subclass written for 1.6 does not compile. A declared capability whose hook returns its default fails the clause. An optional ITestOutputHelper constructor logs the skipped clauses. The suite has 200 clauses.
BackWave.Pro.Mcp
- ChangedThe list_workflows after_cursor is an opaque keyset position that a client passes back verbatim, and an old GUID-only cursor is rejected. The queue-depth State description lists the seven real states.
BackWave.OpenTelemetry
- AddedAddBackWaveOracleInstrumentation() on the tracer and meter builders. It subscribes by the source name BackWave.Oracle.
v1.6.0
- AddedA per-job-type retry override. Put [Retry(maxAttempts, backoffSeconds...)] on a [Job] type or method. That job then retries on its own ceiling and its own backoff list instead of the Worker Group policy. A job with no attribute is unchanged. The list repeats its last interval when it is shorter than the ceiling. The source generator validates the attribute at compile time: BW0008 for a ceiling outside 1 to 1000, BW0009 for an invalid backoff list, and BW0010 for a [Retry] on a type with no [Job]. Nothing new is stored, so no migration is needed. A job that dies by lease expiry still uses the group policy for that attempt.
- AddedAdaptive idle-poll backoff. Set MaxPollInterval above PollInterval on a Worker Group and an idle group stretches its poll delay toward that ceiling instead of the fixed rate. When the store reports the instant the next scheduled job is due, the group sleeps until then. It snaps back to PollInterval the moment a poll claims work, or the store reports work due now. The ceiling bounds worst-case pickup latency, so polling stays the sole correctness mechanism. It defaults to TimeSpan.Zero (off), and the default path issues exactly the queries it issued before.
- AddedA ClaimBatchAsync method on the storage contract. It returns the claimed jobs plus an advisory NextDue, the earliest future instant at which an empty claim can return work. It is a default interface method that reports null, so an adapter written before 1.6 compiles and behaves as before. All four first-party adapters compute it, and the Conformance Suite certifies the semantics.
v1.5.0
- AddedThe send span now carries the enqueue call site as OpenTelemetry code.* tags: code.function.name, code.file.path, and code.line.number, so a job in your trace points back at the line that enqueued it. The values come from compiler caller-info attributes, so they are Native AOT-safe and cost nothing at runtime. The testing harness forwards the call site as well, so it survives the harness wrapper rather than naming it.
- FixedW3C tracestate now survives the enqueue-to-execute hop. A Job stored only the traceparent, so the vendor routing and sampling state carried in tracestate was dropped. The process span’s link back to the send context now restores both parts. A tracestate too large for the narrowest store column is dropped instead, so an oversized one never fails the enqueue.
v1.4.0
- AddedA reflection-free, Native AOT-compatible core. BackWave, BackWave.Hosting, BackWave.Postgres, BackWave.OpenTelemetry, and BackWave.Pro.Mcp are certified trim- and AOT-safe, so they build warning-free and run under a published Native AOT host with no dynamic-code fallback. The dashboard also renders correctly under a consumer’s Native AOT publish.
- AddedBackWave.OpenTelemetry, a new registration package. A single AddBackWaveInstrumentation() call, defined on both the tracer- and meter-provider builders, subscribes BackWave’s job-lifecycle traces and metrics to your existing OpenTelemetry pipeline. It references only the OpenTelemetry API and subscribes by source name, so it pulls no BackWave assembly into your build.
- AddedA structured-logs pillar, the third alongside traces and metrics. A source-generated log catalog with stable event ids across Core, Hosting, and Pro emits through ILogger and auto-bridges into OpenTelemetry Logs. It is off by default and allocates nothing until a listener attaches.
- AddedPer-adapter database store spans (db.* attributes) and a backwave.store.faults meter for PostgreSQL, SQL Server, and SQLite, opt-in per adapter so the chattier store signals never crowd the job-lifecycle view unless you ask for them.
- AddedNew lifecycle metrics: schedule-delay, queue-wait, and observer-dispatch duration histograms, a dead-letter counter, worker-slot active and capacity instruments that join by consumer group, and an error.type dimension on the failure counter.
- ChangedBackWave’s job-lifecycle telemetry now follows the OpenTelemetry messaging semantic conventions, so it lines up with the rest of your OTel data out of the box. Enqueue emits a send span, claim a receive span, and execute a process span, all carrying standard messaging.* attributes; throughput reports on messaging.client.sent.messages and messaging.client.consumed.messages, and execution latency on the messaging.process.duration histogram in seconds.
- ChangedEnqueue-to-execute correlation is now carried as a span link, so a process span links back to its originating enqueue and a workflow fan-in step links to every upstream step it joined, correlation a single parent edge cannot express. It degrades safely: a Job enqueued with no ambient trace still starts a clean root span.
v1.3.0
- AddedWorkflows v2: a strongly-typed workflow builder that references each step by its .NET type instead of a string name, so a renamed or moved step becomes a compile error rather than a runtime surprise. It lowers to the same underlying graph the previous builder produced, so storage, adapters, and the determinism boundary are unchanged.
- AddedTyped Job Output. A step reads a parent’s result with ctx.Output<TStep, TOut>() and publishes its own with SetOutput, both checked at compile time.
- AddedAn immutable Workflow Input seed, passed once when the workflow is enqueued and readable from any step, so a graph no longer needs a first step whose only purpose is to carry parameters.
- AddedTyped parallel fan-out and fan-in for running independent steps at once and joining their results.
- AddedSaga compensation with .WithCompensation, so a step can register the undo that runs if a later step fails.
- AddedChild workflows with .ThenWorkflow, spliced inline into one flat graph.
- AddedConditional branching with .If, wired as a gate step whose predicate can read the input seed; the arm that is not taken is cancelled rather than left pending.
- AddedA span per workflow step on the existing OpenTelemetry trace, tagged with the workflow name, member count, and dependencies.
- AddedA BW0007 analyzer with a one-click fix that flags a workflow or output type missing from your JsonSerializerContext and adds it for you.
v1.2.0
- AddedA distributed lock that coordinates cold-boot migration. When a whole fleet starts at once, one node acquires the lock and migrates while the rest wait, so concurrent startup can no longer race the schema. Postgres uses a transaction-scoped advisory lock, SQL Server sp_getapplock, and SQLite a single-writer transaction. SQL Server also retries transient cold-boot faults.
- AddedBackWave.Pro.Mcp, a bundled Model Context Protocol server that exposes BackWave to AI agents. Its 23 tools sit behind a per-tool gate: writes and sensitive tools stay hidden and fail closed unless you enable them.
- AddedA live metrics panel on the dashboard for in-process throughput, top and faulting endpoints, and approximate p95/p99 latency, wired up through the AddBackWave block.
- AddedA backwave.job.duration OpenTelemetry histogram emitted at the execute edge.
- AddedAn injectable TimeProvider threaded through the worker and observer pumps, so you can drive time from your host and in tests.
- AddedTag Suggest on the dashboard: case-insensitive prefix completion for job tags, with full keyboard navigation.
- AddedThe Observers page now surfaces delivery lag, and cron schedules render as friendly labels.
- ChangedThe dashboard now follows the host page’s light and dark theme and honors reduced-motion preferences.
- ChangedThe dashboard layout is now responsive down to narrow widths: tables reflow, filter rows stack, and card headers stack.
- ChangedThe Failures page splits into Dead-Lettered and Quarantine tabs, shows job tags, and adds a copy button on the Failure Detail.
- ChangedConsolidated each adapter’s migrations into a single v1 schema baseline. Existing databases continue to upgrade in place.
- ChangedAligned the free-use and Pro license text with the terms published on the site.
- FixedGenerate a valid deserializer for a job that has no payload members.
- FixedThe live dashboard stream now closes on navigation and preserves scroll position across live updates.
- FixedReport an unknown observer outside the reader scope, correcting a reader-scope error on the PostgreSQL and SQL Server adapters.
v1.1.1
- FixedCorrected package version pins.
v1.1.0
- AddedSupport for .NET 8 and .NET 9 (previously .NET 10 only).
v1.0.0
- AddedTyped jobs and handlers. Attribute a method with [Job] and the source generator emits its payload, handler, and a wire name that survives refactors.
- AddedScheduling. Run a job now, hold it for later, or put it on a recurring cron schedule.
- AddedWorkflows. Compose jobs into a cancellable, restartable DAG where a step can read its parents’ output.
- AddedTransactional enqueue. Jobs enlist in your own database transaction, so they commit or roll back with your business write, with no outbox to maintain.
- AddedStorage on the database you already run, with adapters for PostgreSQL, SQL Server, and SQLite.
- AddedA write-capable dashboard. Inspect queues and job timelines, then requeue, cancel, or pause a queue, with every action authorized by your own app.
- AddedDeterministic testing. Exercise scheduling, retries, and workflows under a virtual clock and assert the outcomes like ordinary code.