# NOTES — eight files, and three lines that decide what a tariff change costs

Compile and run from the directory holding the sources:

```
..\..\..\.toolchain\jdk-21\bin\javac.exe -Xlint:all -d out *.java
..\..\..\.toolchain\jdk-21\bin\java.exe -cp out Main
```

`javac -Xlint:all` prints nothing and exits 0. Real output, from exactly this code:

```
== 1 - the same lot, the same stay, two tariffs ==
  MOTORBIKE  3h01m   flat  4000   tiered  2500
  CAR        3h01m   flat  8000   tiered  5000
  TRUCK      3h01m   flat 16000   tiered 10000
  ParkingLot.java mentions neither tariff class. Entry.java names both.

== 2 - started hours: the unit both tariffs bill in ==
    0 min -> 1 started hour(s)
    1 min -> 1 started hour(s)
   59 min -> 1 started hour(s)
   60 min -> 1 started hour(s)
   61 min -> 2 started hour(s)
  180 min -> 3 started hour(s)
  181 min -> 4 started hour(s)

== 3 - a barrier sign that re-derives the price instead of asking ==
  quote() asks the injected policy      5000
  a second call site with its own table 8000
  Both compile. The tariff moved once and only one of them followed.

== 4 - Footprint is a fact, not a policy ==
  4 standard spots: room for 2 trucks or 4 cars
  one truck parked: room for 1 trucks or 2 cars
  third truck: java.lang.IllegalStateException: no spot available for TRUCK
```

Two lines in there are the lesson. `CAR 3h01m tiered 5000` is the worked example
`corpus/parking-lot/curveballs/02-tiered-pricing/REQUIREMENT-CHANGE.md` states in its own words, so
this code agrees with the corpus arithmetic rather than with itself. And `5000` against `8000` in
block 3 is what a bypassed seam looks like when nothing has thrown.

---

## `PricingPolicy.java` — the signature is the seam, and its width is two arguments

```java
    long feeMinor(VehicleType type, Duration stay);
```

**This line is the bet.** Any requirement that can be written as a function of a vehicle type and
an elapsed duration is a new class and no edit anywhere. Tiered rates, weekend rates, an EV
discount, a per-type cap: all of them are functions of these two arguments.

**What the line does not carry is the whole of `when-not.md`.** A rule that needs the entry
instant, the lot's occupancy, or the customer's loyalty tier is not expressible here, and widening
the signature costs every implementation and every call site. `contrast/` measures that at **27
lines against 14** for the un-seamed version of the same design.

The corpus has the same finding at full scale, in
`corpus/trip-state-machine/curveballs/03-no-show-timeout/reference-patch/PATCH.md`: *"The table is a
function of two values, and this rule is a function of three"*. Curveballs 01 and 02 on that problem
cost 8 and 3, because they were functions of two. Number 03 cost **28**.

```java
    static long startedHours(Duration stay) {
        long seconds = Math.max(0L, stay.toSeconds());
        return Math.max(1L, (seconds + 3599L) / 3600L);
    }
```

**A static on the interface is the one part a new implementation cannot fix by existing.** Every
tariff bills in started hours, so the rounding lives here once and two policies cannot disagree
about what "three hours" means.

**The price is stated rather than hidden.** A tariff that bills by the minute cannot override this,
because static interface methods are not inherited and not overridable. It would have to ignore the
helper, and then the rounding rule exists in two places. That is the moment to move `startedHours`
into a `BillingUnit` of its own — not before.

**`Math.max(1L, ...)` is the "even nothing is one hour" rule, and it is load-bearing.** Drop it and
a vehicle that leaves in under a minute pays nothing at all. Block 2 shows the boundary: 60 minutes
is one started hour, 61 is two.

**`Math.max(0L, seconds)` is the negative guard.** A caller that passes a negative duration gets one
hour rather than a negative fee. Without it, `(-3600 + 3599) / 3600` is 0, `Math.max(1, 0)` rescues
it, and the bug only appears for durations under minus one hour. Guarding the input is cheaper than
reasoning about that.

---

## `ParkingLot.java` — the class that holds the sequence and no rule about money

```java
    public ParkingLot(int standardSpots, PricingPolicy pricing) {
```

**One constructor, and it takes the tariff.** There is no no-argument convenience constructor here,
and the absence is deliberate. A default would be this class naming a concrete tariff class, and
then a tariff change opens this file.

**That is exactly the 2 in the corpus.**
`corpus/parking-lot/reference/src/ParkingLot.java` keeps `ParkingLot(int standardSpots)`, which
calls `new FlatHourlyPricing()`. When the tariff changed, one line of it changed, and
`corpus/parking-lot/curveballs/02-tiered-pricing/budget.json` records `reference_diff: 2` — one
line added, one removed.

**Read that as a fact about placement, not as a trick.** Two lines is not a design defect, and
`diff_budget: 3` means the corpus reference passes comfortably. The reason to move the default out
is that `ParkingLot` then cannot name a tariff even by accident. STANDARD v1.0's D4 level 3 is
*"absorbed by adding files only — zero lines changed in pre-existing files"*. The zero is a
by-product of a decision that stands on its own.

```java
        this.pricing = Objects.requireNonNull(pricing, "pricing");
```

**Fails at construction rather than at the barrier.** Without it, a null policy is a
`NullPointerException` inside `unpark`, at the end of somebody's stay, with a stack trace pointing
at the lot rather than at whoever built it wrong.

```java
        occupied -= stayed.spots();
        return pricing.feeMinor(stayed.type(), stay);
```

**`stayed.spots()`, not `Footprint.of(stayed.type()).spots()`.** The spots the vehicle took are
recorded on the open stay, so freeing them cannot disagree with taking them. Recompute instead, and
the day a footprint changes, vehicles parked before the change free the wrong number of spots and
the lot leaks capacity.

