Skip to content

fix: don't propagate envelope-managed headers onto a chained OutboxResponse (F5-01/F5-02)#85

Merged
lesnik512 merged 1 commit into
mainfrom
fix/f5-01-propagate-inbound-headers-envelope-keys
Jun 14, 2026
Merged

fix: don't propagate envelope-managed headers onto a chained OutboxResponse (F5-01/F5-02)#85
lesnik512 merged 1 commit into
mainfrom
fix/f5-01-propagate-inbound-headers-envelope-keys

Conversation

@lesnik512

Copy link
Copy Markdown
Member

What

When propagate_inbound_headers=True, the subscriber copied the inbound row's full header dict onto a handler's OutboxResponse — including the envelope-managed content-type and correlation_id.

A chained OutboxResponse re-encodes through _encode_payload, which re-derives content-type from the new body and reads correlation_id from the dedicated field. The propagated inbound values then collided there, raising ValueError and nacking the successful inbound row to retry-exhaustion on:

  • any cross-content-type relay (e.g. inbound strtext/plain, response dictapplication/json) — F5-01 (audit-rated High)
  • any relay where the handler set an explicit OutboxResponse(correlation_id=...)F5-02 (Medium)

Found by the 2026-06-14 pass-3 deep audit (planning/audits/2026-06-14-deep-audit-pass3-findings.md); two prior same-day passes called the relay path clean.

Fix

Extract _maybe_propagate_inbound_headers and strip content-type/correlation_id from the propagated headers only when the result is an OutboxResponse (the path that re-encodes). Foreign-publisher relays (Kafka/etc.) don't re-encode through the outbox envelope, so they keep forwarding these headers verbatim — the existing Kafka content-type-forwarding contract (test_relay.py) is preserved.

The extraction also keeps process_message under the PLR0912 branch limit rather than suppressing it.

Tests

Test-first. Two regression tests in tests/test_fake.py (cross-content-type + custom-correlation_id outbox→outbox relay) — both fail before the fix (relay never delivers), pass after.

  • just test497 passed, 100% coverage
  • just lint clean (ruff + ty)

Docs

  • architecture/relay.md — records the envelope-key-stripping invariant.
  • Pass-3 findings doc — F5-01/F5-02 marked RESOLVED; F5-03 (no distinguishing signal for non-encode relay-publish failures) noted as the remaining open residual.

🤖 Generated with Claude Code

…sponse (F5-01/F5-02)

With propagate_inbound_headers=True, the subscriber copied the inbound row's
full header dict onto a handler's OutboxResponse, including the envelope-managed
content-type and correlation_id. A chained OutboxResponse re-encodes through
_encode_payload, which re-derives content-type from the new body and reads
correlation_id from the dedicated field; the propagated inbound values then
collided there, raising ValueError and nacking the *successful* inbound row to
retry-exhaustion on any cross-content-type or custom-correlation_id relay.

Extract _maybe_propagate_inbound_headers and strip content-type/correlation_id
from the propagated headers when the result is an OutboxResponse. Foreign-publisher
relays (Kafka/etc.) don't re-encode through the outbox envelope, so they keep
forwarding these headers verbatim.

Regression tests in tests/test_fake.py; invariant recorded in architecture/relay.md;
pass-3 audit findings marked resolved.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@lesnik512 lesnik512 merged commit bf9945a into main Jun 14, 2026
3 checks passed
@lesnik512 lesnik512 deleted the fix/f5-01-propagate-inbound-headers-envelope-keys branch June 14, 2026 12:06
lesnik512 added a commit that referenced this pull request Jun 14, 2026
)

Documentation-only. Adds the clarifications the pass-3 audit asked for and records the
final disposition of every remaining tail finding.

CLAUDE.md:
- F2-09 within-batch dispatch order is unspecified (ORDER BY governs selection, not dispatch)
- F1-01 true lease bound is fetch_batch_size + max_workers, not fetch_batch_size
- F2-04/F2-05 activate_at / publish_batch NOTIFY timing is worker-clock-relative
- F1-07 NOTIFYs emitted during a reconnect window are lost (latency, not correctness)
- F2-07 deliveries_count counts claims, not handler runs
- F2-01 max_total_delay_seconds is a lower bound (allows ~one more interval)
- F4-09 test-broker session leniency on publish/cancel_timer/fetch_unprocessed

docs/operations/troubleshooting.md:
- F3-03 recommended alerts (row count, lease_lost rate, dlq_written divergence)
- F5-03 residual: a chained-OutboxResponse publish failure surfaces as a handler-failure
  nack with no distinct signal (new entry + index row)

planning/audits: appended a "Tail disposition" ledger — resolved (#85-#94), documented
(#94), won't-do with rationale (F4-03, F6-06, F1-02, F2-02-residual, F5-03-residual,
F5-04, F3-04), deferred (#95: F7-07, F7-09, F1-08).

just lint clean; mkdocs build --strict passes; non-PG tests green.

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
lesnik512 added a commit that referenced this pull request Jun 14, 2026
…t adapters) (#97)

Covers PRs #85-#96: the F5-01 High fix, the F8-01 FastAPI dlq/recorder forwarding and
F3-01 last_exception_renderer features, the drain_timeout metric, the robustness/validation
fixes, and the test-hardening + docs sweep. Three behavior notes flagged for upgraders.

Tag-driven release: pushing the bare `0.10.0` tag publishes to PyPI.

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
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