2026-04-29 on prod. Final R4 sub-phase. tenants.plan_code and tenant_limits have been zombie writes since the 04-20 cpanel-pivot moved truth onto subscriptions(tenant_id, product_code).{plan_code, limits_json}. R4.4 finishes the migration: Cafe's getTenant() derives tier from subscriptions WHERE product_code='nix_cafe', the PayWay webhook stops writing the deprecated column, all 12 prod tests use a new cafePlanController helper to flip subscriptions.plan_code instead, the seed script upserts subscription rows for fresh DBs, and the migration drops both the column and the table.
getTenant() joins subscriptions in the same Hyperdrive-bypass transaction it already used for tenants + cafe.tenant_config. New deriveTier() maps cafe_starter→'starter', cafe_pro→'pro', cafe_master→'enterprise'. Defaults to 'pro' when no subscription row exists.billing.router.ts PayWay webhook stops writing tenants.plan_code on subscription activation. tenant.service.ts already strips legacy plan_code on tenant create (since 04-20).seed-supabase-getcoffee.ts now upserts subscription rows for both outdoor_sales (default outdoor_enterprise) and nix_cafe (default cafe_pro, env-overridable via SEED_CAFE_PLAN_CODE). Fresh DBs ship with full state from minute zero.cafePlanController(prodSql, tenantId) helper in test-utils.mjs. Captures the current subscriptions.plan_code for nix_cafe, exposes flipToStarter() + restore() with self-heal (cafe_starter → cafe_pro). Two-pass bulk-edit script handled the regex variations across files (older 1-line capture vs newer self-heal 2-line).node migrate.js output: "- Dropped tenants.plan_code column" + "- Dropped tenant_limits table". Idempotent — re-runs are no-ops.tenants.plan_code dropped. No remaining consumer.
| test-r4-3-prod.mjs | 8/8 |
| test-r4-2-prod.mjs | 9/9 |
| test-r4-1-prod.mjs | 9/9 |
| test-phase1-prod.mjs | 11/11 |
| test-receipt-preview-prod.mjs | 6/6 |
| test-r2-followups3-prod.mjs | 7/7 |
| test-r2-followups2-prod.mjs | 11/11 |
| test-r2-followups-prod.mjs | 19/19 |
| test-nix-os-r2-4b-prod.mjs | 12/12 |
| test-nix-os-70-2-prod.mjs | 10/10 |
| test-cafe-followups-prod.mjs | 5/5 |
nix-outdoor-sales-backend @ ab0ae72:
migrations/20260429300000_drop_legacy_tenant_plan_code_and_tenant_limits.ts — NEW
migrate.js — idempotent entry
src/billing/billing.router.ts — PayWay webhook
no longer writes
tenants.plan_code
nix-cafe @ 12d0660:
lib/tenant.ts — getTenant() reads
tier from
subscriptions in
the same tx;
deriveTier() maps
cafe_* → starter/
pro/enterprise
lib/db/schema.ts — drop tenants.planCode
field; add
subscriptions table
mapping
scripts/seed-supabase-getcoffee.ts — upserts both
outdoor_sales +
nix_cafe sub rows
scripts/seed-local.ts — drop planCode
D:/NIX Software/:
test-utils.mjs — NEW
cafePlanController
helper
12 test-*-prod.mjs files — bulk-migrated
via 2-pass regex
script
prod migration applied:
ALTER TABLE tenants DROP COLUMN plan_code
DROP TABLE tenant_limits
R4.1 ─ Multi-shop activation E2E ✓ shipped (2026-04-29) R4.x ─ Carried-forward follow-ups bundle ✓ shipped (2026-04-29) R4.2 ─ Impersonation ✓ shipped (2026-04-29) R4.3 ─ Per-product usage endpoint ✓ shipped (2026-04-29) R4.4 ─ Drop legacy tenant_limits/plan_code ✓ shipped (2026-04-29) Phase 5 (NIX Cpanel) per NIX_COMMERCE.md is functionally complete. Next big rocks: R3 — Phase 7 port Outdoor Sales into Commerce fully. Pro-tier Cafe latency mitigation (mirror Odoo orders into nix-db). R5+ TBD by Narong.