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.
@@ -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)
@@ -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)_ — 5 doc(s) → [2.0/apps/_underscore/INDEX.md](2.0/apps/_underscore/INDEX.md)
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
 
@@ -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.77",
3
+ "version": "1.0.79",
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",
@@ -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
- That text is the developer's description of today's work it is **not** permission to
12
- shortcut the flow.
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 before Steps 0–6 complete.
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