Two lanes that must never mix. The customer lane holds the customer's own item, which is not company stock and may never enter a balance. The stock lane holds company stock pulled from QUARANTINE or DISPLAY_DAMAGED, which stays owned and stays counted. The page is split, not filtered, because a shared table with a lane column is exactly the affordance that lets somebody pick the wrong one.
2 tickets have reached round trip 3. A fourth send is refused by the API and by the round_trips <= 3 CHECK, matching the guard CSD already enforces in repair-lifecycle.ts. Both need a replace or scrap decision today; RPR-00298 is 31 days old.Decide →
5 tickets are past their vendor SLA across 3 vendors, AED 4,210 of stock-lane value held at a vendor and still counted in owned_qty. LC Straps is the worst at a 21-day median against a 14-day SLA.Vendor scorecard →
14 open · 0 units in any balance never sellable · never restockable
Ticket
Case
SKU
Serial
Vendor
Issue
Age
Due
RT
State
Stock effect
RPR-00311
CSD-4803
MSR31-07
E280…218F3C
LC Straps
Crown seized
6d
08d left
at vendor
none · customer owned
RPR-00310
CSD-4798
MSR31-01
E280…21A991
Bench 319
Strap pin loose
3d
11d left
in repair
none · customer owned
RPR-00308
CSD-4776
LBA01
E280…225A19
Bench 319
Handle stitching
24d
10d over
overdue
none · customer owned
RPR-00306
CSD-4770
FYN01
E280…22C201
Bench 319
Clasp replacement
2d
12d left
re-QC pass
none · returns to customer
RPR-00303
CSD-4744
JAE17
E280…22B740
VJ Jewels
Stone reset
12d
2d left
at vendor
none · customer owned
RPR-00301
CSD-4738
TNS01
E280…22A118
Bench 319
Lens scratch
5d
9d left
in repair
none · customer owned
RPR-00299
CSD-4721
MST-CRM-42-BLK
E280…229C04
ORO Leather
Edge paint lifting
15d
1d over
overdue
none · customer owned
RPR-00298
CSD-4702
MIN-CHR-001
E280…227E11
LC Straps
Frame crack
31d
17d over
RT3 · no 4th
none · replace or scrap
RPR-00295
CSD-4688
JRY01
E280…226044
VJ Jewels
Clasp fatigue
9d
5d left
at vendor
none · customer owned
RPR-00292
CSD-4671
FIS12
E280…224F80
VJ Jewels
Unrepairable
18d
4d over
replaced
-1 FIS12 @ HQ_319 · replacement_out
RPR-00289
CSD-4655
MSR31-11
E280…223C22
Bench 319
Battery
4d
closed
returned
none · handed back to owner
RPR-00286
CSD-4641
CDW03
E280…222188
LC Straps
Buckle replacement
7d
7d left
at vendor
none · customer owned
RPR-00284
CSD-4629
LP_MSR31-07
not tagged
Bench 319
Engraving redo
2d
12d left
in repair
none · customer owned
RPR-00281
CSD-4614
MST-CRM-44-BLK
E280…220994
ORO Leather
Zip failure
22d
8d over
escalated L3
none · offer refund or replace
Every row in this lane shows none in the stock effect column, and that is not a display convention. A repair_tickets row with lane='customer' cannot name a holding location or a holding stock type, cannot close with outcome restocked, and no repair_events row for it may carry a ledger_entry_id. Three CHECK constraints and one trigger. The only ledger row this lane can ever produce is a replacement_out when the repair fails.
stock lane
Company stock, held while repaired
9 open · 16 units held QUARANTINE or DISPLAY_DAMAGED · still owned
Every row in this lane names a location and a stock type, and that is also a constraint: lane='stock' requires both to be non-null, so a company unit at a vendor can never go missing from the balance sheet the way it does in ops2 today. In ops2 a repair-bucket classification writes one retail_operations row with from_location_id NULL and no inventory movement in either direction, so both lanes look identical and neither is counted.
Vendor turnaround against SLA · 90 days
median days out · bar past the tick is a breach
within SLAmedian past SLAthe black tick is each vendor's own repair_vendors.sla_days, not one global number
Round-trip funnel · 90 days
109 tickets · what a third round resolves to
round 1round 2round 3 · terminal
closed fixedreplacedscrappeda fourth send is refused by CHECK, not by policy
every hop names the ledger row it did or did not write
Opened from IWMS QC reject21-07 09:14
2 units failed the local QC bucket repair_local. Ledger: STOCK -2 → QUARANTINE +2 @ HQ_319, claim repair_out. Still owned, still in on_hand.
Triaged to Bench 31921-07 11:02
Internal vendor, so no custody transfer and no location change. Ledger: no row, correctly.
Sent to vendor21-07 14:30
Round trip 1 of a maximum 3. Due 28-07 from repair_vendors.sla_days = 7. Ledger: no row, an internal bench does not move stock.
Vendor returned · re-QC opened28-07 10:44
6 days out, inside SLA. A repair_qc_checks row is now required before anything can restock.
Re-QC PASS round 128-07 15:12
Inspector Subesh · 4 photos · checklist 9 of 9. The pass is recorded against round trip 1; a pass from an earlier round could not authorise this restock.
Restock queued28-07 15:13
Ledger: QUARANTINE -2 → STOCK +2 @ HQ_319, claim repair_in. The two units re-enter sellable for the first time in 8 days.
The lane invariant, in the schema
CHECK (lane <> 'customer' OR
(holding_location_id IS NULL
AND holding_stock_type_id IS NULL))
CHECK (lane <> 'stock' OR
(holding_location_id IS NOT NULL
AND holding_stock_type_id IS NOT NULL))
CHECK (lane <> 'customer' OR
outcome NOT IN ('restocked','rts'))
CHECK (round_trips <= 3)
A customer's item cannot enter a balance because the columns that would put it there cannot be populated. A company unit cannot fall out of a balance because the same columns cannot be null. Neither is a policy anybody has to remember.
Why the split is a page layout, not a filter
In ops2 today the returns-scan repair bucket calls one dispatcher, _dispatch_repair, which inserts a single retail_operations row with operation_type='repair', direction='to_wh' and from_location_id NULL. There is no lane field, no owner flag, and no inventory movement in either direction.
So a customer's own watch and a company carton of six defective bottles produce identical rows, and neither is counted. The dangerous half is that the operator's dropdown also offers stock: choosing it for a customer-owned item calls add_back, which credits a unit the company does not own into a carton and makes it sellable. There is no barrier of any kind between those two menu items.
i3 answers with three CHECKs, a trigger on repair_events.ledger_entry_id, and this page, which never puts the two lanes in the same table.
All five come from the inbound QC path (module 06) and all five land in the stock lane. The customer lane can only be reached from a CSD case, a return-scan disposition or a store walk-in. Nothing in the inbound pipeline can ever create a customer-lane ticket.
Escalation ladder
Day 7: ping the vendor, escalation_level = 1.
Day 14: escalate to the ops owner, level 2.
Day 21: offer the customer a replacement or a refund, level 3.
The ladder is CSD's, taken verbatim from its PRD so the two systems escalate on the same day rather than on two different clocks. i3 raises alert.raise on the internal bus at each step and never posts to Slack itself; module 01 owns dedupe as a database constraint.
customer lane
Loading
stock lane
Loading
No open repairs in either lane
Zero customer items at a vendor and zero company units held in QUARANTINE for repair. Closed tickets from the last 30 days are still available.
Filtering to Round trip 2+ and Internal bench together returns nothing: the internal benches have never needed a second round.
Ticket write refused: lane constraint violated. The request carried lane='customer' together with holding_location_id='HQ_319'. The database rejected it, which is the intended behaviour: a customer-owned item may never name a holding location. Nothing was written and nothing was partially written.
ERROR: new row for relation "repair_tickets" violates check constraint "ck_rt_customer_never_stock" DETAIL: Failing row contains (00312, customer, ..., HQ_319, QUARANTINE, ...)
You can view both lanes and record a re-QC result. Sending to a vendor needs role >= manager plus reverse_dispose; scrapping a stock-lane ticket needs role >= ops_manager plus reverse_scrap, and above AED 500 it also needs a finance signature.
Vendor portal sync is paused for maintenance. Vendor acknowledgements will not land until 06:00 Dubai. Local actions (open, triage, re-QC, close) all still work and will push on resume from the ack cursor.
GATE 2 has held for 4 of 7 consecutive days. Acceptance check AC-08.6 returns zero rows: no customer-lane ticket names a holding location, no customer-lane event carries a ledger row, and no unit in any balance traces to a customer-lane ticket. The page is correct; the gate has not aged.