# Reference patch — 02-method-aware-routing

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/MiddlewareRouterApi.java` onto `contract/MiddlewareRouterApi.java`
   — it adds `register(String method, String pattern, Handler handler)`. Contract files are
   given, so nothing here is charged.
2. **Apply** `edits.json` to `CompiledRoute.java` and `Router.java`. No new file this time — the
   new capability is a new dimension on an existing concept (which method a route accepts), not a
   new kind of object.
3. `SpecificityMatchPolicy.java` and `PatternCompiler.java` are untouched.

Measured: `reference_diff = 47` (`CompiledRoute.java` +11/-1, `Router.java` +25/-10), so
`diff_budget = max(3, 47 + ceil(47/2)) = 71`. Both the curveball suite (6 tests) and the base
suite (23 tests) are green after the patch.

## Why this is the priciest of the three curveballs, honestly

The catch-all curveball added a new *value* the matcher could see (a fourth segment kind); this
one adds a new *dimension* to precedence itself — method, ahead of path shape — and that has to
be reasoned about in the one place precedence is decided, which is more structural than adding a
case to a `switch`.

**What stayed untouched, and why:**
- **`SpecificityMatchPolicy`** needed no change. `dispatch()` still calls
  `matchPolicy.pickBest(...)` — it just calls it on a smaller, pre-filtered pool. The comparator
  itself has no idea method-awareness exists, which is the point of injecting it: precedence-by-
  path and precedence-by-method are two separate concerns, and only `Router` needs to know both.
- **`match(String)`** needed no change either, which is *why* it costs zero of the 47 lines
  despite being the most-quoted method in the contract. It was written to answer "what would
  match this path" and nothing else; `dispatch()` now answers a different, narrower question
  ("what serves this request") using a method it does not share with `match`, on purpose — see
  the contract delta's "match(path) is unaffected" note.
- **`PatternCompiler`** needed no change: a method is not part of a pattern string, so nothing
  about splitting or classifying segments moved.

**What had to change, and why it's not smaller:**
- `CompiledRoute` needed a `method` field, because "does this route accept this request's method"
  is a fact about the route, not about the router — asking `Router` to keep a side table mapping
  routes to methods would have been the same fact, filed in a worse place.
- `Router.dispatch()` grew from "delegate to `match`, then look the handler up" to "filter by
  method, split into a method-specific pool and an any-method pool, prefer the specific pool when
  it is non-empty, then hand whichever pool survives to the unchanged policy." That is a real
  algorithm, not boilerplate, and it is the whole answer to "what happens when a method-specific
  route and an any-method route are both compatible" — the second-hardest question the
  requirement asks (the first, method-only-404, falls out for free: an empty pool already meant
  404 before this curveball existed).

**What was deliberately rejected to keep this honest.** An earlier draft made `dispatch()` reuse
`SpecificityMatchPolicy`'s comparator directly via a `Comparator.thenComparing` chain. It measured
smaller, but it made `Router` reach past `MatchPolicy` into one concrete implementation of it —
exactly the coupling the seam exists to prevent, and it would have been true only by accident:
the day a different `MatchPolicy` is injected, that shortcut silently stops using it. The
Map-keyed version costs a few more lines and keeps `matchPolicy.pickBest(...)` as the only thing
`Router` calls to decide path precedence, which is the honest trade.

## What a candidate is most likely to get wrong here

Treating "no route for this method" as a reason to invent a new status code, or forgetting that
an any-method route registered for a shape must still answer a method no specific route claims —
both are asserted directly by the curveball tests.
