# Business rules — unity-framework

Evidence date: 2026-09-30. Revision: `master` `c72613b` (2021-02-13).

No business-rules document existed in the repository or in `silicon_ark/unity-framework/` before this file. Rules below are taken from code. Each rule is marked **Documented**, **Implemented**, or **Inferred**. Nothing here is an approved policy unless the code itself enforces it.

There is no domain document for customers, billing, or tenancy. The product surface that has rules is sign-in, route access, and user records.

## Identity and access

### BR-001 — Guest is an empty role

**Implemented.** Initial user state is `role: []` (`src/app/auth/store/userSlice.js`). `FuseUtils.hasPermission` treats a missing or empty role as the guest.

### BR-002 — Empty auth array means guests only

**Implemented.** `authRoles.onlyGuest` is `[]`. Login (`/login`) and register (`/register`) use it. A user with a non-empty role is redirected away from those routes (`FuseAuthorization.js`).

### BR-003 — Null auth means the route is open

**Implemented.** `routesConfig.js` generates routes with default auth `null`. `hasPermission` returns true when `auth` is null or undefined. Demo apps and `/apps/users` do not set their own `auth`, so a guest can open them. The Cognito setting `mandatorySignIn: true` does not change this route table.

### BR-004 — Cognito sign-in always stores the role superuser

**Implemented.** `buildUser` in `src/app/services/jwtService/jwtService.js` sets `role: "superuser"`. It does not read a role from the API body. `src/app/auth/authRoles.js` has no `superuser` entry. The role names that file does define are `admin`, `staff`, and `user`.

**Inferred.** The string `superuser` was copied as a fixed session flag, not as a privilege list. No code path grants or denies a screen because the role equals `superuser`.

### BR-005 — Logout allows admin, staff, and user only

**Implemented.** `/logout` sets `auth: authRoles.user`, which is `['admin','staff','user']`. Role `superuser` does not match. Logout clears `localStorage.jwt_access_token` and does not call Cognito `Auth.signOut()`.

### BR-006 — Named roles on a few navigation examples

**Implemented.** `navigationConfig.js` still attaches `onlyGuest`, `user`, `admin`, or `staff` to a handful of Authentication Pages entries (around the lines that set `auth: authRoles.*`). Those are Fuse examples. They are not applied to User Management.

### BR-007 — Access-control checkboxes are not a rule engine

**Implemented** UI only. View, Add, Edit, and Delete render per navigation child. The profile tab initialises every box to checked and does not save. The dialog variant initialises from `user.permissions[id + 'View'|'Add'|'Edit'|'Delete']` and is not opened by the list. No route consults those flags.

**Inferred** from commented code: someone intended permissions to be stored on the user as keys `{itemId}View`, `{itemId}Add`, `{itemId}Edit`, `{itemId}Delete`. That store is not written by the live tab. Confirm before treating it as a rule.

## Sign-in and registration

### BR-008 — Login fields are required and at least 4 characters

**Implemented.** `JWTLoginTab.js`. There is no format check on the email. The same minimum is not evidence of a Cognito password policy. Cognito's pool policy is **Unknown / Requires Confirmation** (it is not in this repository).

### BR-009 — Sign-in identity for the API is the Cognito username

**Implemented.** The uuid request uses `response.username` from `Auth.signIn`, not the email field, in the path `/unity/uuid/{username}`.

### BR-010 — Profile fields taken from the first API row

**Implemented.** `buildUser` expects `JSON.parse(body)` to be an array and reads index `0`: `strUser` → display name, `strEmail` → email, `strCognitoUuid` → uuid, `strSettings` → settings passed to `setDefaultSettings`. Photo URL is the static Abbott avatar. Shortcuts are always `calendar`, `mail`, `contacts`.

If `body` is missing or the array is empty, the promise throws. There is no "user not provisioned" screen.

### BR-011 — Register does not create a Cognito user

**Implemented.** `createUser` posts `/api/auth/register`. The mock rejects a duplicate email ("The email is already in use") and an empty display name ("Enter display name"). It does not check password strength. The new mock user role is `admin`.

### BR-012 — A stored token is kept only until its exp, then the mock must accept it

**Implemented.** `isAuthTokenValid` uses `jwt-decode` and compares `exp` to `Date.now()/1000`. If the token is still unexpired, `signInWithToken` asks the mock to `jwt.verify` it with the mock secret. Cognito tokens fail that verify. The client then logs out with "Failed to login with token." or the mock's 401.

