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.
@@ -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)
@@ -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
 
@@ -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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "toga-ai",
3
- "version": "1.0.76",
3
+ "version": "1.0.78",
4
4
  "description": "TOGA Technology Team Claude Knowledge System — shared AI coding harness with skills, knowledge base CLI, and project installer for Claude Code.",
5
5
  "keywords": [
6
6
  "claude",