One of the most common questions we get from prospective operators is the commingling question. Two clients sell physically identical units — say, a generic 24oz sport bottle that both sourced from the same Alibaba supplier. Can the warehouse treat them as one inventory pool?
The short answer is: no, never, under any circumstances, and the moment you do it you’ve created an audit nightmare you can’t unwind. The longer answer is about why the temptation exists and how the software needs to be structured to make commingling impossible at the database level — not just discouraged in the operator handbook.
Why operators are tempted to commingle
Floor space is finite. Storing two physically identical bottles in two separate locations doubles the storage cost and looks redundant. The pick-and-pack operator picks ‘a 24oz sport bottle’ — does it really matter which client’s pool it came from?
Yes. It matters in three ways:
- Billing: storage fees are per-client. If you can’t prove which client’s unit you’re storing, you can’t bill correctly.
- Inventory reconciliation: if Client A’s on-hand drops by 1 because you shipped Client B’s order, both clients’ books are now wrong.
- Audit and dispute: when a client claims you shipped their inventory to someone else, your only defense is a clean separation in the database. Commingling means you can’t prove either direction.
How the schema prevents commingling
Every inventory row in iShipTo carries a tenant_id column (the operator) and a client_id column (the seller within the operator). Two units that are physically identical but belong to different clients sit in different database rows with different client_id values. The application code physically cannot reduce them to a single row — there’s no SQL operation that would combine them.
On the floor, we enforce this with location strategy. Each client gets their own pick locations. Bottle from Client A is at A-12-03. Bottle from Client B is at B-04-07. The picker sees a pick list that names a specific location; they don’t see a generic ‘sport bottle’ that could come from anywhere.
Why row-level security matters here
The other half of commingling protection is making sure no software bug ever exposes one client’s inventory to another. We use Postgres row-level security: every query against the inventory table is automatically scoped to the current client via a server-set JWT claim. There’s no SQL the application can write that returns inventory belonging to a different client.
This is meaningfully different from application-layer filtering. With app-layer filtering, the developer has to remember to apply the WHERE clause on every query. Miss one query — say, in a CSV export endpoint — and you’ve leaked rows. With RLS, the database refuses to return them regardless of what the application does.
The customer-facing version
Your sellers don’t care about RLS or schema design. What they care about is: ‘when I look at my portal, can I see how many of my units are on your shelf, and will I ever see anyone else’s units?’ The answer is: yes, you can see yours; no, you will never see anyone else’s. We pen-test cross-tenant reads on every release. If a query ever returned another client’s data, the deployment would fail.
If you’re evaluating WMS products and you’re not sure how they handle this, ask the vendor to walk you through a specific query: ‘show me an inventory query where the application code forgets to filter by client_id — what happens?’ If their answer is anything other than ‘the database refuses to return rows from other clients’, keep shopping.