# Reference patch — 03-no-show-timeout

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/TripEvent.java` onto `contract/TripEvent.java`, and add
   `../contract-delta/WaitExpired.java` to `contract/`. Contract files are given, so nothing here is
   charged.
2. **Add** the two new files in this directory — `TripGuard.java` and `WaitTimeout.java`. New files
   are free.
3. **Overlay** the six changed files onto their namesakes in `reference/src/`: `Trip.java`,
   `TripFactory.java`, `TripRules.java`, `TripProjection.java`, `RiderNotification.java`,
   `Demo.java`.

Every file in this directory is either genuinely changed or genuinely new. There is no unchanged copy
here, deliberately: a patch directory that carries a file it did not touch reports a blast radius
larger than the change, and this curveball spent a session in `incomplete/` because five such copies
made an unfinished patch look finished.

Measured: `reference_diff = 28` — `diff_budget = max(3, 28 + min(ceil(28/2), 6)) = 34`.

The curveball suite (13 tests) and the base suite (18 tests) are both green after the patch.

## Where the 28 lines went

| File | Lines | What changed |
|---|---|---|
| `Trip` | 19 | 10 of code, 9 of javadoc — see below |
| `TripFactory` | 3 | the one construction line gains `new WaitTimeout()` and wraps onto two |
| `TripRules` | 3 | one appended row — `DRIVER_ARRIVED + WaitExpired → CANCELLED` — plus the comma the row above it now needs |
| `TripProjection` | 1 | `case WaitExpired expired -> driver` — a no-show trip keeps the driver it had |
| `RiderNotification` | 1 | one arm in `message`: the rider is told the driver could not wait |
| `Demo` | 1 | one arm in `narrate` |
| `TripGuard`, `WaitTimeout` | 0 | new files |

`Trip`'s ten lines of code: one import, one `TripGuard` field, the constructor signature growing a
fifth parameter and wrapping onto two lines, one `Objects.requireNonNull`, and three lines in
`handle` —

```java
        TripState to = table.next(from, event);
        Instant now = clock.instant();
        guard.check(from, event, journal.transitions(), now);
        Transition entry = new Transition(from, to, event, now);
