Skip to content

feat: opt-in last_exception redaction hook for the DLQ (F3-01)#93

Merged
lesnik512 merged 1 commit into
mainfrom
feat/f3-01-last-exception-renderer
Jun 14, 2026
Merged

feat: opt-in last_exception redaction hook for the DLQ (F3-01)#93
lesnik512 merged 1 commit into
mainfrom
feat/f3-01-last-exception-renderer

Conversation

@lesnik512

Copy link
Copy Markdown
Member

What

PR 4 of the tail program. Closes F3-01: the DLQ stores repr(exc) verbatim, and for a poison message that repr can embed the request body / rejected row / credentials — durably, in the audit table.

Adds an opt-in last_exception_renderer: Callable[[BaseException], str | None] to OutboxBroker (and, for parity, the FastAPI OutboxRouter):

# Redact: store only the class name.
OutboxBroker(engine, outbox_table=t, dlq_table=dlq,
             last_exception_renderer=lambda exc: type(exc).__name__)

# Drop entirely:
OutboxBroker(..., last_exception_renderer=lambda exc: None)
  • Default (None) keeps the full repr for forensic detail.
  • The renderer's output is still length-capped at 8 KiB.
  • Rendering centralized in _render_last_exception (replacing _truncate_exception), read from OutboxBrokerConfig.last_exception_renderer.

Backward-compatible: with no renderer configured, every existing path is byte-for-byte identical (the existing truncation tests still pass unchanged).

Tests

  • test_fake_dlq_redacts_last_exception_with_renderer — class-name-only renderer; asserts the secret-bearing message is absent.
  • test_fake_dlq_drops_last_exception_when_renderer_returns_noneNone → stored None.
  • Existing test_flush_terminal_dlq_payload_{truncates,short} cover the default repr path.
  • just test516 passed, 100% coverage; just lint clean.

Docs

CLAUDE.md (DLQ invariants) and docs/usage/dlq.md (new "Redacting last_exception" section) updated.

🤖 Generated with Claude Code

The DLQ stores repr(exc) verbatim, which for poison messages can embed the request
body / rejected row / credentials — durably, in the audit table. Add an opt-in
last_exception_renderer: Callable[[BaseException], str | None] on OutboxBroker (and
the FastAPI OutboxRouter) so PII-sensitive deployments can redact
(lambda exc: type(exc).__name__) or drop it (return None). Default stays repr for
full forensic detail; the rendered string is still length-capped at 8 KiB.

Rendering is centralized in _render_last_exception (replacing _truncate_exception),
read from OutboxBrokerConfig.last_exception_renderer. Backward-compatible: with no
renderer, every existing path is byte-for-byte identical.

Tests: test_fake_dlq_{redacts_last_exception_with_renderer,drops_last_exception_when_renderer_returns_none};
existing truncation tests still cover the default path. just test -> 516 passed, 100%
coverage; just lint clean. CLAUDE.md + docs/usage/dlq.md updated.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@lesnik512 lesnik512 merged commit d155001 into main Jun 14, 2026
3 checks passed
@lesnik512 lesnik512 deleted the feat/f3-01-last-exception-renderer branch June 14, 2026 14:06
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