by Intelliverse X

Role-based access control

Who can see what, and what they can do about it. The consoles (operator.kiosk-x.ai, /admin, /superadmin) do not invent a second permission model — they show whatever the Partner API grants the signed-in principal. Operator-OS labels such as field_tech are product segments, not API roles.

The source of truth for scopes is app/config.py (ALL_SCOPES, OPERATOR_SCOPES, FACTORY_SCOPES). How a request becomes a Principal is in authentication.md. Tenant narrowing lives on Principal.tenant_scope() / Principal.browse_scope() in app/auth.py.

The identities

These are independent gates. Holding one does not confer another.

Identity How you get it What it is
Operator Sign-in at /api/v1/auth/login, or an operator API key. operatorEmail is pinned. Scopes = OPERATOR_SCOPES. Owns a fleet. Sees only that fleet's machines, books, factory paperwork and shipping. Cannot write a build record or accept delivery of someone else's cabinet.
Full admin Admin API key, Cognito group admin / Admins / super_admin with accounts:admin, or an */all M2M client. Principal.full_admin = adminaccounts:admin. Cross-tenant reads and writes. Enumerates operators. Can emulate any operator for reads. Can write factory data (same PUT the plant uses). Cannot take the operator acceptance signature — that requires principal.operatorEmail.
Browse-admin A Cognito identity with admin=True but no accounts:admin. Sandbox browsing of non-money surfaces. Money, PII, accounts list, and the operator picker all 403. Cannot POST /sandbox/reset or POST /api/v1/orders/reconcile-stranded — those are full-admin money writes. Do not treat admin on /api/v1/auth/me as full admin.
MFA super-admin Operator login (factor 1a) whose email is on KIOSKX_SUPERADMIN_ALLOWLIST, plus KIOSKX_SUPERADMIN_PASSWORD, plus TOTP. Session cookie kioskx_superadmin/api/v1/superadmin/*. Autopilot capital, deal-level manufacturer orders, and the per-serial factory/shipping ledger. This is not the Cognito super_admin group. A Cognito super_admin without the MFA session cannot hit /superadmin. An MFA super-admin without accounts:admin still cannot impersonate writes on the operator API.
Factory / manufacturer Plant key (vtm_kioskx_factory_zhzn_…, vtm_kioskx_factory_hotbox_…, or the live KIOSKX_FACTORY_KEY). Scope is exactly machines:build, pinned to one manufacturer name. Writes build, tests, factory sign-off, and shipping on serials named as that plant's. Cannot list operators, read books, claim machines, see acquisition cost, or accept delivery. This is the manufacturer.kiosk-x.ai contract — there is no second frontend; the plant talks to these endpoints.

Consoles

Surface URL Who What they see
Operator console operator.kiosk-x.ai Any signed-in operator. Full admins land here too. Own fleet. Machine detail includes Build & factory QA (tests, both sign-offs, crate shipping).
Operator picker Top bar (desktop) / More (phone) / Admin sidebar Full admin only (canSwitchOperator = adminaccounts:admin) Sets kx.actingOperator. Subsequent reads append ?operatorEmail= on SCOPED_PREFIXES in lib/acting-operator.ts. Writes stay on the admin's own account. Reload on change. Banner is the reminder.
Admin hub /admin Same session as the operator console; pages assume a signed-in user. Cross-tenant data only if the principal is full admin. Fleet ops. Factory (/admin/factory) is GET /api/v1/machines/build/summary — auto-scoped by the picker. Machine detail mounts the same build card, without the Accept button.
Super-admin /superadmin MFA session on top of a signed-in allowlisted account. Orders = deal-level manufacturing status. Factory = every serial's QA, sign-off timestamps, carrier, tracking, ship-to. Links through to /admin/machines/{no}.

Manufacturer sign-off and shipping

There is one record per serial (app/machine_build.py), not a parallel "manufacturer portal database". The plant (or a full admin) writes it:

PUT /api/v1/machines/{machineNo}/build
{
  "manufacturer": "zhzn",
  "shippedAt": "2026-08-20",
  "shipping": {
    "carrier": "DHL",
    "trackingNumber": "DHL1234567890",
    "consignmentRef": "ZH-CRATE-014",
    "shipToName": "Superior Vape",
    "shipToAddress": "4100 S Dairy Ashford",
    "shipToCity": "Houston",
    "shipToRegion": "TX",
    "shipToPostal": "77082",
    "shipToCountry": "US"
  }
}

Then the bench posts tests and the factory QA signature (POST …/build/tests, POST …/build/signoff). The operator — and only the operator who owns the cabinet right now — accepts delivery (POST …/build/accept). Ownership is derived from STORE.machines; a resale does not orphan the test history.

Who can read that record:

Caller Own serial Another operator's serial
Owning operator Yes, including shipping. Acquisition cost is theirs. 404
Full admin / MFA super-admin Yes, every serial. Admin card hides Accept. Yes
Factory key for that plant Yes, acquisition stripped. 404 / 403 for other plants
Anyone else 404 404

Deal-level tracking on a DFY manufacturing order (/superadmin Orders, Autopilot deal page) answers "where is my purchase?". Serial-level shipping answers "where is this crate?". They are allowed to disagree; they are not the same field.

Plant runbook: zhzn-factory-install.md §4.

Acting-operator is a read preference, not impersonation

kx.actingOperator is stored in the browser and attached as ?operatorEmail= on listed prefixes. The API honours that parameter only for full_admin. An operator who forges the query still sees their own tenant. Creates, patches, accepts, payouts and payroll writes use the authenticated principal, which is why the picker footnote says so.

Operator-OS "roles"

POST /api/v1/operator-os/roles stores field_tech, refiller, etc. as osRoles on an email — the signed-in operator, a Team member, or (full admin) any mailbox, including plus-tags and corporate domains. Adding that person to Team with role: field_tech writes the same label. Those unlock agent flavours in the Operator OS product when that email later signs in. They confer no Partner API scope. Do not use them as RBAC. A crew mailbox is not an operator tenant: it does not appear in GET /api/v1/accounts and cannot sign in with the sandbox demo password.

POST /api/v1/accounts accepts roles: ["field_tech", …] on create (the old integer roleId was never stored).

Scope cheat sheet

Scope Operator Full admin Factory
machines:read / write own fleet all fleets no
inventory:* own fleet all fleets no
orders:* own sales all sales no
commands:* own cabinets all cabinets no
ads:* own campaigns all campaigns no
machines:build no (cannot write provenance) yes only this
accounts:admin no yes no

A credential missing a scope gets 403 Insufficient scope. A credential asking for another tenant's row gets 404 on operator-scoped resources so existence is not leaked.

What to click