¶ Built
The BU is registered in src/bu/registry.ts and has screens.
The Ops app serves several business units (BUs) from one codebase. Each BU keeps its own Laravel backend, and the app reaches every one of them through Utopia OE, the Spring Boot gateway in utopiaspace-core.
This page explains how a signed-in worker lands in a BU, what OE does with each request, and how a new BU is added. The full standard is in src/bu/README.md and src/bu/AGENTS.md in the utopiaspace-ops repository.
The app only offers a BU that passes all three checks.
The BU is registered in src/bu/registry.ts and has screens.
The build lists it in EXPO_PUBLIC_ENABLED_BUS. Production leaves this unset, which means Encik Beku only.
OE's /oe/auth/resolve returns it for this person, based on their unit scope in User Access.
OE has a development switch, OE_DEV_ALLOW_ALL_UNITS, that hands everyone every BU. OE marks those extra BUs as devGranted.
devGranted.Signing out clears every cached result, so the next person on a shared phone never sees the last person's data.
OE stores nothing and changes nothing. It forwards the method, path, query, and body as they are, and returns the BU backend's reply unchanged.
| Route group | Caller | Token the BU backend receives | Who checks access |
|---|---|---|---|
/api/v1/ops/** | The Ops app, through OE | The worker's Hub token | The BU backend: "is this my job or trip?" |
/api/v1/oe/** | The OE server (dispatch board, provisioning) | The BU's machine token | OE, which has already checked the user |
X-OE-BU header picks the BU. Without it, OE uses its default, Encik Beku. Encik Beku calls send no header.
A new /api/v1/ops/* route on a BU backend needs no change in OE.
Each BU is one oe.bu.<key> block in OE's application.yaml, with BU_<CODE>_BASE_URL, BU_<CODE>_TOKEN, and OE_BU_<KEY>_UNIT_ID. A blank URL or token leaves the BU switched off.
On first sign-in OE calls the BU's POST /api/v1/oe/auth/provision to create the person's user there.
Each BU has a fixed key, env code, and local port digit. Backends run on 808N and portals on 300N.
| # | BU | Key and env code | State |
|---|---|---|---|
| 0 | Encik Beku | encik-beku, EB | Live. Its screens still sit in the shared folders (app/(tabs), src/features/*). |
| 1 | Utopia OE | none | The gateway, on local port 8081. |
| 2 | Ibnu Sina | ibnu-sina, IB | Built, not switched on in production. See Ibnu Sina. |
| 3 | Scaffolding Malaysia | scaffolding, SC | App shell only. The backend has no /oe or /ops routes yet. Its README (15 Sep 2026) warns that OE production already has Scaffolding configured, so sign-ins try to provision there and get a 404. |
| 4 | Sticker Lorry | sticker-lorry, SL | Planned. A README only. |
| 5 | Jomvend | jomvend, JV | Planned. A README only. |
Never create a BU by hand. The script creates the folder and registers it everywhere it needs to be.
src/bu/<key>/ with its own API client, UI parts, navigation, and Profile screen, plus route files in app/<key>/.registry.ts, slices.json, the BU picker, and both language files.oe.bu.<key> block in OE and its values in utopiaspace-core/.env./oe and /ops routes, copying Ibnu Sina, and move it to ports 808N and 300N.EXPO_PUBLIC_ENABLED_BUS in .env.local.src/bu/README.md.Enforced by ESLint from src/bu/slices.json.
src/components, or another BU. Each BU keeps its own copy of buttons, dialogs, and the tab bar. The duplication is on purpose.
All API calls go through the BU's own client so the X-OE-BU header is always sent.
Screens show only the steps the BU backend supports. No invented fields, statuses, or steps.
Proof photos come from the camera only.
Any rule that decides something goes in lib/ with a test.