Fourth phase of the H4 Product/Variant rework. POS terminal now resolves variant attributes from
the new Odoo-shape singular tables (cafe.product_*) instead of R10 plural tables. Schema bridge
+ backfill v2 + Cartesian generator safeguard + in-place rewrite of
buildShellAttributePairs. cafe.products stays authoritative through P4;
H4-P5 retires R10 next slice.
14ef245 + nix-cafe a5788c8. Migration applied to prod Supabase via
node migrate.js from local; backfill ran clean on all 3 tenants with the
dup-name multi-variant materialization landing 8 new product_product rows on
get-coffee.
Every cafe.products row on get-coffee + lumiere-coffee now has exactly 1
cafe.product_product row pointing back to it. The H4-P3 dup-name regression
(17 → 9) is reversed: 17 products → 9 templates + 17 product_products on get-coffee.
Login + /cafe/pos on lumiere-coffee renders without 5xx. Then end-to-end
attribute resolution proven via probe: seeded a TAG-prefixed R10 attribute + 2 values +
assignment for one lumiere product → re-ran both H4-P3 + H4-P4 backfills from local → asserted
singular tables received the mirror (attribute, values, line with is_required=true
+ default_value_id backfilled) → called buildShellAttributePairs
directly against the live prod DB → confirmed the returned ShellProductAttribute[]
matches the seeded shape:
/cafe/pos renders without 5xx (read path swap survives)buildShellAttributePairs on prod resolves the TAG attribute (sourced from singular tables)Raw: result.json
| test-phase1-prod.mjs | 11/11 |
| test-phase2-sso-outdoor-prod.mjs | 6/6 |
| test-phase2-cafe-multishop-prod.mjs (parallel on demo) | 6/6 |
| test-m1-prod.mjs | 10/10 |
| test-r7-prod.mjs | 14/14 |
| test-r8-prod.mjs | 4/4 |
| Plus prod feature test (above) | 7/7 |
| Total | 58/58 |
H4-P5 — retire R10 + cafe.products consolidation. Pure-cleanup slice:
drop cafe.product_attributes, cafe.product_attribute_values,
cafe.product_attribute_assignments. Decision pending: keep
cafe.products as a real table (admin still writes there), or migrate to a view
over (product_template JOIN product_product). Delete legacy
/cafe/settings/modifiers admin route + R10 DAO + sync hooks (none added in P4).
New:
nix-outdoor-sales-backend/migrations/20260520100000_h4_p4_pos_readpath_bridge.ts ·
nix-cafe/scripts/backfill-h4-p4-pos-readpath.ts
Modified:
nix-outdoor-sales-backend/migrate.js ·
nix-cafe/lib/db/schema.ts ·
nix-cafe/lib/db/product_attribute.ts ·
nix-cafe/lib/pos/attribute-pairs.ts (in-place rewrite) ·
nix-cafe/lib/actions/product_attribute.ts ·
nix-cafe/app/(authed)/settings/attributes/attributes-client.tsx
Migrations: 1 (3 column adds, idempotent).