# Reference patch — 01-a-new-channel

The answer key for how the reference absorbs this change, and the thing that makes `budget.json`
reproducible. **Never shipped to the candidate.**

1. **Overlay** `contract-delta/Channel.java` onto `contract/` by filename. That adds the `WHATSAPP`
   constant and is the whole of the contract change. It is not charged to the candidate: contract
   files are given, not owned.
2. **Change one line** in `reference/src/NotificationService.java`, in the convenience constructor
   (mechanised in `edits.json`):

   ```diff
                    new InMemoryDeliveryLog(DUPLICATE_WAIT_LIMIT),
   -                new PreferenceAwareSelection(Set.of(Channel.SMS, Channel.PUSH)),
   +                new PreferenceAwareSelection(Set.of(Channel.SMS, Channel.PUSH, Channel.WHATSAPP)),
                    new RecipientBook(List.of(Channel.EMAIL, Channel.SMS, Channel.PUSH)));
   ```

There is **no new file**. Measured: `reference_diff = 2` (1 added, 1 removed, one file touched), so
`diff_budget = max(3, 2 + ceil(2/2)) = 3`.

## Why it is one line, and one line in the wiring rather than in a rule

Three separate things had to already be true, and each of them is a decision the base reference
defends in its own javadoc:

- **"Which channels are intrusive" is an injected set, not a condition.**
  `PreferenceAwareSelection` is constructed with `Set.of(Channel.SMS, Channel.PUSH)` and asks
  `forbiddenDuringQuietHours.contains(channel)`. Adding a channel to that set is the entire
  quiet-hours requirement. The class is never opened.
- **The fallback order is the recipient's own list.** There is no global channel order anywhere in
  the reference, so "tried in the position the recipient put it in" needs no code at all — the new
  constant simply appears in a list the recipient supplied and `RetryingDelivery` iterates it.
- **The retry rules are keyed on the exception, never on the channel.** `RetryingDelivery`
  distinguishes transient from permanent from undocumented and has no idea which channels exist, so
  "the same retry schedule for the new one" is free.
- **The default order is a separate constructor argument.** `new RecipientBook(List.of(EMAIL, SMS,
  PUSH))` is untouched, which is exactly what makes the new channel opt-in. Note that these two
  arguments would have been the *same list* in a design that had one notion of "all channels" — and
  then "intrusive" and "given to everybody by default" could not have been answered differently.

## What it costs a design without those seams

"SMS and push buzz a phone" is the line that normally gets written as
`if (channel == Channel.SMS || channel == Channel.PUSH)` inside whatever decides quiet hours. That is
one edit. But `Channel.values()` also tends to get used as "the channels we support", so the default
order silently grows a fourth entry and everybody who never configured anything starts getting
WhatsApp — which is the failure `the new channel is opt-in, so an unconfigured recipient is
unaffected` catches, and it is a failure a candidate does not see coming because it is an omission
rather than a mistake. Add a `switch` in the retry policy for "how many times do we try WhatsApp" and
it is three edits in three files with a product rule spread across them. Realistically 8–14 lines and
a new consistency risk every time marketing signs another deal; this reference pays 2 and buys none.

**Both suites are green after the patch** — the curveball suite (6 tests) and the base suite (22
tests). The base suite is unaffected on purpose rather than by luck: it only ever names the three
original channels and asserts the default order explicitly, so a change that grew the default would
have been caught there too.