```

— of which the last is a rewrite of a line that used to read the clock inline.

`Trip`'s nine lines of javadoc are not decoration and are not padding either. The class javadoc said
that a refusal happens *"before the clock has been read, before the log has been touched and before
the world has been told anything"*, and after this change the first third of that is false: an event
the table permits but the guard refuses reads the clock exactly once. That claim is the argument for
why the class has no rollback path, so leaving it standing would have left the reference lying about
its own central decision. The rest of the explanation went into `TripGuard`, which is a new file and
free — the correction stayed in `Trip` only where `Trip` itself was wrong.

**Nothing else was opened.** `TransitionTable`, `Edge`, `TripJournal`, `InMemoryJournal` and
`EffectRunner` are byte-for-byte what they were.

## 28 against curveball 01's 8: a new dimension, not a defect

This is by a wide margin the most expensive curveball in the problem, and the honest question is
whether that is the transition table's fault. It is not.

**The table is a function of two values, and this rule is a function of three.** `Edge` is
`(TripState, Class<? extends TripEvent>) -> TripState`. Whether a `WaitExpired` may be accepted
depends on the state, the event **and how long the trip has been sitting where it is** — a fact that
lives in the log's timestamps and in the clock. No `(state, event) -> state` table expresses that,
however well it is indexed, so no amount of rows would have made this cheap. Curveballs 01 and 02
cost 8 and 3 because they genuinely were rows; this one is not, and a design that could absorb it in
rows would be a design that had already paid for guards it did not need.

**The alternatives, so the choice is a choice:**

- **A fourth component on `Edge`** — `record Edge(from, on, to, guard)` — would have put the rule
  where the rules are, and it costs more, not less. All seven existing rows grow a no-op guard, which
  is seven edits that say "nothing changed", and `TransitionTable.next` has to be threaded the log
  and the clock, which turns a pure two-argument lookup into something that reads the world. The
  table's purity is what makes "ask, then record, then react" free of a rollback path, and it is not
  for sale for one row.
- **An `if` in `Trip.handle`** — `if (event instanceof WaitExpired && ...)` — measures fewer lines
  and is the wrong answer for the reason the class javadoc gives: `Trip` holds the *sequence* and not
  one rule a requirement could reach. The second time-dependent rule (a scheduled trip that may not
  be matched before its pickup window, say) would then be a second `if` in the same method, and the
  method that decides legality would be the method that also timestamps, records and fires effects.
  `TripGuard` costs the plumbing once and the next such rule is a new implementation and no edit at
  all.
- **Trusting a `Duration` on the event** would have made this nearly free — one row and a comparison
  — and is why `WaitExpired` is given carrying nothing. Its javadoc says so: the trip already knows
  when the driver arrived, because it is in the log, and it already has a clock. Two sources for one
  fact is how a trip gets cancelled thirty seconds after the driver pulled up because a timer
  somewhere had a skewed clock.

**Where the guard sits was already decided by the base reference.** `EffectRunner`'s javadoc draws
the line: *"may this happen?" and "tell the world it happened" are on opposite sides of the log
append*, and a precondition "belongs before the transition, in the same place the table lookup is:
asked first, and the transition simply not made if the answer is no." `TripGuard` lands exactly
there. The base design named the seam before it had anything to put in it, which is why the ten lines
of code in `Trip` are plumbing and not a redesign.

## The one thing the base reference over-claimed

`TripRules`' own javadoc lists what a change to this design looks like: *"A driver-side cancellation,
a re-match, **a no-show timeout**, a scheduled trip that waits before it can be matched — all of them
are rows here and nothing else."* This curveball is the counter-example to its own example. A no-show
timeout is a row here **and** a guard: the row makes `DRIVER_ARRIVED + WaitExpired` legal in
principle, and the guard decides whether it is legal yet.

The sentence is left standing in `reference/src/TripRules.java` on purpose rather than by oversight.
Both earlier curveballs ship a whole copy of that file in their `reference-patch/`, so correcting the
base would silently re-measure two budgets that are already recorded and verified — the fix costs
more trust than the sentence does. It is noted here instead, and it is worth raising with a candidate
who claims a table absorbs everything: **a table absorbs every rule that is a function of (state,
event), and the interesting requirements are the ones that are not.**

## What the compiler catches, and what it does not

Adding `WaitExpired` to the `permits` clause breaks the build in the same three files as curveball
01, one at a time, before any test runs. Verified by compiling `reference/src` against
`contract-delta/` with no patch applied:

```
Demo.java:106: error: the switch expression does not cover all possible input values
        return switch (entry.event()) {
```

Removing `Demo.java` from the compilation surfaces `RiderNotification.java:30`, and removing that
surfaces `TripProjection.java:52`. `javac` reports only the first exhaustiveness error per
compilation, which is a property of the compiler and not of the design.

What the compiler does **not** catch is the whole of the interesting part: the missing `TripRules`
row (because `Edge` keys on a `Class`, which is not exhaustiveness-checked — a cost `Edge`'s own
javadoc states), and the entire five-minute rule. Those are caught by the suite. That is the honest
limit of "make it a compile error", and this curveball is where it is reached.

## What the suite is really measuring

Three assertions separate a design that thought about this from one that made it pass:

- **`refusedByTheTableCostsNoClockRead`** — a `WaitExpired` from `REQUESTED` or `MATCHED` must not
  move the clock on. The clock read has to sit *after* the table lookup, not at the top of `handle`.
  A design that reads the clock first to have a timestamp ready fails this, and it fails it by one
  minute on a later entry's timestamp, which is the kind of defect nobody finds by reading.
- **`refusedGuardCostsOneClockRead`** — and exactly one. The guard is handed the instant rather than
  a clock, so it cannot read again and cannot judge the wait against a different moment from the one
  the entry would have carried.
- **`replaysTheExpiry`** — the wait is measured from the log and the injected clock and from nothing
  else, so replaying a no-show into a fresh trip with an equivalent clock reproduces it exactly. A
  design that cached "when the driver arrived" in a field outside the log gets a second source for a
  fact the log already holds, and replay is where it shows.

`WaitTimeout` reads `log.get(log.size() - 1).at()` for the arrival time, with no search and no
`DriverArrived` lookup, and that is not a shortcut: the trip's state *is* the last entry's `to`, and
the table gives `WaitExpired` a cell only from `DRIVER_ARRIVED`, so the last entry is necessarily the
transition that put the driver at the kerb — whichever event did it. Written that way rather than as
"find the last `DriverArrived`", it needs no change if a second row into `DRIVER_ARRIVED` is ever
added, and it cannot pick up the timestamp of an arrival the trip has since left and come back to.
