# Reference patch — 01-shortcuts

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/FileSystemApi.java` onto `contract/FileSystemApi.java`, and add
   `../contract-delta/SymlinkLoopException.java` to `contract/`. Contract files are given, so
   nothing here is charged.
2. **Add** `SymlinkNode.java` (in this directory) to `reference/src/`. New file, free.
3. **Overlay** `Node.java`, `Nodes.java`, `TreeResolver.java` and `InMemoryFileSystem.java` (in
   this directory) onto the files of the same names in `reference/src/`.

Measured: `reference_diff = 28` — `TreeResolver.java` 15+1, `InMemoryFileSystem.java` 8+0,
`Node.java` 1+1, `Nodes.java` 2+0 — so `diff_budget = max(3, 28 + min(ceil(28/2), 6)) = 34`.

Both the curveball suite (10 tests) and the base suite (29 tests) are green after the patch.

## Why this one is not cheap, and why that is the right call rather than a massaged number

Per the authoring spec: *"If d exceeds 20, stop and ask which of two things is true... The
requirement genuinely adds a new dimension rather than a new value within an existing one... A
capped budget keeps that fair and still discriminating, so the curveball may stay."* This is that
case, not the other one (wrong seams).

**A shortcut is not a new value of an existing kind — it is a third kind of thing where the
problem said there were exactly two.** `Node` was `sealed ... permits FileNode, DirectoryNode`
because a file system, for this problem, genuinely has two kinds of entry. That was true, not a
missed abstraction; the requirement changes what is true. Once it does, walking a path stops being
"look up a name, check what you got" and becomes "look up a name, and if what you got is a pointer,
go look up *that*, possibly several times, possibly forever if nobody stops it." That is new
control flow, not a new branch in old control flow, and it could not have been anticipated by any
seam in the base problem — nothing about lending, pricing, or slots is genuinely analogous to
"this answer might not be an answer, it might be another question."

**Where the 28 lines actually went, and what is not among them.** No new class was needed for the
core behaviour beyond `SymlinkNode` itself (free, one field). Nothing in `InMemoryFileSystem`'s
seven other operations changed — `mkdir`, `ls`, `read`, `mv` and `delete` all got shortcut-awareness
for free, because they were already written in terms of `PathResolver`, never in terms of a `Map`
they walked themselves. That is the seam this curveball is actually measuring, and it held:

| File | Lines | Why |
|---|---|---|
| `TreeResolver.java` | 16 | the one real algorithm change: `get` now threads a hop count through every nested resolution a shortcut causes, so a cycle of any length is caught rather than recursed into forever. This is the requirement itself, not scaffolding. |
| `InMemoryFileSystem.java` | 8 | 4 lines are the new `symlink()` method every design must declare somewhere (the same reasoning as vending-machine's `enterMaintenance`); 4 are a real fix — `write`'s three-way `existing` check needed a fourth arm so that writing over a shortcut replaces it instead of crashing against `DirectoryNode`'s own invariant guard. |
| `Node.java` | 2 | one word added to a `permits` list. |
| `Nodes.java` | 2 | one forced `case` in the one exhaustive switch over `Node` — see below. |

**The forced case in `Nodes.kindOf` is the A3 payoff, not a cost.** Adding `SymlinkNode` to the
`permits` clause broke compilation at exactly one place: the switch that must now list it. The
case it needed is `throw new IllegalStateException(...)`, because `get` never returns an
unresolved shortcut — the compiler demanded a decision for a case that cannot occur, which is
the entire point of a sealed hierarchy over an `instanceof` chain that could simply forget one.

**What the same requirement would have cost a flat `Map<String, Node>` keyed by full path** — the
design this problem exists to distinguish from a tree — is unbounded rather than merely large:
every one of the eight operations resolves a path by hand today, so every one of them would need
its own copy of the follow-and-detect-a-cycle logic, or a shared free function that eight call
sites would all have to remember to call. `PathResolver` being the single choke point is what
turned "eight places might need this" into "one place does."