### BR-013 — Recover Account is not a reset flow

**Implemented.** The login link target is `/`, and `/` redirects to `/login`. No email is collected.

## User records

### BR-014 — The list is loaded with a fixed path suffix

**Implemented.** Every visit calls POST `/unity/getusers/1`. `routeParams` from `/apps/users/:id` (including `all`) are stored and not sent. A comment in `unityRequests.js` mentions `/unity/getusers/2`. The meaning of the number is **Unknown / Requires Confirmation**. Do not document it as a company id.

### BR-015 — The list request builds a bearer token and does not send it

**Implemented.** `unityRequests` awaits `Auth.currentSession()`, puts the id token in `myInit.headers.Authorization`, and then calls `API.post(apiName, path)` with no init object. Whether Amplify attaches credentials on its own is **Unknown / Requires Confirmation**. The code as written does not pass the header it constructed.

### BR-016 — List columns map to specific response fields

**Implemented** bindings, response contract **Unknown** beyond the accessors:

| Column | Field |
|---|---|
| Avatar alt text | `strUser` |
| Avatar image | `strPicturePath` |
| First Name | `strUser` |
| Job Title | `strJobTitle` |
| Email | `strEmail` |
| User Created | `strLastUser` |
| Profile link id | `UserID` |
| Delete id | `id` |

`UserID` and `id` are different properties. Delete can run with `undefined` if the row has only `UserID`.

### BR-017 — Search is client-side and resets on reload

**Implemented.** `FuseUtils.filterArrayByString` over the entities already in memory. `getUsers.fulfilled` sets `searchText` back to `''`.

### BR-018 — A new dialog user must have a non-empty name

**Implemented.** `UserDialog.canBeSubmitted` is `form.name.length > 0`. Email, phone, and the other fields are optional in the dialog. The id for a new dialog user is `FuseUtils.generateGUID()`.

### BR-019 — Dialog writes and the list read are different stores

**Implemented.** Add, update, and remove post to `/api/users-app/*` on the axios mock (`src/@fake-db/db/users-db.js`). The list then reloads from POST `/unity/getusers/1`. A successful dialog save does not by itself change the API list.

### BR-020 — Profile open requires location state

**Implemented.** `UserProfilePage` uses `props.location.data`. The route param `:id` is not read. Missing `location.data` pushes `/apps/users/all` during render. A bookmark or refresh of the profile URL does not re-read the id from the path.

### BR-021 — Details save and deactivate are the same action

**Implemented.** Both buttons call `handleSubmit` → `updateUser(form)` → mock update. No status field is set to inactive.

### BR-022 — Follow and Send Message do nothing

**Implemented.** The buttons render and have no `onClick` (`UserProfilePage.js`).

### BR-023 — Entity groups on the unused buildUser helper are fixed sample data

**Implemented** only inside the uncalled `buildUser` in `usersSlice.js`: groups named Friends, Clients, and Recent Workers with hard-coded user id lists, plus hard-coded `starred` and `frequentUsers`. The live list does not use this function. These are not evidence of a group-membership rule.

## Data the client expects from the unity API

**Inferred** from destructuring, not from a schema file:

| Call | Expected shape the client touches |
|---|---|
| GET `/unity/uuid/{username}` | `{ body: string }` and `JSON.parse(body)[0]` with `strUser`, `strEmail`, `strCognitoUuid`, `strSettings` |
| GET `/unity/getuser/{id}` | same wrapper; the profile header also reads `strUsr` and `avatar` |
| POST `/unity/getusers/1` | `{ body: string }` parsed to an array that `usersAdapter.setAll` can store. The table reads `strUser`, `strPicturePath`, `strJobTitle`, `strEmail`, `strLastUser`, `UserID`, and `id` |

If Amplify already unwraps the Lambda proxy body, `const {body} = await API...` will not see a `body` property. That mismatch is visible in the client and was not executed against the API in this investigation. **Unknown / Requires Confirmation** which shape the live API returns.

## Explicit non-rules

- No rate limit, captcha, or password-rotation rule is implemented in this repository.
- No row-level security is implemented here. Any authorizer would be on API Gateway or Cognito, which this repo does not contain.
- `MAX_ATTACHMENT_SIZE` (5000000) is not referenced by an upload function.
- i18n language is fixed to `en` in `src/i18n.js`. Navigation bundles for `en`, `tr`, and `ar` are registered. No language-based business rule was found.
