← All tests

POS User back-office lockdown STAGING

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.

Bug 1 — the POS User role was over-granted

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.

Bug 2 — Inventory and Finance had no gate at all

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.

Result

SectionBeforeAfter (POS User)
Productsvisible + reachablehidden · /cafe/products → /cafe/pos
Customersvisible + reachablehidden · /cafe/customers → /cafe/pos
Inventoryvisible + reachable (no gate)hidden · /cafe/inventory → /cafe/pos
Financevisible + reachable (no gate)hidden · /cafe/finance → /cafe/pos
The POS itselfworksstill works
POS User sidebar on staging
A real POS User logged in over SSO on 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.

Migration rehearsal on staging

+ 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.

Two test traps worth recording

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.

Checks

SuiteResult
Local — permission data, POS-still-works, nav, all 4 routes (2 consecutive runs)9/9
Staging — real POS User over SSO on the deployed Worker9/9
Staging smoke5/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.