# Migrating Existing Yards into the Registry

The overlay model (`03`) lets the existing yards stay on static config **forever**
with zero risk. Migration is therefore **optional** — we only do it to get one
unified system. When we do, it must be **per-yard, behind a flag, gated by
automated parity + a real ship test, and instantly reversible.** Never a big-bang.

**The test oracle = current static behavior.** "Works exactly as expected" is
defined mechanically: the registry path must produce **byte-for-byte the same
result** as the static path it replaces — at config level *and* at the GPS
ship-payload level — before any real shipping is trusted.

---

## Scope — what "existing yards" means

Everything currently in `config/gopartsship.php` + `shipdev`, grouped by account:
GA, PAMS (USER-23), PartsBox ×4 (USER-24), Counselman ×4 (USER-25), JCAuto,
PBI ×3, Express, PerfRad ×2, USER-30, **Wheels America ×6 (USER-47)**,
**Spalding ×2 (USER-49)**, **OEM Garages (USER-48)**. Migrate **one account at a
time**, easiest/lowest-volume first (e.g. OEM Garages), riskiest last (GA).

---

## Phases

### M0 — Backfill (inert, safe any day)
Read current static config + `shipdev` → write registry rows for every existing
warehouse. **Nothing reads them yet.** They appear on the tracking board as
"Live (legacy)" so the board is complete from day one. Pure data, zero behavior
change.

### M1 — Parity test (config level, mechanical)
`gps:parity` — for every existing warehouse, compute what the **registry-backed**
code *would* return and assert it **exactly equals** the current static output:
- warehouse config entry (authorization, postalCode, exclusive)
- dropdown name + ZIP (WarehouseHelper, ShipmentController, Filament)
- B2C membership / B2B exclusion
- HAZMAT result for sample items
- warehouse→ShipStation id map

**Must diff to zero** before any cutover. Catches backfill typos without touching
production.

### M2 — Shadow payload diff (ship level, NO real label)
The real proof for shipping. `gps:shadow {order_id} {warehouse}`:
- builds the exact GPS `Orders/CreateOrder` payload via the **static** path
- builds it again via the **registry** path
- diffs them — credential/account, ship-from location id, items, advancedOptions

Run across a **sample of recent real orders per account**. **Zero diff required.**
This proves "exactly as expected" at the payload level **without sending two
labels or spending a cent.**

### M3 — Per-yard cutover behind a flag
Per-account flag `served_by = static | registry` (default `static`). Flip **one**
account to `registry`. Anything off → flip back instantly (no deploy). Start with
OEM Garages (1 location, low volume).

### M4 — Acceptance ship test (the manual-order test)
After flipping an account, run the live acceptance script **per warehouse** in it
(detailed below). Real manual order → real ship → verify correct account /
location / label / tracking → **void the label**. Record pass/fail on the board.

### M5 — Reconcile & monitor
`gps:reconcile` confirms registry == shipdev on a schedule. Watch logs for the
GA-fallback warning (= a silent mis-point; per `03` this becomes a hard error).
Leave the flag at `registry` only after the yard passes M2 + M4.

### M6 — (optional, much later) retire static entries
Only after *all* accounts are migrated and stable for weeks. Not required —
static entries can remain as the fail-safe fallback (`03` G2) indefinitely.

---

## M4 — Acceptance ship test script (per warehouse)

Baseline = how it ships **today**. Goal = identical behavior on the registry path.

1. **Create a manual order** with one item sourced to this yard's supplier/warehouse
   (low value, internal test address).
2. **Ship form (single + bulk):** warehouse appears in the dropdown; rate quotes
   from the **correct origin ZIP**.
3. **Ship via GPS**, then verify in `shipdev`:
   - order's `user_id` / `warehouse_api_credential_id` = **this account** (NOT GA — confirms no silent fallback)
   - item `ship_from_location_id` = the **correct location**
   - label created; tracking number returned
4. **Central side:** item/order status transitions correctly; SHIP_NOTIFY webhook
   returns → tracking lands on the order.
5. **Fulfillment page:** supplier shows in Create **B2C**, blocked from **B2B**;
   per-vendor **export** button produces the right CSV.
6. **HAZMAT:** a hazmat item on this yard is **blocked** from GPS.
7. **Void/cancel** the test label (avoid cost / real shipment). Mark the
   warehouse's acceptance item ✓ on the board.

Pass criteria: every check matches the static baseline exactly; GPS payload diff
from M2 was zero.

---

## Rollback
- Per-account flag → `static` (instant, no deploy). Registry stays backfilled and
  inert for that account; behavior reverts to today.
- Because static config is never deleted (M6 optional), rollback is always
  available even after full migration.

## Definition of done (per account)
M1 parity zero · M2 shadow diff zero on sampled orders · M3 flag flipped ·
M4 acceptance ship test green for every warehouse · M5 reconcile clean.
