toga-ai 1.0.77 → 1.0.79
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/2.0/apps/_underscore/INDEX.md +1 -0
- package/knowledge/2.0/apps/_underscore/features/email-template-sending.md +88 -0
- 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 +2 -1
- package/knowledge/registry.json +3 -1
- package/package.json +1 -1
- package/skills/kickoff/SKILL.md +24 -4
|
@@ -4,5 +4,6 @@
|
|
|
4
4
|
|-----|---------|-------|
|
|
5
5
|
| [_underscore Framework Architecture](architecture.md) | `_underscore` is the shared PHP backend framework for **all 2.0 applications**. | _underscore/_underscore.php, _underscore/Loader.php, _underscore/Framework.php, _underscore/Model.php, _underscore/Database.php, _underscore/Query.php, _underscore/Route.php, _underscore/Component.php |
|
|
6
6
|
| [Carrier Shipping Labels (UPS/FedEx) & NetSuite Item Fulfillment](features/carrier-shipping-labels.md) | Backend mechanics behind TOGa Supply's Fulfill & Ship: buying a carrier label (UPS/FedEx), persisting it, and creating the NetSuite Item Fulfillment with tracki | _underscore/Model/Client/ItemFulfillment.php, _underscore/Component/Library/Carriers/Ups/Ups.php, _underscore/Trait/Netsuite/ItemFulfillment.php, _underscore/Trait/Netsuite/SalesOrder.php, _underscore/Component/Library/NetSuite/NetSuite.php, _underscore/Model/Client/TrackingNumber.php, _underscore/Model/Client/ShippingMethod.php, _underscore/Model.php, _underscore/Cloud.php |
|
|
7
|
+
| [Client Email Template Sending](features/email-template-sending.md) | `_Model_Client_EmailTemplate` sends a stored, client-defined email template by UUID. | _underscore/Model/Client/EmailTemplate.php, _underscore/Model/Client/EmailTemplateOutgoingEmailAddress.php, _underscore/Email.php |
|
|
7
8
|
| [Recursive Item Fulfillments (upstream mirroring)](features/recursive-item-fulfillments.md) | In a multi-tier supply chain a sales order (SO) spawns a purchase order (PO) that becomes another SO downstream, and so on. | _underscore/Model/Client/ItemFulfillment.php, _underscore/Model/Client/ItemFulfillmentItem.php, _underscore/Model/Client/ItemFulfillmentItemUnit.php, _underscore/Model/Client/ItemFulfillmentPackage.php, _underscore/Model/Compass/AdvanceShippingNotice.php, dbchanges2/Core/2026-02-13 - 75601 - RecursiveItemFulfillmentCreation.sql, dbchanges2/Core/2026-06-04 - RecursiveItemFulfillmentPut.sql |
|
|
8
9
|
| [Tracking-Number Bridge Migration (ASN / Item Fulfillment / Item Receipt)](features/tracking-number-bridges.md) | Shipment tracking numbers used to live as **scalar FK columns** (`trackingNumberId`, `returnTrackingNumberId`) directly on the lowest-level "unit"/"item" tables | _underscore/Model/Client/AdvanceShippingNoticeItemUnit.php, _underscore/Model/Client/AdvanceShippingNoticeItemUnits/TrackingNumber.php, _underscore/Model/Client/ItemFulfillmentItemUnits/TrackingNumber.php, _underscore/Model/Client/ItemFulfillment.php, _underscore/Model/Prudential/AdvanceShippingNotice.php, _underscore/Model/Compass/AdvanceShippingNotice.php, _underscore/Trait/Netsuite/ItemFulfillment.php, api2/Component/Api/Cxml/Cxml.php, dbchanges2/Client/2026-06-10 - TrackingNumberBridges.sql, dbchanges2/Core/2026-06-10 - TrackingNumberBridges.sql |
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Client Email Template Sending
|
|
3
|
+
framework: "2.0"
|
|
4
|
+
repo: _underscore
|
|
5
|
+
project: _Underscore
|
|
6
|
+
client: shared
|
|
7
|
+
type: feature
|
|
8
|
+
status: active
|
|
9
|
+
updated: 2026-06-15
|
|
10
|
+
owners: ["jcardinal"]
|
|
11
|
+
files:
|
|
12
|
+
- _underscore/Model/Client/EmailTemplate.php
|
|
13
|
+
- _underscore/Model/Client/EmailTemplateOutgoingEmailAddress.php
|
|
14
|
+
- _underscore/Email.php
|
|
15
|
+
related: []
|
|
16
|
+
---
|
|
17
|
+
|
|
18
|
+
## Summary
|
|
19
|
+
|
|
20
|
+
`_Model_Client_EmailTemplate` sends a stored, client-defined email template by UUID. It
|
|
21
|
+
loads the template from the client DB (`EmailTemplates` table), merges any template-stored
|
|
22
|
+
recipient addresses, substitutes `{variable}` placeholders in the subject and body, and
|
|
23
|
+
sends via `_Email`. The only client-specific input the send actually needs is the
|
|
24
|
+
**client identifier** (used to scope the outgoing mail via `_Email::setClientIdentifier()`).
|
|
25
|
+
|
|
26
|
+
## Key files / entry points
|
|
27
|
+
|
|
28
|
+
- `_underscore/Model/Client/EmailTemplate.php` — the model. Three relevant methods:
|
|
29
|
+
- **`sendEmail(&$api, $uuid, $to, $cc, $bcc, ...$args): bool`** — the scripted-API entry
|
|
30
|
+
point. First param is `&$api` per the Record Script contract (see backend-php standard,
|
|
31
|
+
*Record Scripts*). The only thing it uses from `$api` is `$api->client->clientIdentifier`.
|
|
32
|
+
- **`send(string $clientIdentifier, $uuid, $to, $cc, $bcc, ...$args): bool`** — the
|
|
33
|
+
non-API entry point. Caller passes the client identifier directly; no `$api` object.
|
|
34
|
+
- **`dispatch(string $clientIdentifier, ...): bool`** (private) — the shared body both
|
|
35
|
+
entry points call. Holds all the real logic.
|
|
36
|
+
- `_Model_Client_EmailTemplateOutgoingEmailAddress` — per-template stored TO/CC/BCC
|
|
37
|
+
addresses (`toCcBcc` enum), merged into the caller-supplied recipients.
|
|
38
|
+
- `_underscore/Email.php` — `_Email` requires a non-empty `clientIdentifier` (throws
|
|
39
|
+
`clientIdentifier is required` otherwise) and uses it as the CloudWatch log stream name.
|
|
40
|
+
|
|
41
|
+
## How it works
|
|
42
|
+
|
|
43
|
+
1. Load the template by `uuid`; return `false` immediately if `!isActive`.
|
|
44
|
+
2. Search `EmailTemplateOutgoingEmailAddress` for the template and append each stored
|
|
45
|
+
address to `$to` / `$cc` / `$bcc` by its `toCcBcc` value.
|
|
46
|
+
3. Build `_Email`, set the client identifier, add recipients, set From from the template's
|
|
47
|
+
`sendFromEmailAddress` / `sendFromName`.
|
|
48
|
+
4. Replace `{key}` placeholders in subject and body from the `$args` variadic map
|
|
49
|
+
(`replaceTemplateVariables`), then `send()`.
|
|
50
|
+
|
|
51
|
+
Both `sendEmail()` and `send()` are thin wrappers that forward to `dispatch()`, so the API
|
|
52
|
+
and non-API paths run identical code — no behavioral drift between them.
|
|
53
|
+
|
|
54
|
+
## Data model
|
|
55
|
+
|
|
56
|
+
- `EmailTemplates` (client DB): `uuid`, `isActive`, `sendFromEmailAddress`, `sendFromName`,
|
|
57
|
+
`priority`, `subject`, `body`.
|
|
58
|
+
- `EmailTemplateOutgoingEmailAddress` (client DB): `emailTemplateId`, `emailAddress`,
|
|
59
|
+
`toCcBcc` (`TO`/`CC`/`BCC`).
|
|
60
|
+
|
|
61
|
+
## Client variations
|
|
62
|
+
|
|
63
|
+
The model is shared; client-specific senders pass their own identifier. Workers use a class
|
|
64
|
+
constant (e.g. `self::CLIENT_IDENTIFIER`); model/interceptor code that already has an `$api`
|
|
65
|
+
can keep using `sendEmail($api, ...)`.
|
|
66
|
+
|
|
67
|
+
## Gotchas / known issues
|
|
68
|
+
|
|
69
|
+
- **Use `send()` from any non-API context (workers, cron, internal code).** Before
|
|
70
|
+
2026-06-15 the only entry point was `sendEmail(&$api, ...)`, so callers with no API
|
|
71
|
+
context faked one: `$api = (object)['client' => (object)['clientIdentifier' => …]]`.
|
|
72
|
+
That hack is obsolete — pass the identifier to `send()` instead.
|
|
73
|
+
- **`sendEmail()`'s signature is load-bearing for scripted APIs** — the Record Script engine
|
|
74
|
+
(`api2/Component/Api/V2/V2.php`, ~line 3594) calls the method with `api` as a named
|
|
75
|
+
argument, so the first param must stay `&$api`. Do not "clean it up" by removing it.
|
|
76
|
+
- `_Email::send()` throws if the client identifier is empty — `send('')` will fail at send
|
|
77
|
+
time, not at call time.
|
|
78
|
+
|
|
79
|
+
## Change history
|
|
80
|
+
|
|
81
|
+
- 2026-06-15 — Added non-API `send(clientIdentifier, …)` entry point + private `dispatch()`;
|
|
82
|
+
`sendEmail(&$api, …)` kept unchanged as a wrapper for backward compatibility. Migrated the
|
|
83
|
+
`worker2` Compass Report and AI-BDR NetSuite callers off the fake-`$api` hack. (jcardinal)
|
|
84
|
+
|
|
85
|
+
## Related docs
|
|
86
|
+
|
|
87
|
+
- `2.0/standards/backend-php.md` — *Record Scripts* (the `&$api` contract).
|
|
88
|
+
- `2.0/apps/api2/architecture.md` — scripted-API dispatch in `V2.php`.
|
|
@@ -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
|
@@ -11,13 +11,14 @@ _Auto-generated by `knowledge.js index`. Do not hand-edit._
|
|
|
11
11
|
|
|
12
12
|
## 2.0 framework
|
|
13
13
|
|
|
14
|
-
- **_underscore** (_Underscore) _(framework core)_ —
|
|
14
|
+
- **_underscore** (_Underscore) _(framework core)_ — 6 doc(s) → [2.0/apps/_underscore/INDEX.md](2.0/apps/_underscore/INDEX.md)
|
|
15
15
|
- **worker2** (Worker) — 6 doc(s) → [2.0/apps/worker2/INDEX.md](2.0/apps/worker2/INDEX.md)
|
|
16
16
|
- **api2** (API) — 1 doc(s) → [2.0/apps/api2/INDEX.md](2.0/apps/api2/INDEX.md)
|
|
17
17
|
- **dbchanges2** (Database Changes) _(framework core)_ — 1 doc(s) → [2.0/apps/dbchanges2/INDEX.md](2.0/apps/dbchanges2/INDEX.md)
|
|
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
package/skills/kickoff/SKILL.md
CHANGED
|
@@ -5,16 +5,36 @@ description: Start-of-session context loader for TOGA Technology projects. Run t
|
|
|
5
5
|
|
|
6
6
|
# Kickoff — prime a coding session from the team knowledge base
|
|
7
7
|
|
|
8
|
+
> ## 🛑 STOP — THIS IS A BLOCKING GATE. READ BEFORE DOING ANYTHING ELSE.
|
|
9
|
+
>
|
|
10
|
+
> When `/kickoff` is invoked you **MUST** complete Steps 0–6 of this skill **before**
|
|
11
|
+
> any other tool call — no `Read`, `Grep`, `Glob`, `Edit`, `Write`, `Bash`, no agent
|
|
12
|
+
> spawn, no investigation, no answering the developer's question. Priming comes first,
|
|
13
|
+
> **always**.
|
|
14
|
+
>
|
|
15
|
+
> **Trailing text after `/kickoff` is NEVER a reason to skip priming.** A long, detailed
|
|
16
|
+
> paragraph describing a specific task (file paths, line numbers, a design question) is
|
|
17
|
+
> *still just the Step 2 task description* — it is input to the interview, **not**
|
|
18
|
+
> permission to start working. The more detailed and actionable the request looks, the
|
|
19
|
+
> more tempting it is to dive in — **resist that.** If you find yourself about to open a
|
|
20
|
+
> file the developer named before you have loaded the knowledge base, you are violating
|
|
21
|
+
> this gate. Stop and run Steps 0–6 first.
|
|
22
|
+
>
|
|
23
|
+
> Only after Step 5's "primed and ready" summary (and Step 6's plan, for non-trivial work)
|
|
24
|
+
> may you touch the task itself.
|
|
25
|
+
|
|
8
26
|
## Arguments — text passed after `/kickoff` never skips any step
|
|
9
27
|
|
|
10
|
-
`/kickoff` may be invoked with trailing text (e.g. `/kickoff worker2 backend fix for Compass`)
|
|
11
|
-
|
|
12
|
-
|
|
28
|
+
`/kickoff` may be invoked with trailing text (e.g. `/kickoff worker2 backend fix for Compass`),
|
|
29
|
+
including a long, specific paragraph naming exact files, line numbers, and a concrete
|
|
30
|
+
change. That text is the developer's description of today's work — it is **not** permission
|
|
31
|
+
to shortcut the flow, no matter how actionable it looks.
|
|
13
32
|
|
|
14
33
|
- **Step 0 (auto-update check) ALWAYS runs first**, with or without arguments.
|
|
15
34
|
- Use the argument text to **pre-fill answers** to the Step 2 interview (framework, layer,
|
|
16
35
|
repo, client, task). Only ask about whatever is still missing or ambiguous.
|
|
17
|
-
- Never treat the argument as an instruction to start coding
|
|
36
|
+
- Never treat the argument as an instruction to start coding — or even to start *reading
|
|
37
|
+
the named files* — before Steps 0–6 complete. Investigation IS work; it waits for priming.
|
|
18
38
|
|
|
19
39
|
## Step 0 — Auto-update check (runs before anything else, even with arguments)
|
|
20
40
|
|