# Reference patch — 03-write-through-backing-store

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

1. **Add** `ReadThroughCache.java` (in this directory) to `reference/src/`. New file, so
   `measureChange` charges nothing for it. It implements `LruCacheApi` by wrapping any other
   `LruCacheApi` and a `BackingStore`, and its constructor registers the store as an ordinary
   `EvictionListener` on the delegate — write-through costs nothing extra to build because it is
   exactly what that seam is for.
2. **Add** `contract-delta/BackingStore.java` and `Entry.java` (the full revised entry point, with
   a third factory added beside the existing two) to the candidate's contract. Not counted in the
   measured diff — a contract extension is free, and only the reference's own absorption of it is
   charged. `LruCacheApi` itself is untouched by this curveball — no new delta file for it — which
   is the whole reason `reference/src/LruCache.java` never had to change either.

Measured: `reference_diff = 0` (0 added, 0 removed, 0 files touched, 1 new file), so
`diff_budget = max(3, 0 + min(ceil(0/2), 6)) = 3`.

Both the curveball suite (5 tests) and the base suite (16 tests) are green after the patch, with
no edit to any existing file.

## Why write-through was free, and read-through was not

The requirement has two halves, and they land very differently:

- **Write-through** (evict → store) is the `EvictionListener` seam, used for exactly the purpose
  its own javadoc already describes — "notified when the cache drops an entry to stay within
  capacity" — by a caller nobody anticipated by name, which is the actual test of whether a seam
  earns its place rather than merely existing. `ReadThroughCache`'s constructor is the entire
  implementation of this half: one lambda, registered once.
- **Read-through** (miss → store → repopulate) is genuinely new behaviour that `LruCacheApi` never
  had a hook for, because nothing in the base contract anticipated a second source of truth behind
  the cache. It could not be built by composing an existing seam the way write-through was; it had
  to be written, in `get`, as the one real piece of new logic this curveball adds. That it still
  cost zero *charged* lines is because it lives entirely inside the new file, not because it was
  free to design.

## The one deliberate asymmetry, stated rather than left implicit

`get` on a store hit pulls the entry back into the cache (a real read, so it refreshes standing
like any other); `containsKey` on a store hit reports present without pulling anything in, because
`containsKey` is defined everywhere else in this contract as a pure check that never changes what
the cache is holding (rule 4), and a presence check that silently populated the cache would be the
exact thing that rule exists to rule out. `containsKeyChecksTheStoreWithoutRepopulating` is the
test that pins this down: it asserts `size() == 0` after a `containsKey` call that returns `true`.