**The `return` line is the whole of open/closed in this file.** It asks. It does not know the answer
and it does not know the name of the class that does. Replace it with
`new FlatHourlyPricing().feeMinor(...)` and everything still compiles, the base suite still passes,
and the design is now closed to the one change it was built for. That defect has a name in the
grader's vocabulary: `seam-bypassed`, and it routes back to this lesson.

```java
    public long quote(VehicleType type, Duration stay) {
        return pricing.feeMinor(type, stay);
    }
```

**The second call site is where seams actually die.** A price on a sign at the barrier is a
different feature from a price on a receipt. It is also a plausible place to re-derive the number
from a rate table of its own. Block 3 runs both: the injected policy says `5000`, a re-derived flat
table says `8000`.

**Nothing throws.** Both numbers are legal fees, so the disagreement surfaces as a customer
complaint. Then the next tariff change fixes one of them and not the other.

```java
        int needed = Footprint.of(type).spots();
        if (occupied + needed > standardSpots) {
```

**Asking `Footprint` keeps the room a vehicle takes in one place.** Write `int needed = 1` and a
truck fits into a single spot. Block 4 would report room for 4 trucks instead of 2, and two trucks
would leave a four-spot lot looking half empty.

**The capacity rule stays here on purpose.** Running out of room is the lot's own business, not a
policy somebody revises by email. That is the same reasoning `Footprint` uses, applied to a
different fact.

---

## `Footprint.java` — the axis left un-seamed, and why that scores higher

```java
    public static Footprint of(VehicleType type) {
        return switch (type) {
            case MOTORBIKE -> MOTORBIKE;
            case CAR -> CAR;
            case TRUCK -> TRUCK;
        };
    }
```

**A switch on a type, and it is the right answer here.** STANDARD v1.0 puts *"behaviour selected by
`if`/`switch` on a type field, in more than one place"* at D3 level 0, and the qualifier is the
whole rule. This switch exists once, it returns a fact rather than selecting behaviour, and there is
no second implementation of "how big is a truck" to name.

**An exhaustive switch over an enum is a seam of a different kind.** Add a fourth `VehicleType` and
this file stops compiling, which is where somebody has to decide how much room the new thing takes.
An interface here would accept the new type silently and return whatever the default did.

**The compiler is the cheapest extension point you have, and it is free.** That argument is made at
scale in `corpus/file-system/curveballs/01-shortcuts/reference-patch/PATCH.md`. Adding a type to a
`permits` clause broke exactly one switch there, and the patch calls the forced case *"the A3
payoff, not a cost"*.

**A `FootprintPolicy` interface with one implementation is the `over-engineered` tag.** It routes to
`when-not.md`, and it costs score rather than earning it.

---

## `TieredPricing.java` — the entire absorption of a requirement, in one new file

```java
        for (long hour = FREE_HOURS + 1; hour <= hours; hour++) {
            fee += rate(hour <= STANDARD_UNTIL_HOUR ? STANDARD : LONG_STAY, type);
        }
```

**Billed hour by hour so the boundaries in the code are the requirement's own sentences.** The
closed form is shorter and it hides where the tiers change. When finance moves the free hour to two,
that is a constant here, not an algebraic rearrangement someone has to re-derive.

**`hour <= STANDARD_UNTIL_HOUR` is the tier boundary, and getting it wrong is silent.** Charge every
hour at the standard rate and a car staying 3h01m pays 3000 instead of 5000. Both are plausible
numbers on a receipt, which is why `faded/GapTest.java` asserts this exact case.

**The file is free.** `measureChange` charges nothing for a file that did not exist at injection
time, because the cost being measured is *being forced back into an old file*. See the header of
`server/lib/diff.mjs`.

---

## `Entry.java` — where the wiring lives decides what the change is charged

```java
    public static ParkingLot create(int standardSpots) {
        return new ParkingLot(standardSpots, new TieredPricing());
    }
```

**One file names tariffs, and this is it.** Grep the design for `FlatHourlyPricing` or
`TieredPricing` and every hit is in this file or in the class itself. That is a checkable property,
and it is what makes "the tariff changed" a one-line edit in a known place.

**Its counterpart in the corpus is `corpus/parking-lot/contract/Entry.java`, and contract files are
not measured.** The cheapest curveball in the whole corpus says so in as many words.
`corpus/lru-cache/curveballs/01-least-frequently-used/budget.json` records `reference_diff: 0` and
explains the zero: the new policy is a new file, and the line that names it lives in
`contract-delta/Entry.java`, *"contract, not reference/src, so not measured at all"*.

**Do not read that as a way to hide lines from the instrument.** Read it as the reason lru-cache
measures 0 and parking-lot measures 2 for changes of identical shape. Both seams are real. They
differ in where the sentence "today we use this one" is written.

**`createFlat` is kept rather than deleted.** Last month's revenue is reported against last month's
rates, and
`corpus/parking-lot/curveballs/02-tiered-pricing/reference-patch/PATCH.md` makes the same call:
*"deleting it would be a bigger diff than keeping it."*

---

## The threshold

Open a seam when **you can name the second implementation and write the one signature both would
satisfy**. Naming the dimension is not enough. "Pricing might change" is a feeling; `(VehicleType,
Duration) -> long` is an axis.

Two corollaries, both measured in `contrast/`:

- If you cannot write the signature without an extra argument you do not yet need, you do not know
  the axis. Leave the code concrete and small. Widening `contrast/b/`'s interface cost 27 lines
  across 3 files; the same change to the concrete version cost 14 in 1.
- Do not seam a set the compiler already protects. `Footprint` over `VehicleType` is exhaustive, so
  a new type is a build failure at the one place a decision is owed.
