The unified catalog: v2's 39 Shopify scenarios reconciled with INV1's 37-scenario audit into 72 numbered rules · editable sentences, immutable versions, shadow mode with a promotion gate
Origin says where the rule comes from · Today says what actually exists in the incumbents right now · every posting rule has a non-empty key formula
| # | Rule | Group | Origin | Today | Ledger rows | Idempotency key | Mode | Hits 7d |
|---|---|---|---|---|---|---|---|---|
| C-01 | straight_sale_online | sale | both | both | 1 @ STOCK | shopify:sale:{order}:{line}:{fulfil} | live v4 | 4,182 |
| C-02 | multi_line_sale | sale | both | both | 1 per line | shopify:sale:{order}:{line}:{fulfil} | live v2 | 2,594 |
| C-03 | pos_sale | sale | both | both | 1 @ store | shopify:sale:{order}:{line}:pos | live v3 | 2,008 |
| C-05 | cod_sale | sale | INV1 4 | INV1 | 1 @ STOCK | shopify:sale:{order}:{line}:{fulfil} | live v1 | 288 |
| C-10 | b2b_consignment | sale | v2 15 | neither | 2, movement pair | shopify:consign:{order}:{line}:{leg} | live v1 | 0 |
| C-11 | preorder_backorder | sale | i3 new | neither | 0, obligation | shopify:backorder:{order}:{line} | shadow v1 | 22 |
| C-11x | preorder_from_production | sale | v2 16 | never built | - | removed by L17 | removed | - |
| C-12 | bundled_sku_expand | sale | INV1 | INV1 | n components, parent 0 | {parent}:recipe:{recipe_id}:{comp} | live v2 | 378 |
| C-14 | cancel_post_fulfilment | cancel | v2 11 | i2 | 1 @ STOCK + | shopify:cancel:{order}:{line}:postfulfil | live v1 | 46 |
| C-16 | refund_restock | refund | both | both | 1 @ QUARANTINE | shopify:refund:{refund}:{line}:restock | live v3 | 462 |
| C-17 | refund_no_restock | refund | both | both, differently | 0, drift case | shopify:refund:{refund}:{line}:norestock | shadow v3 | 58 |
| C-21 | price_adjustment_only | refund | INV1 14 | INV1 | 0, monetary | shopify:adjust:{refund} | live v1 | 0 |
| C-25 | size_variant_exchange | exchange | INV1 17 | INV1 | 2, one anchor | csd:{case_id}:{line_id} | live v2 | 72 |
| C-27 | ghost_exchange | exchange | INV1 18 | detect only | 0, drift case | shopify:ghost:{refund_id} | live v1 | 44 |
| C-33 | cross_location_fulfilment | fulfilment | INV1 23 | wrong in both | 1 @ fulfil loc | shopify:sale:{order}:{line}:{fulfil} | shadow v2 | 122 |
| C-34 | missing_sku_line | fulfilment | INV1 25 | INV1 skips | 0, noop reason | shopify:nosku:{order}:{line} | live v1 | 62 |
| C-37 | variant_sku_change | catalog | v2 37 | partial | 0, alias only | shopify:skurename:{variant}:{new_sku} | live v2 | 86 |
| C-40 | shopify_inventory_manual_adjust | catalog | v2 34 | i2 logs | 0, never auto | shopify:invlevel:{item}:{loc}:{ts} | live v1 | 6 |
| C-41 | ops2_dispatch_scan | ops2 | v2 6.1 | neither | 0 or 1, reconciles | ops2:dispatch:{awb}:{order}:{sku} | live v2 | 1,214 |
| C-42 | ops2_returns_scan | ops2 | v2 6.1 | neither | 1 @ QUARANTINE | ops2:retscan:{awb}:{line_id} | disabled | 0 |
| C-44 | carrier_status_change | ops2 | INV1 30, 31 | neither | 1 @ QUARANTINE | ops2:carrier:{awb}:{status}:{at} | disabled | 0 |
| C-51 | csd_reservation_place | csd | v2 6.2 | wrong · D16 | 2 per line, pair | csd:res:{res_code}:{line}:{seq} | live v2 | 168 |
| C-54 | grn_receipt_line | grn_iwms | v2 6.3 | INV1 broken · D10 | 1 @ RNI | grn:{grn_number}:{line_id} | live v1 | 64 |
| C-57 | iwms_picking_replenish | grn_iwms | v2 6.5 | broken · D1 | 2, movement pair | iwms:replen:{carton}:{task_id} | live v1 | 31 |
| C-61 | rfid_gate_read | rfid | v2 6.4 | rfid | 1, authoritative | rfid:GATE:{epc}:{occurred_at} | live v1 | 296 |
| C-64 | rfid_write_off | rfid | v2 6.4 | rfid | 1 @ WRITTEN_OFF | rfid:WRITE_OFF:{epc}:{approval_ref} | live v1 | 0 |
| C-70 | known_noop | terminal | both | INV1 | 0 | shopify:noop:{topic}:{order_id} | live v1 | 1,506 |
| C-71 | blocked | terminal | i3 new | neither | 0, retryable | n/a · retry queue carries it | live v1 | 18 |
| C-72 | unmatched | terminal | v2 38 | i2 | 0, drift case | n/a · the drift case carries it | live v1 | 31 |
Rule cards keep their heights so a sentence does not reflow as the hit counts land.
The catalog seed ships all 72, so a truly empty list means the seed did not run. Zero-filter shows the chips: Shadow RFID together hide all 72.
The classifier keeps running the last live version. A broken draft never displaces a working rule, and it never silently becomes live.
A viewer sees every sentence, every key formula and every count, and no editable token. Promotion is gated on a feature flag plus a second human.
During the nightly verify the page is read-only. Rule authoring writes to the audit chain, so it waits for the walk to finish rather than appending mid-verification.
Thirteen rules cannot be enabled because their topic is not emittable upstream. They render with awaiting sibling and the ruling reference, not as disabled or dead.