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.

Draft source docs/superpowers/specs/2026-07-05-inventory-reservation-design.md
Plan HTML docs/superpowers/specs/2026-07-05-inventory-reservation-design.html
Write-back status Decisions written to spec
Glossary update CONTEXT.md created

Decision Log

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.

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.

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.

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.

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