Skip to content

[Service] Wire-level qty upper bounds on cart lines and /inventory/reserve - #98

Merged
vedanshujain merged 1 commit into
mainfrom
fix/qty-caps
Jul 26, 2026
Merged

vedanshujain merged 1 commit into
mainfrom
fix/qty-caps

Conversation

@vedanshujain

Copy link
Copy Markdown
Contributor

What

From the 2026-07-26 API-contract QA: cart/reserve qty had no upper bound — qty: 10^9 passed the schema and minted a junk failed-reservation audit row per attempt.

  • CART_LINE_MAX_QTY = 10_000 on addLineBody and patchLineBody (capping only add would leave PATCH as a bypass)
  • RESERVE_MAX_QTY = 1_000_000_000 on reserveBody (aligned with the existing admin stock-movement cap)
  • Over-cap ⇒ structured 400 at the zod layer — before any store call; tests assert the reserve spy never fires and onHand is untouched

Scope honesty (also in the changeset): this removes the absurd-magnitude vector, not the junk-row amplification one — that is request-count-bound and tracked in #91 (rate limiting).

Verification

  • format / lint / typecheck clean; workspace suite 1370 passed; @urumi/service vs Postgres 310/310
  • 8 new boundary tests (at-cap succeeds against real seeded stock; over-cap rejected wire-side)
  • Plan and diff each approved by two independent reviewers

Changeset

@urumi/service minor (0.x wire-visible: previously-accepted qty now 400s).

Follow-up noted by review: unify stockMovementBody's literal with RESERVE_MAX_QTY (trivial, out of scope here).

🤖 Generated with Claude Code

https://claude.ai/code/session_013Fkmqxb8d6hh4ETNNz43eZ

…serve

Zod .max() caps on the three unbounded qty sites (service-hardening plan §4):
CART_LINE_MAX_QTY = 10_000 on addLineBody/patchLineBody (the shopper-facing,
anonymous-caller surface), RESERVE_MAX_QTY = 1_000_000_000 on reserveBody
(aligned with the existing admin stockMovementBody precedent — the raw
inventory primitive, a machine caller). Both exported as named constants so
tests assert boundaries against the same symbol. Today qty: 1e9 (or
Number.MAX_SAFE_INTEGER) is a "valid" wire request rejected only by store
arithmetic; the cap makes "how much may one request ask for" an explicit,
tested part of the contract and rejects it early (400 at the zod boundary,
before any store call or row write). Wire-only — the domain's
positive-integer bound is unchanged (defense-in-depth); no port change.

This is NOT a rate limit and does not fix junk-failed-reservation-row
amplification, which is request-count-bound, not qty-bound. Follow-up filed
and linked from the schemas.ts comment and the changeset:
#91

Tests: packages/service/test/qty-bounds.test.ts (8 new, IO-free, in-memory
stores via app.request()) — over-cap 400 with structured error, store never
touched (reserve() spy + onHand unchanged), at-cap 200 boundary for both
POST/PATCH cart-line paths, the reserve-endpoint pair at RESERVE_MAX_QTY /
+1, and the exact QA repro (qty: 1e9) now 400.

Changeset: @urumi/service minor — the breaking bump at 0.x (no major yet);
previously-accepted over-cap requests now 400.
@gemini-code-assist

Copy link
Copy Markdown

Caution

The consumer version of Gemini Code Assist on GitHub has been sunset. All code review activity has officially ceased.

@vedanshujain
vedanshujain merged commit 89a0286 into main Jul 26, 2026
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant