Skip to main content

Changelog

This page records issues discovered and resolved while hardening the library. All entries are fully resolved — none represent open bugs. The detailed engineering notes for each fix live in docs/known-issues/ (mirrors the GitHub bug-report template; kept as a developer reference).

Core DI

IDSummarySeverity
001Open-generic pipeline behavior registrations throw at runtime under Native AOT when TResponse is a value typeHigh
002ValidateOnBuild = false does not suppress the issue-001 runtime errorMedium
004AddValidator registers a validator that is never wired into the pipeline — validation silently never runsHigh
015EnableCqrsBoundaryEnforcement() was an [Obsolete] no-op; runtime-API callers silently lost all enforcementHigh
019Behavior dedup compared (ServiceType, ImplementationType) only — lifetime-blind, skipped factory/instance registrationsMedium
029Scoped IContext cached a boxed snapshot while WithCorrelationId returned a new box, so a pre-middleware reader and the response header could report different correlation IDs; identity is now fixed at creation via IInboundContextStoreMedium
035Context held baggage and features in plain dictionaries while the concurrent event orchestrator runs every handler against that one instance, so entries were lost, the byte counter drifted and enumeration threw; both stores are now copy-on-writeHigh
036ContextHandler built its context lazily without synchronization, so concurrent handlers in one scope each minted their own trace id and only one won the field; creation is now locked and double-checkedHigh

Source Generator

IDSummarySeverity
005Generator emits an uncompilable type name for nested handler/behavior classes (drops enclosing-type chain)High
006Open-generic behavior closed with the interface's arity, not the class's own → CS0305High
012GlobalizeType emits invalid global:: prefix for tuple / pointer / function-pointer typesMedium
017SplitTopLevelArgs ignored tuple () — a tuple nested as a generic argument emitted uncompilable codeMedium
022[Validator] derived the response type from the first IRequest<T> found; multi-IRequest requests now emit diagnostic MDG011Low
024Open-generic behavior special constraints (class/struct/unmanaged/notnull/new()) were dropped, closing the behavior over non-conforming handlers → uncompilable code (CS0453); now captured as equatable flags and enforced in the cross-productHigh
025Generated RegisterGroup namespace was derived from the assembly name, ignoring MSBuild RootNamespace; it now reads build_property.RootNamespace first (and a new [RegisterGroup] partial class lets users pick the namespace/name explicitly)Medium
030A generic IEvent/IEventHandler<T> declaration was collected for dispatcher registration, emitting typeof(Event<T>) — a type parameter not in scope — so RegisterGroup.g.cs did not compile; generic definitions are now skipped and the last hand-rolled globalizer was deletedMedium
034Issue 030's guard tested the event's own arity, so a non-generic event nested in a generic type still emitted typeof(Box<T>.Opened) and did not compile; the containing-type chain is now inspected tooMedium

Pipeline & CQRS

IDSummarySeverity
008Cross-assembly open-generic behavior is registered (and runs) twice — no dedup on the user-behavior pathHigh
009Order is only a generator-local sort; runtime order follows DI registration across sourcesMedium
013Invoker overloads resolve handlers inconsistently (static TRequest vs runtime request.GetType())Medium
016CQRS boundary marker leaked when a handler threw — spurious violation on later send in the same scopeMedium
018CQRS enforcement was emitted per-discovered-handler only — manually registered handlers were not coveredMedium
021EventDispatcher re-sorted behaviors via OrderBy on every publish (per-publish allocation)Low

Outbox

IDSummarySeverity
010Outbox event lookup narrowed to ReferenceEquals — broke the public contract for equal-but-distinct eventsMedium
014Outbox lookup matched the first value-equal event across all scopes; stored items now carry a stable Guid identity via OutboxEntryHigh
020Outbox "failed count" counted retrying events (Attempts > 0), tripping the health check on transient retriesMedium
023RecordOutbox* metric methods were dead no-ops on ISynapseMetrics (superseded by observable gauges); removedLow
040Inject wrote trace context only when Activity.Current existed, so on a host with no tracing an outbox entry was stored with no traceparent and its dispatch became a disconnected root; the header is now derived from IContext.TraceIdMedium
041Rebuilding an entry's headers with ToDictionary threw on keys differing only by case, from outside the try, so one badly-shaped row aborted the whole batch with nothing marked processed or failedLow
042Dispatch used the entry's restored flow only to parent the activity: the stored baggage was dropped and handlers read the committing scope's IContext, so entries were dispatched under whichever request called CommitAsync; each entry now gets its own scope built from its own stateHigh

Observability

IDSummarySeverity
007Outbox observable gauges are never registered; Record* are no-ops so outbox metrics emit nothingHigh
011Tracing capture stores zero/invalid trace IDs (empty-string guard never fires) and stringifies twiceMedium
028LoggingEnrichmentBehavior copied all context metadata into the log scope, so the internal __CQRSBoundaryEnforcement markers appeared in every log entry; the markers are now a context feature and enrichment reads typed identity plus baggageMedium
031ContextIdentity.ForUnitOfWork read Activity.Current.TraceId without checking it was set, and an all-zeros trace id stringifies non-empty, so contexts carried 000…0; the ambient id is now taken only when non-defaultLow
033SynapsePropagationHandler injected a scoped IContextAccessor, but IHttpClientFactory caches message handlers in a scope of its own, so outbound propagation silently stamped nothing; the context is now mirrored onto the execution contextHigh
037Inject let the platform propagator write its own baggage header from Activity.Baggage, so inbound baggage untrusted mode had dropped was forwarded anyway; the platform's baggage output is now discarded and the context is the only sourceHigh
038IsValidValue refused commas in decoded values although values are percent-encoded, so SetBaggage("k", "a,b") was rejected and a conformant peer's Acme%2C%20Inc was silently dropped inbound; only control characters are refused nowMedium
039Parse charged the byte budget for every repetition of a key even though a repetition overwrites, dropping valid entries after it, and MeasureEntry measured the decoded value so escaped-heavy baggage exceeded the limit on the wireMedium
043Inject wrote the ambient activity's trace id rather than IContext.TraceId, so an untrusted edge propagated the caller's forged trace id onto outbound calls and into outbox entries; the context now wins when the two divergeMedium

ASP.NET Core

IDSummarySeverity
003Pipeline short-circuit via Result.Failure<T>() mapped to HTTP 500; behavior now returns a typed UnauthorizedFailure → 401Medium
026Guid.Empty was accepted as an inbound correlation ID, colliding with the "not set" sentinel and the uninitialized outbox partition; extraction now requires a non-empty Guid and rejects duplicated headersMedium
027The response writer read IContextAccessor.Context, which creates a context, so non-mediator routes got a fabricated correlation ID and the guarding catch was unreachable; a new IsInitialized lets it skip insteadLow
032TrustIncomingHeader = false cleared the inbound trace context but the host had already parented the request Activity to the caller, so the forged trace id was read back from Activity.Current; a new SuppressAmbientTrace flag disqualifies the ambient activityHigh

Discovery context: Issues 001–003 were found while building the pipeline-behavior showcase in examples/MinimalApi on branch feature/typed-pipeline-behaviors against .NET 10 with PublishAot=true. Issues 004–023 were found in successive high-effort code reviews of the same branch (diff + working-tree, multiple finder angles). All are resolved. Line numbers in the individual files are approximate and may drift as the branch evolves.