# Reference patch — 02-per-entry-ttl

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** `TtlLruCache.java` (in this directory) to `reference/src/`. New file, so
   `measureChange` charges nothing for it. It implements `LruCacheApi` by wrapping any other
   `LruCacheApi` — in practice, an ordinary `LruCache` — and tracking each ttl'd key's expiry
   itself, in a private `Map<String, Instant>` the delegate never sees. `LruCache` itself gains no
   knowledge of `Clock`, `Duration`, or expiry at all.
2. **Add one unreachable method** to `reference/src/LruCache.java` — see `edits.json` beside this
   file for the exact text. `LruCacheApi` widened to add `put(String, Object, Duration)`, and
   `LruCache` declares `implements LruCacheApi` directly, so Java requires a body for every method
   on the interface it implements, including one this class has no real use for. The body throws
   `UnsupportedOperationException`, because the only way to reach it is to bind `LruCache` to the
   widened interface directly instead of going through the decorator `Entry.create(int, Clock)`
   actually returns — which no test in this curveball's suite, or the base suite, ever does.
3. **Add** `contract-delta/LruCacheApi.java`, `Clock.java`, `ManualClock.java` and `Entry.java`
   (the full revised files) 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.

Measured: `reference_diff = 9` (9 added, 0 removed, one file touched: `LruCache.java`), so
`diff_budget = max(3, 9 + min(ceil(9/2), 6)) = 14`.

Both the curveball suite (6 tests) and the base suite (16 tests) are green after the patch.

## Why the stub method is charged, and why that is fair rather than a design flaw

This is the same shape `movie-booking`'s curveball 02 (`holdAdjacent`) charges for when a
contract's interface widens and an existing class implements that interface directly: the cost is
not optional, and hiding it by only measuring the new file would misreport what widening a shared
interface actually costs a design that already committed to implementing it concretely. The
alternative — never having `LruCache` implement the full `LruCacheApi` in the first place, and
routing every base call through a thin adapter — was considered and rejected for the base
reference, because it would have meant paying an indirection cost in the 84% of this corpus's
problems that never grow a second `put` overload, to save nine lines in the one curveball that
does. Nine lines, once, is the cheaper trade.

## Where the requirement actually landed

Not in `LruCache` at all, beyond the forced stub. `TtlLruCache` is a pure decorator built entirely
on top of the existing `LruCacheApi` surface — the same shape `logger`'s async-appenders curveball
and `movie-booking`'s occupancy-surge-pricing curveball both use. `get` and `containsKey` are
where the actual requirement lives: each checks this decorator's own expiry map before asking the
delegate anything, and reports a miss without ever touching the delegate when a key has expired.

**The one deliberate simplification, stated rather than hidden.** `TtlLruCache` never proactively
removes an expired key from the delegate — it has no way to, because `LruCacheApi` exposes no
"remove a specific key" operation, only `put` (which means something else) and eviction (which the
delegate drives on its own). An expired-but-undiscovered key keeps occupying a capacity slot until
something overwrites it or the delegate's own recency-based eviction reclaims it under pressure.
This is the honest cost of adding expiry without inventing a removal method the base problem never
asked for, and it is why `expiryIsNotEviction`'s assertion is written the way it is: expiry is
observable through `get` and `containsKey` only, never through `size()`, and the tests do not
claim otherwise.
