LEARNING RECORD · BullionVault Java course

14 August 2026

0002 — Matching: three bugs, eight failures, one design argument

Date: 2026-08-14 Status: accepted

Context

Lesson 0002 covers the order board itself — the part of BullionVault that is the product. Four rules had to be taught: price priority, time priority, price improvement, and partial fills.

The design question was how to seed the bugs so that eight red bars teach three lessons rather than eight disconnected ones.

Decision

Assert on one dimension per test. Every test in OrderBookTest checks the counterparty, or the price, or the remaining quantity — never a combination. That is why the eight failures group cleanly:

Failing testsAssert onBug
4fill pricefills at the incoming limit, not the resting order's price
2counterparty idresting.addFirst + stable sort ⇒ newest-first among equal prices
2quantitycontinue skips any resting order bigger than the incoming order

Had each test asserted on everything, all eight would have failed on whichever assertion happened to run first, and the suite would have said only "matching is broken".

Leave price priority correct. Two of the twelve new tests pass on arrival. That is deliberate — a suite where everything is red teaches nothing about isolating a fault, and it gives Tom something to explain rather than fix.

The design argument the lesson is really about

Time priority can be made green two ways, and I verified both:

Fix A works by accident. The business rule first come, first served ends up living inside an accident of list insertion — unnamed, unasserted, and silently broken the moment someone swaps the collection, parallelises the stream, or reloads the book from Oracle in a different row order.

To make the point unmissable, the verified solution deliberately keeps addFirst and still passes all 25 tests. That is the proof that the comparator is carrying the rule.

The price-improvement bug is the expensive one

Filling at incoming.limit() instead of r.limit() means every client always pays their worst acceptable price. In the test fixture a buyer willing to go to 99,000 meets a resting offer at 96,000 and is charged USD 24,750.00 instead of USD 24,000.00 on 250 g — about 3%, silently, on every trade.

Note which test catches the consequence: fillKnowsItsConsideration, which runs lesson 0001's Pricing.consideration over the fill. The money code was correct and was handed the wrong input. Worth saying out loud — correct components do not compose into a correct system on their own.

Consequences

Good. The lesson reuses lesson 0001 (Pricing.consideration on Fill), so it doubles as spaced retrieval rather than isolated new material.

Good. sequence on Order sets up lesson 0003 directly: something upstream has to hand out those numbers, and that something is shared mutable state.

Cost. Price gained Comparable plus isAtMost/isAtLeast. That edits a file lesson 0001 described as "given and correct". It is additive and does not change any lesson 0001 behaviour, but the lesson-0001 text should not be read as promising those files never change.

Open. Order.withQuantity currently lets quantity go up as well as down. On CME Globex an increase forfeits time priority; here it would silently keep it. Good candidate for a follow-on test.

Verification

Run before shipping, per [[blackjack-course-tdd-feedback]]:

Note on this session

macOS revoked Desktop folder access partway through (TCC), so lesson 0002 was built and verified entirely in the session scratchpad against a copy of the solved project, then applied to the workspace afterwards. Everything above was still run — nothing was written from reasoning alone.