# Projection consistency rules

**Purpose:** Detect **drift** before it becomes silent wrong ATP.  
**Invariants:** [`inventory-invariants.md`](inventory-invariants.md) I5–I7.

---

## Part 6 — Projection integrity checks

### Drift detection

| Check | Description |
|-------|-------------|
| **C1 — Replay diff** | Nightly (or hourly hot SKUs): recompute `replay_sum` from ledger vs `projection` columns; `ABS(diff) > epsilon` → alert. |
| **C2 — Orphan ledger** | Movement committed in window with **no** projection row update log entry (if outbox pattern) — anomaly. |
| **C3 — Monotonic version** | `row_version` on projection only increases on writer path; sudden jump back → corruption indicator. |

### Reconciliation checkpoints

- **End of business day** per warehouse: optional snapshot table `inventory_projection_checkpoints(warehouse_id, checked_at, max_movement_id, checksum)` for audit trail.
- **Post-deploy:** smoke replay on **canary** tenant subset before fleet.

### Rebuild verification comparisons

- **Before/after hash:** `hash(tenant_id, warehouse_id, item_id, on_hand, reserved)` for all rows in scope pre/post rebuild — must match replay-derived hash.
- **Sample audit:** random 100 SKUs full row compare.

### Alert / escalation expectations

| Severity | Condition | Route |
|----------|-----------|--------|
| **P1** | C1 diff on any **pay-adjacent** or regulated item class | On-call + freeze slice |
| **P2** | C1 diff on non-critical SKU | Engineering same day |
| **P3** | Checkpoint job failed retry | Ops ticket |

Escalation ties to [`inventory-failure-recovery.md`](inventory-failure-recovery.md).

---

## Read path rules

- **ATP / picking** must read **projection** row locked under same rules as write when validating concurrent issue — do not mix cached read without TTL invalidation on movement commit.
