Grill-with-docs review
Inventory Reservation Design
The design was grilled against the challenge scope, implementation plan, and current scaffold. The reviewed decisions were written back to the source spec, and a domain glossary was added.
Write-back status
Decisions written to spec
Glossary update
CONTEXT.md created
Decision Log
01
Should idempotency stay in scope?
Question asked during grill
The challenge PDF does not require idempotency keys. Should the spec keep optional idempotent reserve, or cut it to reduce implementation scope?
Why it mattered
Idempotency adds a field, lookup behavior, conflict handling, and tests. It can improve the flash-sale retry story, but it is not part of the grading rubric.
Decision
Keep idempotency as a narrow enhancement. Keys are optional, scoped per product, and stored in memory indefinitely for this challenge.
Practical effect
A same-key, same-quantity replay returns the original reservation object regardless of status. Same key with a different quantity returns IdempotencyKeyConflictError. If time is tight, idempotency is the first removable enhancement.
02
Should quantity support partial fulfillment?
Question asked during grill
If available stock is 3 and a request asks for quantity 5, should the system reserve 3 or reject the whole request?
Why it mattered
Partial fulfillment would make reservation quantity differ from the command, complicating idempotent replay and adding behavior the challenge does not ask for.
Decision
Keep quantity support, but reserve is strictly all-or-nothing.
Practical effect
quantity defaults to 1 and must be a positive integer. If requested quantity exceeds availability, reserve fails with InsufficientStockError. Reservation.quantity always equals the accepted request.
03
What exactly counts as expired?
Question asked during grill
At the exact moment now === expiresAt, is a reservation still active or already expired?
Why it mattered
The boundary determines FakeClock tests and avoids off-by-one millisecond ambiguity around the 2-minute hold time.
Decision
A reservation is expired when now >= expiresAt. The hold is valid until expiresAt, exclusive.
Practical effect
Advancing a FakeClock by exactly 120_000 ms expires the hold. Confirm, cancel, and replay must treat that boundary as expired.
04
Can reads mutate expired reservations?
Question asked during grill
Should getAvailableStock write ACTIVE-but-expired reservations back as EXPIRED, or only ignore them for availability math?
Why it mattered
A read that mutates storage is surprising. The system only needs expiry evaluation for correctness; it does not need a sweep during reads.
Decision
Reads never mutate. Lazy write-back is targeted to operations that touch a specific reservation: confirm, cancel, or idempotent replay.
Practical effect
An expired reservation may still be stored as ACTIVE, but availability treats it as released. Confirm or cancel after expiry marks it EXPIRED and rejects the transition. Replay of an expired original marks it EXPIRED and returns it with that status.
05
How do confirm and cancel find the product lock?
Question asked during grill
confirm(reservationId) and cancel(reservationId) do not receive productId. How do they lock the right product without making a decision outside the lock?
Why it mattered
State transitions must be serialized per product. A pre-lock read can be stale if it is used for business decisions.
Decision
Use a pre-lock reservation read only to discover productId, then acquire that product lock and re-read plus re-validate inside the lock.
Practical effect
The implementation can route to the correct KeyedMutex without trusting stale state. All final transition checks happen inside runExclusive(productId, ...).
Assumptions and Updates
- Made —
CONTEXT.md now defines Reservation, Replay, all-or-nothing Reserve, Expired, and Available stock.
- Made — The spec now records idempotency replay status, quantity all-or-nothing behavior, expiry boundary, read purity, targeted lazy write-back, confirm/cancel lock routing, and unbounded
KeyedMutex entries.
- Assumption — No ADR was created. These decisions are useful implementation constraints, but they are local to the challenge and not hard-to-reverse architecture choices.
- Assumption — The separate HTTP API has no
GET /reservations/:id; reservation state is visible through reserve, confirm, cancel, and replay responses.