toga-ai 1.0.76 → 1.0.78
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/knowledge/1.0/apps/togadesk/INDEX.md +1 -1
- package/knowledge/1.0/apps/togadesk/features/field-service-dispatch.md +37 -1
- package/knowledge/2.0/apps/toga2-hub/INDEX.md +6 -0
- package/knowledge/2.0/apps/toga2-hub/architecture.md +156 -0
- package/knowledge/2.0/apps/toga2-hub/features/acl-permissions-rendering.md +111 -0
- package/knowledge/INDEX.md +1 -0
- package/knowledge/registry.json +3 -1
- package/package.json +1 -1
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
|-----|---------|-------|
|
|
5
5
|
| [TOGa Desk Architecture](architecture.md) | TOGa Desk is the staff-facing support desk **and field-service platform** (analysts work at `/desk/`). | desk/index.php, desk/includes/loader.php, desk/config.php, desk/includes/controllers/actions.php, desk/includes/controllers/data.php, desk/includes/controllers/modals.php, desk/includes/classes/class.app.php, desk/includes/classes/class.ticket.php, desk/api/index.php, desk/api/resources/tickets.php, crons/tickets.php |
|
|
6
6
|
| [Email-to-Ticket Intake (crons/tickets.php)](features/email-to-ticket-intake.md) | TOGa Desk ingests support email into tickets through a cron-driven IMAP poller (`crons/tickets.php`) plus a postfix pipe variant (`crons/pipe.php`). | crons/tickets.php, crons/tickets_prod.php, crons/pipe.php, desk/includes/classes/class.ticket.php |
|
|
7
|
-
| [Field-Service Dispatch (central / repair orders)](features/field-service-dispatch.md) | The **central** subsystem is TOGa Desk's field-service dispatch domain: repair-order lifecycle, technician scheduling, onsite vs depot service, parts, and shipm | desk/includes/controllers/actions/central/, desk/includes/classes/class.repair.php, desk/includes/classes/class.repairhistory.php, desk/_/browser/datatable/central.php, desk/template/pages/central.php, desk/template/pages/central/view.php |
|
|
7
|
+
| [Field-Service Dispatch (central / repair orders)](features/field-service-dispatch.md) | The **central** subsystem is TOGa Desk's field-service dispatch domain: repair-order lifecycle, technician scheduling, onsite vs depot service, parts, and shipm | desk/includes/controllers/actions/central/, desk/includes/classes/class.repair.php, desk/includes/classes/class.repairhistory.php, desk/_/browser/datatable/central.php, desk/template/pages/central.php, desk/template/pages/central/view.php, desk/template/modals/central/addTracking.php, library/app/model/togadesk/repairordertracking.php, library/app/api/carrier/ups.php |
|
|
8
8
|
| [REST API (RPC-over-POST) & API-Key Auth](features/rest-api.md) | TOGa Desk exposes a programmatic API at `desk/api/`. | desk/api/index.php, desk/api/resources/tickets.php, desk/api/resources/assets.php, desk/api/resources/authenticate.php, desk/includes/classes/class.apikey.php, desk/includes/functions.php |
|
|
9
9
|
| [SMB Contract Editing & the clientMspId Corruption Trap](features/smb-contract-editing.md) | The SMB contracts page (`/desk/?route=toga/smbcontracts&togaClientId=<id>`) edits `TOGA_*.SMBContracts` rows via a modal. | desk/template/modals/toga/smbcontracts/smbContract.php, desk/includes/controllers/modals/toga/smbcontracts/smbContract.php, desk/includes/controllers/actions/toga/smbcontracts/smbContract.php, desk/includes/controllers/actions/toga/smbcontracts/edit.php |
|
|
10
10
|
| [Ticket Lifecycle (class.ticket.php)](features/ticket-lifecycle.md) | All TOGa Desk ticket creation and reply handling funnels through `Ticket` in `desk/includes/classes/class.ticket.php`. | desk/includes/classes/class.ticket.php, desk/includes/controllers/actions.php, desk/api/resources/tickets.php, crons/tickets.php, desk/includes/controllers/actions/tickets/merge.php |
|
|
@@ -7,7 +7,7 @@ client: shared
|
|
|
7
7
|
type: feature
|
|
8
8
|
status: active
|
|
9
9
|
updated: 2026-06-15
|
|
10
|
-
owners: ["jcardinal"]
|
|
10
|
+
owners: ["jcardinal", "mhammontree"]
|
|
11
11
|
files:
|
|
12
12
|
- desk/includes/controllers/actions/central/
|
|
13
13
|
- desk/includes/classes/class.repair.php
|
|
@@ -15,6 +15,9 @@ files:
|
|
|
15
15
|
- desk/_/browser/datatable/central.php
|
|
16
16
|
- desk/template/pages/central.php
|
|
17
17
|
- desk/template/pages/central/view.php
|
|
18
|
+
- desk/template/modals/central/addTracking.php
|
|
19
|
+
- library/app/model/togadesk/repairordertracking.php
|
|
20
|
+
- library/app/api/carrier/ups.php
|
|
18
21
|
related:
|
|
19
22
|
- 1.0/apps/togadesk/architecture.md
|
|
20
23
|
- 1.0/apps/togadesk/features/ticket-lifecycle.md
|
|
@@ -53,6 +56,25 @@ the originating page. Status changes are audited through `RepairHistory` (`repai
|
|
|
53
56
|
onsite/install started → review → closed; depot repairs add parts/tracking steps. Bulk actions
|
|
54
57
|
exist for closing installs and editing onsite tickets.
|
|
55
58
|
|
|
59
|
+
## UPS shipment / carrier-account billing (Prepare Shipment flow)
|
|
60
|
+
The "PREPARE SHIPMENT" button opens modal `desk/template/modals/central/addTracking.php`,
|
|
61
|
+
whose carrier/account dropdown is built from the constant
|
|
62
|
+
`App_Model_TogaDesk_RepairOrderTracking::CARRIER_ACCOUNTS`
|
|
63
|
+
(`library/app/model/togadesk/repairordertracking.php`) — a display⇒value list of multiple
|
|
64
|
+
carrier accounts (Agilant `8696XA`, ODP Veyer `H4Y167`, Compass `33951W`, Advance Auto
|
|
65
|
+
`2V1Y10`, Vision Precision `87161X`, plus FedEx accounts). Submit calls
|
|
66
|
+
`Repair::addTracking($_POST)` (~L600 in `class.repair.php`), which builds an
|
|
67
|
+
`App_Api_Carrier_ShipmentRequest` and calls `App_Api_Carrier_UPS::submitShipmentRequest()`.
|
|
68
|
+
|
|
69
|
+
**The displayed account and the billed account come from different places.** The dropdown is
|
|
70
|
+
just `CARRIER_ACCOUNTS`; the account that actually ships/bills is the hardcoded
|
|
71
|
+
`const SHIPPER_NUMBER` in `library/app/api/carrier/ups.php`, injected into the UPS payload as
|
|
72
|
+
*both* `Shipment.Shipper.ShipperNumber` and `BillShipper.AccountNumber`. `SHIPPER_NUMBER` is
|
|
73
|
+
shared **library core** consumed by every 1.0 app (togadesk, togaview, worker); a separate
|
|
74
|
+
synced copy lives in `_underscore` for 2.0 apps, plus a hardcoded `<ShipAccount>` in
|
|
75
|
+
`library/app/api/essls.php`. Full account-location map:
|
|
76
|
+
`C:\WWW\shipping_carrier_ups_account_location.md`.
|
|
77
|
+
|
|
56
78
|
## Data model
|
|
57
79
|
`repair_orders`, `repair_order_customers`, `repair_order_addresses`, `repair_order_technicians`,
|
|
58
80
|
`repair_history` (in `db_togadesk`), joined to TOGA `ServiceRequests` for managed-service-order
|
|
@@ -70,7 +92,21 @@ always confirm the client arm before changing shared central logic.
|
|
|
70
92
|
an order in an inconsistent state.
|
|
71
93
|
- Cross-database context: some central data controllers switch the DB link to a client database
|
|
72
94
|
(`App_Model_Client::changeDbLinkToClientDatabase()`).
|
|
95
|
+
- **UPS carrier-account dropdown is NOT honored by the shipment API call.** In
|
|
96
|
+
`Repair::addTracking()` the selected `carrierAccount` is only saved onto the tracking record
|
|
97
|
+
(`$track->carrierAccount = $data['carrierAccount']`, ~L932/949/966); it is never passed into
|
|
98
|
+
the `ShipmentRequest`. `submitShipmentRequest()` therefore always bills the hardcoded
|
|
99
|
+
`SHIPPER_NUMBER` (Agilant), so picking ODP Veyer / Compass / etc. has no billing effect. True
|
|
100
|
+
third-party billing would require passing the chosen account into the request via a UPS
|
|
101
|
+
`BillThirdParty` / `BillReceiver` payment block instead of `BillShipper`.
|
|
102
|
+
- The Agilant UPS account is **hardcoded in core, not config/DB** — changing it means editing
|
|
103
|
+
`SHIPPER_NUMBER` in both framework cores (`library` and `_underscore`), the ESSLS
|
|
104
|
+
`<ShipAccount>` XML, the `CARRIER_ACCOUNTS` Agilant entry, and test fallbacks. See the
|
|
105
|
+
account-location map noted under *UPS shipment / carrier-account billing*.
|
|
73
106
|
|
|
74
107
|
## Change history
|
|
108
|
+
- 2026-06-15 — documented UPS Prepare Shipment flow: CARRIER_ACCOUNTS dropdown vs hardcoded
|
|
109
|
+
SHIPPER_NUMBER, and the gotcha that the dropdown selection is never forwarded to the UPS API
|
|
110
|
+
(always bills Agilant). Agilant account migrated 135FV5 → 8696XA. (mhammontree)
|
|
75
111
|
- 2026-06-15 — documented from a source read of `controllers/actions/central/` and
|
|
76
112
|
`class.repair.php` (jcardinal)
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
# toga2-hub (TOGA Hub) — 2.0 knowledge
|
|
2
|
+
|
|
3
|
+
| Doc | Summary | Files |
|
|
4
|
+
|-----|---------|-------|
|
|
5
|
+
| [TOGA Hub Architecture](architecture.md) | toga2-hub (TOGA Hub) is a React + TypeScript single-page admin portal for the TOGA platform. | toga2-hub/src/main.tsx, toga2-hub/src/routes.tsx, toga2-hub/src/App.tsx, toga2-hub/src/api/axiosInstance.ts, toga2-hub/src/api/apiFunctions.ts, toga2-hub/src/utils/queryHelpers.ts, toga2-hub/src/contexts/AuthContext.tsx, toga2-hub/src/stores/useUserStore.ts |
|
|
6
|
+
| [ACL Record Permissions — Rendering Semantics (Permissions Page)](features/acl-permissions-rendering.md) | How the TOGA Hub Permissions page must interpret the api2 endpoint `GET /v2/acl-record-permissions/getAclRecordPermissionsByRecordAndRole` and turn its response | toga2-hub/src/pages/Permissions/api/permissionsApi.ts, toga2-hub/src/pages/Permissions/utils/permissionsUtils.ts, toga2-hub/src/pages/Permissions/viewModel/useRolesViewModel.ts, toga2-hub/src/hooks/useAclPermissionsByRoleQuery.ts, toga2-hub/src/pages/Permissions/view/RolesView/RoleDetailView/LogicGroupView.tsx, toga2-hub/src/pages/Permissions/view/RecordsView/RecordDetailView/RecordDetailView.tsx, toga2-hub/src/hooks/useQueryData.ts |
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: TOGA Hub Architecture
|
|
3
|
+
framework: "2.0"
|
|
4
|
+
repo: toga2-hub
|
|
5
|
+
project: TOGA Hub
|
|
6
|
+
client: shared
|
|
7
|
+
type: architecture
|
|
8
|
+
status: active
|
|
9
|
+
updated: 2026-06-15
|
|
10
|
+
owners: [tcox]
|
|
11
|
+
files:
|
|
12
|
+
- toga2-hub/src/main.tsx
|
|
13
|
+
- toga2-hub/src/routes.tsx
|
|
14
|
+
- toga2-hub/src/App.tsx
|
|
15
|
+
- toga2-hub/src/api/axiosInstance.ts
|
|
16
|
+
- toga2-hub/src/api/apiFunctions.ts
|
|
17
|
+
- toga2-hub/src/utils/queryHelpers.ts
|
|
18
|
+
- toga2-hub/src/contexts/AuthContext.tsx
|
|
19
|
+
- toga2-hub/src/stores/useUserStore.ts
|
|
20
|
+
related:
|
|
21
|
+
- 2.0/apps/toga2-hub/features/acl-permissions-rendering.md
|
|
22
|
+
---
|
|
23
|
+
|
|
24
|
+
## Summary
|
|
25
|
+
|
|
26
|
+
toga2-hub (TOGA Hub) is a React + TypeScript single-page admin portal for the TOGA
|
|
27
|
+
platform. It is a pure frontend client — all business logic and persistence live in the
|
|
28
|
+
`api2` backend (framework 2.0), which Hub talks to exclusively over REST. The portal is
|
|
29
|
+
multi-tenant: it selects both its API endpoint and theme from the hostname it is served
|
|
30
|
+
from (e.g. `toga2-hub`, `compass.togahub`).
|
|
31
|
+
|
|
32
|
+
Primary surfaces: **Login** (auth), **Permissions** (manage roles, records, and ACL
|
|
33
|
+
field permissions), and **Users** (user management). The Permissions surface is the most
|
|
34
|
+
involved; its rendering rules are documented separately in
|
|
35
|
+
[ACL Record Permissions — Rendering Semantics](features/acl-permissions-rendering.md).
|
|
36
|
+
|
|
37
|
+
## Stack
|
|
38
|
+
|
|
39
|
+
- React 18.3 + TypeScript 5.9, built with **Vite 5.4** (no Webpack).
|
|
40
|
+
- `@tanstack/react-query` 5.90 — server state, fetching, mutations.
|
|
41
|
+
- `zustand` 4.5 with `persist` — lightweight client state.
|
|
42
|
+
- `react-router-dom` — routing. `react-hook-form` 7.72 — forms.
|
|
43
|
+
- `axios` — HTTP client.
|
|
44
|
+
- `@agilant/toga-blox` — shared Agilant UI library (provides `EnvironmentBadge`,
|
|
45
|
+
`performLogout`, shared components).
|
|
46
|
+
- Tailwind CSS 3.4 (large custom palette: `hub-primary-*`, `supply-blue-*`, `navy-*`,
|
|
47
|
+
`crimson-*` — prefer these over stock Tailwind) + SCSS via `sass-embedded`.
|
|
48
|
+
- Cypress 15.5 for E2E. No unit-test runner is configured (despite `@types/jest`).
|
|
49
|
+
|
|
50
|
+
## API layer (how it talks to api2)
|
|
51
|
+
|
|
52
|
+
Entry points: `src/api/axiosInstance.ts` (instance + interceptors), `src/api/apiFunctions.ts`
|
|
53
|
+
(`apiGet / apiPost / apiPut / apiDelete<T>(route, [data], options, params)`).
|
|
54
|
+
|
|
55
|
+
**Base-URL resolution is hostname-aware.** axiosInstance reads `window.location.hostname`,
|
|
56
|
+
uppercases the first segment, and looks up `VITE_API_<SEGMENT>` (e.g. host
|
|
57
|
+
`compass.togahub` → `VITE_API_COMPASS`), falling back to `VITE_API`. A new tenant hostname
|
|
58
|
+
needs a matching `VITE_API_*` env var.
|
|
59
|
+
|
|
60
|
+
**Auth tokens** (localStorage keys `accessToken`, `refreshToken`, `user`):
|
|
61
|
+
- No user yet → `POST /auth/public` obtains a public token.
|
|
62
|
+
- Logged in → uses the access token from login.
|
|
63
|
+
- On `401` → `POST /auth/refresh`; retry the original request. If refresh fails,
|
|
64
|
+
`performLogout()` (from toga-blox) runs.
|
|
65
|
+
|
|
66
|
+
**Interceptors**: every request receives a UUID `transactionId` query param (tracing) and
|
|
67
|
+
an `Authorization: Bearer <token>` header.
|
|
68
|
+
|
|
69
|
+
**Query language**: api2 uses a structured query object (`fields`, `join`, `ojoin`,
|
|
70
|
+
`where`, `sort`) serialized to a query string by `src/utils/queryHelpers.ts`
|
|
71
|
+
(`assembleOptions`). This is **not GraphQL**. Every read goes through it, so learn it early.
|
|
72
|
+
The `fields` parameter is an allowlist enforced by api2 (EV-8 validation in api2 `V2.php`):
|
|
73
|
+
a field not in the record's allowlist silently won't return, and a single prohibited field
|
|
74
|
+
can fail the whole fetch (see the ACL feature doc for the `Roles.id` / `EZ-2` example).
|
|
75
|
+
Casing must match exactly what api2 expects.
|
|
76
|
+
|
|
77
|
+
## State management
|
|
78
|
+
|
|
79
|
+
- **AuthContext** (`src/contexts/AuthContext.tsx`) — `isAuthenticated`, `user`, `login()`,
|
|
80
|
+
`logout()`, `setUser()`, `isAuthInitialized`; syncs localStorage and listens for cross-tab
|
|
81
|
+
`storage` events.
|
|
82
|
+
- **Zustand** `useUserStore` (`src/stores/useUserStore.ts`) — persisted under key `zu-user`;
|
|
83
|
+
holds the user alongside AuthContext.
|
|
84
|
+
- **React Query** — configured in `main.tsx` with `retry: false`,
|
|
85
|
+
`refetchOnWindowFocus: false`.
|
|
86
|
+
- No Redux. Logout clears `accessToken`, `refreshToken`, `user`, and the `zu-user` store. If
|
|
87
|
+
logout cleanup ever grows, centralize the wipe rather than maintaining per-key lists
|
|
88
|
+
(per-key lists drift — a lesson learned the hard way in sibling repo toga2-commerce).
|
|
89
|
+
|
|
90
|
+
## Routing & layout
|
|
91
|
+
|
|
92
|
+
- `src/main.tsx` — bootstraps the render tree: `QueryClientProvider` → `AuthProvider` →
|
|
93
|
+
`RouterProvider`.
|
|
94
|
+
- `src/routes.tsx` — routes: `/login` (unauthenticated), `/permissions`, `/users`; root `/`
|
|
95
|
+
redirects to `/permissions?tab=roles`. Permissions carries UI state in query params
|
|
96
|
+
(`?tab=roles|records&roleUuid=…&recordId=…`).
|
|
97
|
+
- `src/App.tsx` — authenticated layout: `AppHeader`, `EnvironmentBadge` (shows BETA/GAMMA/…),
|
|
98
|
+
`VerticalNavBar`, and the route `Outlet`.
|
|
99
|
+
|
|
100
|
+
## Critical flows
|
|
101
|
+
|
|
102
|
+
- **Login** — `pages/Login/LoginPage.tsx` → `pages/Login/api/LoginApi.tsx`
|
|
103
|
+
(`validateEmailDomain`, `useSubmitLoginPassword`) → `viewModel/useLoginPageViewModel.ts`.
|
|
104
|
+
Flow: enter email → local validate → `validateEmailDomain` (`/domains`) →
|
|
105
|
+
`POST /auth/login` → store tokens + user → navigate to `/`. Login copy is data-driven from
|
|
106
|
+
`LOGINFIELDS.json` (edit the JSON, not the component).
|
|
107
|
+
- **Permissions** — `pages/Permissions/view/PermissionsPage.tsx` tabs into `RolesView/` and
|
|
108
|
+
`RecordsView/`. Rendering rules in the ACL feature doc.
|
|
109
|
+
- **Users** — `pages/Users/view/UsersPage.tsx` → `UsersView/`, `UserDetailView/`.
|
|
110
|
+
|
|
111
|
+
## Build, environments & deploy
|
|
112
|
+
|
|
113
|
+
Scripts (`package.json`): `npm run dev`; `npm run togahub` / `npm run compass` (dev on a
|
|
114
|
+
specific hostname); `npm run build` = `tsc -b && vite build` (type-checks then bundles to
|
|
115
|
+
`dist/`); `npm run lint`; `npm run cypress`.
|
|
116
|
+
|
|
117
|
+
Environment config via `.env.<mode>` files; only `VITE_`-prefixed vars reach the client.
|
|
118
|
+
|
|
119
|
+
| Env | API base URL |
|
|
120
|
+
|---|---|
|
|
121
|
+
| production | `https://api.togahub.com/v2` |
|
|
122
|
+
| beta | `https://api.beta.togahub.com/v2` |
|
|
123
|
+
| alpha/gamma | `https://api.gamma.togahub.com/v2` |
|
|
124
|
+
| development | beta API by default |
|
|
125
|
+
|
|
126
|
+
> **Dev points at the BETA API by default.** When debugging data, confirm whether you're
|
|
127
|
+
> looking at beta or prod — beta and prod also differ in DB schema naming (the Core DB with
|
|
128
|
+
> RecordFields/ACL lives in a separate `prod-core` cluster; beta tenant schemas are named
|
|
129
|
+
> `Client`, prod tenant schemas `Client_<Tenant>`).
|
|
130
|
+
|
|
131
|
+
Hosting: Elastic Beanstalk (Agilant standard; use `/create-elastic-beanstalk` for new envs).
|
|
132
|
+
Served as a static SPA build; no custom `.ebextensions` in-repo.
|
|
133
|
+
|
|
134
|
+
## Where to start (read in order)
|
|
135
|
+
|
|
136
|
+
1. `src/main.tsx` — render tree, React Query + Auth providers.
|
|
137
|
+
2. `src/routes.tsx` — routes.
|
|
138
|
+
3. `src/App.tsx` — layout.
|
|
139
|
+
4. `src/api/axiosInstance.ts` — hostname-aware base URL + auth tokens.
|
|
140
|
+
5. `src/api/apiFunctions.ts` + `src/utils/queryHelpers.ts` — the api2 query language.
|
|
141
|
+
6. `src/contexts/AuthContext.tsx` — session state.
|
|
142
|
+
7. `src/pages/Permissions/view/PermissionsPage.tsx` — main surface (+ the ACL feature doc).
|
|
143
|
+
8. `tailwind.config.js` — design tokens.
|
|
144
|
+
|
|
145
|
+
## Open questions (unverified — confirm before relying on)
|
|
146
|
+
|
|
147
|
+
- axiosInstance has a deliberate ~1ms pre-request delay whose purpose is undocumented —
|
|
148
|
+
understand before removing.
|
|
149
|
+
- Possible query-param name inconsistency in the Permissions Records detail
|
|
150
|
+
(`recordUuid` written vs `recordId` read) — verify when next touching that path.
|
|
151
|
+
- `src/dummyData/*.json` mocks exist; some surfaces (e.g. the Records-tab permissions list)
|
|
152
|
+
may still render mock data — confirm a component hits the live API before debugging data.
|
|
153
|
+
|
|
154
|
+
## Related docs
|
|
155
|
+
|
|
156
|
+
- [ACL Record Permissions — Rendering Semantics](features/acl-permissions-rendering.md)
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: ACL Record Permissions — Rendering Semantics (Permissions Page)
|
|
3
|
+
framework: "2.0"
|
|
4
|
+
repo: toga2-hub
|
|
5
|
+
project: TOGA Hub
|
|
6
|
+
client: shared
|
|
7
|
+
type: feature
|
|
8
|
+
status: active
|
|
9
|
+
updated: 2026-06-10
|
|
10
|
+
owners: [tcox]
|
|
11
|
+
files:
|
|
12
|
+
- toga2-hub/src/pages/Permissions/api/permissionsApi.ts
|
|
13
|
+
- toga2-hub/src/pages/Permissions/utils/permissionsUtils.ts
|
|
14
|
+
- toga2-hub/src/pages/Permissions/viewModel/useRolesViewModel.ts
|
|
15
|
+
- toga2-hub/src/hooks/useAclPermissionsByRoleQuery.ts
|
|
16
|
+
- toga2-hub/src/pages/Permissions/view/RolesView/RoleDetailView/LogicGroupView.tsx
|
|
17
|
+
- toga2-hub/src/pages/Permissions/view/RecordsView/RecordDetailView/RecordDetailView.tsx
|
|
18
|
+
- toga2-hub/src/hooks/useQueryData.ts
|
|
19
|
+
related: []
|
|
20
|
+
---
|
|
21
|
+
|
|
22
|
+
## Summary
|
|
23
|
+
|
|
24
|
+
How the TOGA Hub Permissions page must interpret the api2 endpoint
|
|
25
|
+
`GET /v2/acl-record-permissions/getAclRecordPermissionsByRecordAndRole` and turn its
|
|
26
|
+
response into the green-check / red-X permissions grid. Semantics confirmed in a design
|
|
27
|
+
review with jcardinal (Naperville, 2026-06-09); an earlier implementation over-thought
|
|
28
|
+
the rules and had to be corrected back to the simple reading below.
|
|
29
|
+
|
|
30
|
+
## Endpoint usage
|
|
31
|
+
|
|
32
|
+
- `?recordId=N` (no roleId) → returns logic groups for **every role** on that record.
|
|
33
|
+
This feeds the **Roles tab record detail** (pick a role, then click a record): every
|
|
34
|
+
element of the response renders as its own table, labeled by role — the role
|
|
35
|
+
selection does NOT filter the tables ("that's useful for this rendering of this
|
|
36
|
+
page" — the unscoped call, per the 2026-06-09 review).
|
|
37
|
+
- The **Records tab** record detail does NOT render these tables — its permissions tab
|
|
38
|
+
is an app-sectioned permissions list (dummy data as of 2026-06-10, real data TBD).
|
|
39
|
+
- `?recordId=N&roleId=M` → returns only that role's logic groups. Supported by the API
|
|
40
|
+
for role-scoped use cases, but not currently used by the Hub UI.
|
|
41
|
+
- `recordId`/`roleId` are the numeric backend PKs, not UUIDs.
|
|
42
|
+
- **Gotcha:** `Roles.id` is not field-ACL-readable for hub users — requesting it on
|
|
43
|
+
`GET /roles` returns `EZ-2` 403 (`fields: ["id"]`, route `roles`, `aclDatabase:
|
|
44
|
+
CLIENT`), and a single prohibited field fails the whole fetch. Don't request `id` on
|
|
45
|
+
`/roles`. Suspect the same for `Apps.id` (needed for the appId → column mapping on
|
|
46
|
+
`/apps`) — if app-specific permissions render all-red, check the `/apps` response
|
|
47
|
+
first. Backend grant requests pending with jcardinal.
|
|
48
|
+
|
|
49
|
+
## Response shape
|
|
50
|
+
|
|
51
|
+
`data.aclRecordPermissions.getAclRecordPermissionsByRecordAndRole` is an array of
|
|
52
|
+
`{ logicGroup, roles[] }`:
|
|
53
|
+
|
|
54
|
+
- `logicGroup` — the condition tree shown above the grid. `logicGroupExpressions` items
|
|
55
|
+
are `type: "EXPRESSION"` (leaf, with `recordExpression.description` as display label)
|
|
56
|
+
or `type: "GROUP"` (nested group with its own `operator` + expressions). A logic group
|
|
57
|
+
with `uuid: null` and no expressions means "no conditions" (e.g. the Public role).
|
|
58
|
+
- `roles[].permissions[]` — flat list of permission entries:
|
|
59
|
+
`{ uuid, appId, indirectRecordId, recordId, allowCreate, allowRead, allowUpdate, allowDelete }`.
|
|
60
|
+
|
|
61
|
+
## Rendering rules (the part that was corrected)
|
|
62
|
+
|
|
63
|
+
The grid has one column per app (Supply, Hub, View, …) and one row per CRUD action.
|
|
64
|
+
Resolve a role's `permissions[]` array like this:
|
|
65
|
+
|
|
66
|
+
1. **`appId: null` = ALL apps.** An entry with `appId: null` and all CRUD true means
|
|
67
|
+
green checks across the board, every column.
|
|
68
|
+
2. **`appId: <id>` = that app's column only.** App id → column via `Apps.slug`
|
|
69
|
+
(`toga-supply` → supply column; app id 5 = Supply).
|
|
70
|
+
3. **No `appId: null` entry present** → columns without an app-specific entry are all
|
|
71
|
+
false → **red X's**. E.g. a role whose only entry is `appId: 5` all-true renders
|
|
72
|
+
Supply fully green and every other column fully red.
|
|
73
|
+
4. **Effective cell value = OR across all matching entries.** Redundant entries are
|
|
74
|
+
normal and harmless (e.g. `appId: null` all-true *plus* `appId: 5` all-true — the
|
|
75
|
+
second adds nothing). A role can also carry multiple `appId: null` entries; OR them
|
|
76
|
+
all in.
|
|
77
|
+
5. Don't over-derive: the backend data is not normalized/human-curated yet (cleanup is
|
|
78
|
+
planned once Hub can visualize it). Render what the entries say, nothing more.
|
|
79
|
+
6. **Every logic group is its own table under the record — never filter.** On the
|
|
80
|
+
Roles tab record detail, each element of the unscoped response renders as its own
|
|
81
|
+
conditions box + CRUD table, labeled with its role name. "Only one table shows" was
|
|
82
|
+
a recurring bug caused by client-side role filtering of the response — the original
|
|
83
|
+
build (multiple tables per record) was correct. Do not reintroduce a roleUuid filter
|
|
84
|
+
on the logic-group array.
|
|
85
|
+
|
|
86
|
+
## indirectRecordId
|
|
87
|
+
|
|
88
|
+
Entries with `indirectRecordId` set are indirect grants managed by the backend
|
|
89
|
+
(observed: Compass Base role carries a direct `appId: null` entry and a second
|
|
90
|
+
`appId: null, indirectRecordId: 2` entry). The Hub UI ORs them into the rendered grid
|
|
91
|
+
but must **never POST/PUT/DELETE them** from the permissions save flow — only direct
|
|
92
|
+
entries (`indirectRecordId: null`) are editable. Exact backend semantics still to be
|
|
93
|
+
confirmed.
|
|
94
|
+
|
|
95
|
+
## Save/cleanup conventions (Hub-side)
|
|
96
|
+
|
|
97
|
+
- All columns fully checked → collapse to a single `appId: null` entry and delete
|
|
98
|
+
redundant app-specific entries.
|
|
99
|
+
- Mixed state → delete any existing `appId: null` entry and write per-app entries.
|
|
100
|
+
- All unchecked → delete every direct entry for that role/record.
|
|
101
|
+
|
|
102
|
+
## Validating against the database
|
|
103
|
+
|
|
104
|
+
Permissions live in Core DB table `AclRecordPermissions` (per-role CRUD), conditions in
|
|
105
|
+
the logic-group/record-expression tables, field-level in `AclFieldPermissions` (see api2
|
|
106
|
+
architecture doc). The API response is the source of truth for rendering; SQL is the
|
|
107
|
+
fallback for verifying unexpected data.
|
|
108
|
+
|
|
109
|
+
## Change history
|
|
110
|
+
- 2026-06-15 — Added to the team knowledge base alongside the toga2-hub architecture doc. (tcox)
|
|
111
|
+
- 2026-06-09 — Rendering/save semantics confirmed in design review (appId:null collapse, mixed-state per-app writes, Core DB validation path). (jcardinal)
|
package/knowledge/INDEX.md
CHANGED
|
@@ -18,6 +18,7 @@ _Auto-generated by `knowledge.js index`. Do not hand-edit._
|
|
|
18
18
|
- **toga2-supply** (TOGa Supply) — 2 doc(s) → [2.0/apps/toga2-supply/INDEX.md](2.0/apps/toga2-supply/INDEX.md)
|
|
19
19
|
- **saml** (SAML SSO Gateway) — 2 doc(s) → [2.0/apps/saml/INDEX.md](2.0/apps/saml/INDEX.md)
|
|
20
20
|
- **toga2-view** (TOGa View Frontend) — 0 doc(s) → [2.0/apps/toga2-view/INDEX.md](2.0/apps/toga2-view/INDEX.md)
|
|
21
|
+
- **toga2-hub** (TOGA Hub) — 2 doc(s) → [2.0/apps/toga2-hub/INDEX.md](2.0/apps/toga2-hub/INDEX.md)
|
|
21
22
|
|
|
22
23
|
## Clients
|
|
23
24
|
|
package/knowledge/registry.json
CHANGED
|
@@ -9,5 +9,7 @@
|
|
|
9
9
|
{ "repo": "saml", "project": "SAML SSO Gateway", "framework": "2.0", "role": "app", "dependsOn": [] },
|
|
10
10
|
{ "repo": "toga2-view", "project": "TOGa View Frontend", "framework": "2.0", "role": "app", "dependsOn": ["api2"] },
|
|
11
11
|
{ "repo": "togadesk", "project": "TOGa Desk", "framework": "1.0", "role": "app", "dependsOn": [] },
|
|
12
|
-
{ "repo": "togaview", "project": "TOGa View", "framework": "1.0", "role": "app", "dependsOn": [] }
|
|
12
|
+
{ "repo": "togaview", "project": "TOGa View", "framework": "1.0", "role": "app", "dependsOn": [] },
|
|
13
|
+
{ "repo": "toga2-hub", "project": "TOGA Hub", "framework": "2.0", "role": "app", "dependsOn": ["api2"] }
|
|
14
|
+
|
|
13
15
|
]
|
package/package.json
CHANGED