Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

ADR-0007: Redaction normalises both sides instead of special-casing matchers

Status: Accepted · Reference: PLAN.md §7 decision 9

Context

Redaction replaces a secret with a placeholder before the interaction reaches disk. The cassette then holds Authorization: Bearer <REDACTED>. On the next run the live request still carries the real token — so a header matcher comparing the two sees Bearer sk_live_abc… against Bearer <REDACTED> and reports a mismatch. The cassette is correct, the request is correct, and matching fails anyway.

Two ways out. Teach the matchers about redaction — every matcher gains a special case for placeholder values. Or make sure both sides look the same by the time matching happens.

Decision

Both sides are normalised. The incoming request goes through the same redaction rules before it is compared, so a redacted field is compared placeholder-to-placeholder. Matchers know nothing about redaction at all.

Consequences

Good. RequestMatcherInterface stays a pure comparison of two RecordedRequests, which is what makes it plausible to implement from outside. A custom matcher inherits correct redaction behaviour without knowing redaction exists. Adding a new redaction target does not mean revisiting every matcher.

Bad. A redacted field stops distinguishing requests. If two requests differ only in a redacted header, they now look identical and the first recorded interaction answers both. This is real and occasionally surprising — includeSensitiveHeaders() exists precisely for it, storing the header as sent so it can tell requests apart again.

Two-way rules. A rule with a value callback restores the real value on playback, so the code under test receives what it expects. A field is only restored when it holds exactly the placeholder — a partially-matching value is left alone rather than guessed at.