Narong's Next-Phase POS items, all four red-flagged 2026-07-24. Includes a migration.
His asks: "Hide Products as POS Users" · "Hide Customers" · "Hide Inventory" · "Hide Finance"
All four verified against staging before building — unlike the Order-Management list, where 3 of 5 ❌ turned out to be stale. These were real, and they were two different bugs.
The n1 migration gave POS User the same 8 permissions as Cashier, including
nix_cafe.products.view and nix_cafe.customers.view, on the stated assumption the till
needed them to "select customers" and list products. It doesn't. Every usage was traced: the
register gates on nix_cafe.pos.view alone, and the till's customer picker calls
/api/odoo/customers, which performs no permission check. Those two codes are read only by
/products and /customers — so the grant let a POS User open the full product and customer
admin.
Migration 20260724120000 revokes both. Because those pages already call
requirePermission(), one data change hides the nav and blocks the routes.
Neither nav item had a requires, and neither route had a requirePermission — any
authenticated user could reach them. Both are ComingSoon stubs so nothing was exposed, but they
shouldn't appear in a POS User's world. Now gated, with matching entries in the middleware route map.
| Section | Before | After (POS User) |
|---|---|---|
| Products | visible + reachable | hidden · /cafe/products → /cafe/pos |
| Customers | visible + reachable | hidden · /cafe/customers → /cafe/pos |
| Inventory | visible + reachable (no gate) | hidden · /cafe/inventory → /cafe/pos |
| Finance | visible + reachable (no gate) | hidden · /cafe/finance → /cafe/pos |
| The POS itself | works | still works |

get-coffee.staging. Products, Customers, Inventory and Finance are gone; Point of Sale and Order Management remain. Shown on /cafe/orders — the POS screen itself has no sidebar, so asserting there would pass vacuously.+ POS User role: 0 role(s) created, 0 grant(s) added across 3 tenant(s) - pos_user: revoked back-office view perms (6 grant(s) across 3 role(s))
2 codes × 3 tenants = 6. The 0 grant(s) added line matters: migrate.js re-runs
every entry on each invocation, so the n1 entry's permission list had to have those two codes removed as
well — otherwise it would re-grant them on every run and this migration would just undo it again. After: pos_user
8 → 6 permissions on all three staging tenants, pos.view retained.
Deliberately NOT changed: the Cashier role
Cashier carries the identical over-grant (it shares CASHIER_PERMS), so cashiers can still open
/products and /customers. Narong asked specifically about POS Users, so the change is
scoped to pos_user and this is flagged rather than changed silently. The test asserts cashier is
untouched, so the scope can't drift by accident.
HTTP status proves nothing here. requirePermission() redirects from a
streaming render, so Next can only emit a 200 carrying a client-side redirect.
/reports and /team return 200 for a POS User too, and always have. An early version of
this test asserted on status and reported every route as unguarded — wrongly.
A shared browser context lies. Next's client Router Cache keeps serving the previous shell, so the URL never changes and a guarded route looks unguarded. Only a fresh context per route is honest. Both suites now do that.
| Suite | Result |
|---|---|
| Local — permission data, POS-still-works, nav, all 4 routes (2 consecutive runs) | 9/9 |
| Staging — real POS User over SSO on the deployed Worker | 9/9 |
| Staging smoke | 5/5 |
tsc --noEmit (nix-cafe + backend) | clean |
Negative control: rolling the migration back fails exactly the four right assertions and isolates
the two mechanisms — Products/Customers become reachable again while Inventory/Finance stay blocked, because those
are code guards. It also confirms customers.create and pos.view survived, so nothing was
over-revoked.
nix-cafe staging aaf7c46 (Worker ded512ac) · nix-OS-backend
staging ba3734c. Migration 20260724120000 — the 5th in the parked
promote bundle (still one node migrate.js). Note: nix_cafe.reports.view gates
Inventory/Finance as a deliberate placeholder — inventing nix_cafe.inventory.view now would pre-empt
the permission model NIX Inventory Lite still has to define.