# Inventory invariants (always true)

**Role:** Define **correctness guarantees** that must never drift silently.  
**See also:** [`movement-validation-rules.md`](movement-validation-rules.md), [`replay-consistency-rules.md`](replay-consistency-rules.md), [`projection-consistency-rules.md`](projection-consistency-rules.md), [`transaction-engine.md`](transaction-engine.md).

Corruption usually enters through **invariant drift**, **silent projection mismatch**, **races**, or **replay inconsistencies**. This document names what **must** hold.

---

## Part 1 — Inventory invariants

### Core quantity invariants (per projection row)

Let `on_hand`, `reserved`, `available` be stored or derived consistently **within one committed transaction**.

| ID | Invariant |
|----|-----------|
| **I1** | `available = on_hand - reserved` (when all three are materialized; if `available` is computed-only, it must equal that expression at read time). |
| **I2** | `on_hand ≥ 0` unless tenant policy **explicitly** allows negative on-hand (documented exception; default **deny**). |
| **I3** | `reserved ≥ 0` and `reserved ≤ on_hand` when negative on-hand is disallowed. |
| **I4** | Reservation **release** quantity cannot exceed **currently reserved** quantity for that reference (no double-release). |

### Ledger–projection invariants

| ID | Invariant |
|----|-----------|
| **I5** | **Ledger replay** over committed movements in deterministic order reproduces **exactly** stored projection totals for each `(tenant, warehouse, item[, section])` slice (within numeric tolerance policy). |
| **I6** | Every movement row that affects stock has a **corresponding** projection delta applied in the **same** DB transaction (no orphan ledger rows visible post-commit without projection update). |
| **I7** | **Movement totals** for a bounded window (e.g. per `correlation_id` bundle) **reconcile** to net projection change for affected keys (sum of signed deltas equals delta of projections). |

### Idempotency invariants

| ID | Invariant |
|----|-----------|
| **I8** | Same `idempotency_key` + tenant + source cannot commit **two** distinct movement bundles (duplicate request returns original outcome). |

### Invalid states (must never persist after commit)

- `available < 0` when defined as `on_hand - reserved` and non-negative policy holds.
- `reserved > on_hand` when I3 applies.
- Projection row exists for `(tenant, warehouse, item)` but **no** ledger path could produce it (detected by replay mismatch).
- Two **active** conflicting reservations on the same stock slice exceeding `on_hand` — **Phase 1** avoids complex reservations (see Part 2); if only simple holds exist, cap by policy.

### Corruption indicators

| Signal | Likely cause |
|--------|----------------|
| Replay ≠ projection | Partial TX bug, manual DB edit, failed rebuild |
| Idempotency collision with different payloads | Client bug or attack |
| Monotonic `id` gaps with missing projection | Partial failure / wrong isolation |
| Negative available under deny policy | Race or validation bypass |

### Invariant severity levels

| Level | Meaning | Response |
|-------|---------|----------|
| **S0 — Blocker** | I5/I6/I8 violated or replay mismatch | **Freeze** movements for slice/tenant; incident; rebuild |
| **S1 — Critical** | I1–I4 violated post-commit | Same as S0 for affected warehouse/item |
| **S2 — Warning** | Near-limits, clock skew risk, long job queue | Alert; no freeze unless policy says |

---

## Part 2 — Foundational movement types (Phase 1 scope only)

**Phase 1** implements **only** these `movement_type` values:

| Type | Purpose |
|------|---------|
| `stock_in` | Increase on-hand (receipt, return-to-stock, opening balance — **not** procurement workflow UI). |
| `stock_out` | Decrease on-hand (consumption, shipment — **not** sales fulfillment orchestration). |
| `adjustment` | Signed delta with governance (cycle count, damage) — **not** multi-step approval engine in code paths yet. |

### Explicitly NOT in Phase 1

- Procurement workflows, PO matching, three-way match.
- Sales fulfillment, pick/pack/ship state machines.
- **Complex reservations** (multi-line allocation, partial promises across orders) — keep **none** or a **single** simple hold pattern only if absolutely required; default **none** until Phase 2.
- Manufacturing BOM consumption / output.
- Multi-step approvals inside the movement engine.

### Compatibility expectation

Future types (`transfer_out`, `transfer_in`, `reservation`, …) **must** extend the same engine pipeline ([`transaction-engine.md`](transaction-engine.md)) and **must not** break I1–I8 for existing slices.

---

<a id="part-9-self-validation"></a>

## Part 9 — Self-validation

- [x] Inventory invariants documented (this file, Part 1).
- [x] Phase 1 movement scope bounded (Part 2).
- [x] Movement validation rules defined → [`movement-validation-rules.md`](movement-validation-rules.md).
- [x] Replay consistency defined → [`replay-consistency-rules.md`](replay-consistency-rules.md).
- [x] Corruption-prevention tests planned → [`movement-test-strategy.md`](movement-test-strategy.md).
- [x] Projection integrity rules defined → [`projection-consistency-rules.md`](projection-consistency-rules.md).
- [x] Recovery procedures documented → [`inventory-failure-recovery.md`](inventory-failure-recovery.md).
- [x] Future movement expansion rules → [`inventory-failure-recovery.md`](inventory-failure-recovery.md#part-8-future-movement-expansion) (Part 8).

**Correctness > speed · Integrity > convenience · Determinism > shortcuts**

**Implementation sign-off (minimal engine):** [`stock-minimal-engine-signoff.md`](stock-minimal-engine-signoff.md).
