Skip to content

fix(messaging): Fault<T> payload shape, consume-side header fidelity, and TTL expiration#306

Merged
Vulthil merged 1 commit into
mainfrom
fix/messaging-wire-fidelity
Jul 9, 2026
Merged

fix(messaging): Fault<T> payload shape, consume-side header fidelity, and TTL expiration#306
Vulthil merged 1 commit into
mainfrom
fix/messaging-wire-fidelity

Conversation

@Vulthil

@Vulthil Vulthil commented Jul 9, 2026

Copy link
Copy Markdown
Owner

Summary

Fixes three wire-fidelity defects in the messaging stack so the documented contracts hold against real broker traffic:

  • Fault<T>.Message now carries the original message payload. The RabbitMQ fault publisher used to embed the entire wire envelope, so JsonSerializer.Deserialize<Fault<TMessage>> against a real fault yielded default/missing fields; the only E2E assertion substring-matched an id, which passed because the envelope contained it. The worker now unwraps the envelope (reusing the already-parsed one from dispatch) and the docs' "original message body" promise is true. Bare-JSON (non-Vulthil) deliveries keep the whole-body behavior.
  • Consume-side header values are normalized to stable CLR primitives on every path. Envelope deliveries surfaced JsonElements and bare-AMQP deliveries surfaced byte[] for strings — AddHeader("tenant", "acme") was never a string again on the consume side, and the shape differed by path. Now strings/booleans arrive as string/bool and numbers as int/long/double (narrowest fit) on the envelope path, the bare-AMQP path, and the outbox relay path alike; objects/arrays surface as JsonElement. Contract documented on IMessageContext.Headers.
  • ExpirationTime is anchored to the publish timestamp. The AMQP per-message TTL is relative to publish, but was mapped to UtcNow + ttl at consume time, systematically overstating expiry; it now uses SentTime + ttl when the delivery carries a timestamp, with the old behavior as fallback.
  • Outbox-relayed headers now match a direct publish exactly. The persisted-metadata converter rematerialized integrals as long regardless of width and complex values as raw-JSON strings (re-published as escaped strings). It now aligns with the consume-side normalization and rematerializes complex values as detached JsonElements, so a relayed message's header bytes and consumed types are identical to a directly-published one.

Broker-path outbox rows already stamp TraceParent/TraceState and the relay already restores them; the outbox article now documents that preservation. No public API changes; new helpers are internal.

Observable wire-format changes (release notes)

  • Fault payloads for envelope-wrapped messages: Fault<T>.Message is the original message object, no longer the MessageEnvelope wrapper ({"messageId": …, "message": {…}} nesting is gone).
  • Consumed IMessageContext.Headers values: string/bool/int/long/double primitives (previously JsonElement on envelope deliveries, byte[] strings on bare-AMQP deliveries). Integral values narrower than before where they fit in Int32.
  • IMessageContext.ExpirationTime on TTL-bearing bare-AMQP deliveries: computed from the publish timestamp instead of the consume-time clock (earlier, more accurate instants).
  • Outbox-relayed complex header values: re-published as their original JSON objects/arrays instead of JSON-escaped strings.

Verification

  • Regression tests fail on unfixed main and pass with the fix (verified by reverting src/ and re-running): fault-payload capture through the real worker/channel seam with full Fault<T> deserialization, header-fidelity matrix ({string, int, long, double, bool, Guid-as-sent, complex} × {envelope, bare AMQP, outbox relay, in-memory harness}), TTL-anchoring, and a relayed-equals-direct consumed-headers equivalence test.
  • The E2E fault assertion was strengthened from substring-match to full Fault<PoisonCommand> deserialization.
  • Full solution build clean (TreatWarningsAsErrors + analyzers); messaging, RabbitMQ, outbox, inbox, and TestHarness suites green; PublicAPI files untouched.

Backport to v1.0: no — wire-content changes are minor-appropriate; ships in 1.1.0.

…ormalize header types and TTL expiration across consume paths
@claude

claude Bot commented Jul 9, 2026

Copy link
Copy Markdown

Code review

No issues found. Checked for bugs and CLAUDE.md compliance.

@Vulthil
Vulthil merged commit 2b615be into main Jul 9, 2026
7 of 8 checks passed
@Vulthil
Vulthil deleted the fix/messaging-wire-fidelity branch July 9, 2026 18:37
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.

1 participant