toga-ai 1.0.909 → 1.0.911
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/library/features/netsuite-suiteql-api-reference.md +5 -3
- package/knowledge/2.0/apps/dbchanges2/features/rerunnable-additive-inserts.md +11 -1
- package/knowledge/2.0/apps/toga-blox/features/table.md +10 -3
- package/knowledge/2.0/apps/toga-blox/workflows/dynamic-publish-pipeline.md +7 -1
- package/knowledge/2.0/standards/secrets-and-build-credentials.md +73 -0
- package/knowledge/INDEX.md +1 -1
- package/knowledge/clients/adyen/INDEX.md +1 -1
- package/knowledge/clients/adyen/profile.md +36 -2
- package/knowledge/clients/quad/INDEX.md +1 -0
- package/knowledge/clients/quad/profile.md +5 -1
- package/knowledge/clients/quad/workflows/granting-all-personas-and-global-admin.md +86 -0
- package/knowledge/clients/quad/workflows/granting-internal-admin-navigation.md +5 -0
- package/package.json +1 -1
|
@@ -6,8 +6,8 @@ project: Library
|
|
|
6
6
|
client: shared
|
|
7
7
|
type: feature
|
|
8
8
|
status: active
|
|
9
|
-
updated: 2026-09-
|
|
10
|
-
owners: ["dfranks", "jcardinal", "kyalamarthi", "ajean"]
|
|
9
|
+
updated: 2026-09-28
|
|
10
|
+
owners: ["dfranks", "jcardinal", "kyalamarthi", "ajean", "rgirish"]
|
|
11
11
|
files:
|
|
12
12
|
- library/app/api/netsuite/rest.php
|
|
13
13
|
- library/ssl/netsuite_ec_key.pem
|
|
@@ -110,7 +110,9 @@ Status codes (`transaction.status` letter):
|
|
|
110
110
|
|
|
111
111
|
Header columns present (✓ confirmed in SuiteQL): `id`, `tranid`, `trandate`, `lastmodifieddate`, `type`, `status`, `entity`, `employee`, `leadsource`, `terms`, `memo`, `otherrefnum`, `foreigntotal`/`total` (both return the same value), `foreignamountunpaid` (AR balance — invoices only; NULL for SO/credit memo/cash sale), `duedate`, `shipmethod`, `opportunity`, `shippingaddress` (numeric id → join `transactionShippingAddress`), and custom fields (`CUSTBODY_END_CUSTOMER`, `CUSTBODY_STOCKING_ORDER`, …).
|
|
112
112
|
|
|
113
|
-
Header fields NOT in SuiteQL (✗ — require a per-id REST GET): `shippingCost`, `taxTotal`, `subTotal`, `createdFrom` (also derivable from `previousTransactionLineLink`, but non-deterministically — see shim doc),
|
|
113
|
+
Header fields NOT in SuiteQL (✗ — require a per-id REST GET): `shippingCost`, `taxTotal`, `subTotal`, `createdFrom` (also derivable from `previousTransactionLineLink`, but non-deterministically — see shim doc), and the **structured** `shippingAddress` object (SuiteQL gives only the id).
|
|
114
|
+
|
|
115
|
+
Tracking numbers in SuiteQL (verified 2026-09-28): `transaction.trackingnumberlist` (on the IF) / `linkedtrackingnumberlist` (on the SO) return **`trackingnumber` record ids**, not the numbers — resolve with `SELECT id, trackingnumber FROM trackingnumber WHERE id IN (...)`. `trackingnumbermap` returned nothing. The REST per-id GET below also has them.
|
|
114
116
|
|
|
115
117
|
### `transactionline` table
|
|
116
118
|
|
|
@@ -6,7 +6,7 @@ project: Database Changes
|
|
|
6
6
|
client: shared
|
|
7
7
|
type: feature
|
|
8
8
|
status: active
|
|
9
|
-
updated: 2026-09-
|
|
9
|
+
updated: 2026-09-28
|
|
10
10
|
owners: [bala, kyalamarthi, apeterson, ajean, jcardinal, rgirish]
|
|
11
11
|
files:
|
|
12
12
|
- dbchanges2/Client_Compass/2026-08-26a - CompassCreativeStudioPersona.sql
|
|
@@ -182,6 +182,8 @@ the item-fulfillment bridges, which sit side by side and do **not** agree:
|
|
|
182
182
|
| `ItemFulfillmentItems_TrackingNumbers` | yes — `(itemFulfillmentItemId, trackingNumberId)` |
|
|
183
183
|
| `ItemFulfillmentItemUnits_TrackingNumbers` | yes — `(itemFulfillmentItemUnitId, trackingNumberId)` |
|
|
184
184
|
| `ItemFulfillmentItemUnits` | **no** — and it already holds **254** duplicate `(itemFulfillmentItemId, unitId)` rows as a result |
|
|
185
|
+
| `Users_Personas` | yes — `(userId, personaId)` (`userId_personaId_UNIQUE`, `Client/2026-09-22-BLANK_CLIENT_DATABASE.sql:11410`) |
|
|
186
|
+
| `Users_Roles` | yes — `(userId, roleId)` |
|
|
185
187
|
|
|
186
188
|
The last row is the whole point: the table with no unique key had already accumulated duplicates
|
|
187
189
|
that nobody noticed, because nothing ever errored.
|
|
@@ -198,6 +200,13 @@ WHERE
|
|
|
198
200
|
ur.id IS NULL;
|
|
199
201
|
```
|
|
200
202
|
|
|
203
|
+
**Not every table has a `dtCreated` to set.** The "always write `dtCreated = NOW()`" rule below
|
|
204
|
+
applies only where the column exists. The user bridges do **not** have one —
|
|
205
|
+
`_Model_Client_Users_Persona` (`_underscore/Model/Client/Users/Persona.php`) declares exactly
|
|
206
|
+
`(id, uuid, userId, personaId)`, and `Users_Roles` is the same shape — so a hand-written INSERT names
|
|
207
|
+
only the uuid and the two ids. Read the model (or the blank-client schema) before adding a timestamp
|
|
208
|
+
column that does not exist.
|
|
209
|
+
|
|
201
210
|
**A guarded insert that inserts zero rows is a success, not a wasted file** — see
|
|
202
211
|
[Client-DB grant migrations are re-runnable NO-OPs](../../_underscore/features/acl-permission-chain.md).
|
|
203
212
|
|
|
@@ -505,6 +514,7 @@ an INSERT that names only `uuid`/`tableViewId`/`recordFieldId`/`isVisible`/`inde
|
|
|
505
514
|
[FIELD_SQL calculated fields](../../_underscore/features/calculated-sql-fields.md).
|
|
506
515
|
|
|
507
516
|
## Change history
|
|
517
|
+
- 2026-09-28 — `Users_Personas` / `Users_Roles` added to the composite-unique table (both unique on the id pair, so a guarded re-run is a clean no-op), plus the counterpoint to the dtCreated rule: these user bridges have no timestamp column at all. (apeterson)
|
|
508
518
|
- 2026-09-23 — Added the NULL-unsafe flag-guard trap (`col = 0` matches no NULL row; use `COALESCE(col, 0) = 0`), found when a bundles TableViewFields migration updated 0 rows; flagged the 2026-08-26 `EnableTableViewFieldFilters` files as carrying the same hole. (apeterson)
|
|
509
519
|
- 2026-09-22 — Added multi-tier chain backfill rules from the Compass tracking/serial backfills: re-run the same file until the verify count is 0 (each pass moves data up one tier; both files needed 3 passes); `GROUP BY` the target key pair instead of `SELECT DISTINCT` when many downstream rows roll up to one upstream row (bundles tripped a bridge UNIQUE 6-to-1); measure the safe/ambiguous/no-match split before writing bulk DML and skip the ambiguous rows (4,900 broken links = 1,982 safe + 0 ambiguous + 2,918 no-match). Sharpened the unique-key rule with measured composite keys across the four item-fulfillment tables — `ItemFulfillmentItemUnits` has none and already carries 254 duplicates. (rgirish)
|
|
510
520
|
- 2026-09-21 — Added three lessons from the platform-wide `Users.contactId` NOT NULL backfill: a guarded
|
|
@@ -6,7 +6,7 @@ project: TOGa Blox
|
|
|
6
6
|
client: shared
|
|
7
7
|
type: feature
|
|
8
8
|
status: active
|
|
9
|
-
updated: 2026-09-
|
|
9
|
+
updated: 2026-09-28
|
|
10
10
|
owners: [apeterson, tcox, bala]
|
|
11
11
|
files:
|
|
12
12
|
- toga-blox/src/components/Table/hooks/useFetchTablePageMeta.ts
|
|
@@ -85,10 +85,10 @@ Per-column header controls, each a popover trigger with `data-active` / `data-op
|
|
|
85
85
|
|
|
86
86
|
- **`HeaderFilterSearch`** — text filter, **multi-chip with per-chip mode**. Each confirmed search becomes its own `TextFilterTag = { value, mode }` (mode ∈ Includes / Starts with / Ends with / Exactly / Excludes). The mode dropdown sets the mode for the **next** chip added; existing chips keep the mode they were created with. All chips on a column are combined with **AND**. `onFilterChange` emits the full tags array (`{ tags }`); the component rehydrates every chip from props and renders each as "Mode: value". `isActive = tags.length > 0`. The mode dropdown is portalled to `document.body` with fixed positioning recomputed on scroll. The search icon (solid when active, regular otherwise) is hidden while the input is focused; both the trigger button and the icon span carry `data-active`. The clear button must not widen the input row (fixed-width menu). (Previously a column allowed only one term / one mode — a second search replaced the first and switching mode re-applied it to everything.)
|
|
87
87
|
- **`HeaderFilterRange`** — numeric, modes exactly/moreThan/lessThan + a range toggle (min/max); emits `{ key, value }[]` (`eq`/`min`/`max`/`between`). Reads its persisted operator/values back from a `filterParams` prop (not `filterValue`, which collapses to the literal `"active"` when a range filter is set); `PrimaryTableHeaderCell` must pass `filterParams` through.
|
|
88
|
-
- **`HeaderFilterMultiselect`** — checkbox list with "Select All"; emits comma-joined slugs.
|
|
88
|
+
- **`HeaderFilterMultiselect`** — checkbox list with "Select All"; emits comma-joined slugs. **Its "Clear" must call the `onClear` prop, not just reset local state** — see the clear-contract gotcha below.
|
|
89
89
|
- **`HeaderFilterDate`** — calendar picker, modes exactly/before/after + range; converts `MM/DD/YYYY` ↔ URL `YYYY-MM-DD`; calendar + mode dropdown both portalled.
|
|
90
90
|
- **`HeaderFilterButton`** — generic trigger: shows a numbered badge when active with a `filterIndex`, otherwise the search icon (`data-active`).
|
|
91
|
-
- **`SortIcon`** — asc/desc/reset menu with a multi-sort index badge; button visibility keys off current sort direction.
|
|
91
|
+
- **`SortIcon`** — asc/desc/reset menu with a multi-sort index badge; button visibility keys off current sort direction. The badge **points down for `desc`** via a `.sortDropdownBadgeDesc` modifier that rotates the whole badge 180deg, with a nested rule counter-rotating `.sortDropdownBadgeIndex` so the sort-position digit still reads upright. **Rotate, never swap the icon** — FontAwesome Pro has no down-pointing `triangle`, and `caret`/`sort` have a different silhouette, so a swap changes the designed shape. The offset is held in one CSS variable both rules read (`--sortDropdownBadgeIndex-offset`) so retuning the base offset cannot drift the rotated copy. These rotations are deliberately **not** host tokens: a 180deg flip is geometry, not brand — same treatment as the un-tokenized `translateX(-50%)` centering on `.sortDropdownMenu` (a CSS comment records this so it is not "fixed" later). (Before 2026-09-28 the badge always rendered the solid `triangle` and ignored `isSorted`, so asc and desc looked identical.)
|
|
92
92
|
|
|
93
93
|
### Hooks (`hooks/`)
|
|
94
94
|
|
|
@@ -118,6 +118,10 @@ This exists because an unscoped `GET /{recordRoute}` returns *every* row of a lo
|
|
|
118
118
|
|
|
119
119
|
## Gotchas
|
|
120
120
|
|
|
121
|
+
- **A filter popover's "Clear" must call `onClear()` — resetting local state only leaves the table filtered.** `HeaderFilterMultiselect.handleClear` did just `setSelectedOptions([])`, so the URL param (owned by the consuming app) and the applied filter survived: the popover showed nothing selected while the table stayed filtered. Fixed 2026-09-28 — it now calls `onClear()` and closes, **guarded by `if (filterValue)`**. The guard matters: `onClear` routes through `PrimaryTableHeaderCell` → `table.options.onColumnFiltersChange` → toga25-supply `useServerTableUrlState`, which resets to **page 1 on any filter change** — so an unguarded Clear with nothing applied would bounce the user off their page.
|
|
122
|
+
- **⚠ OPEN — `HeaderFilterRange.handleClear` has the same bug** (~line 162): it resets `singleValue`/`minValue`/`maxValue` but never calls `onClear()` and never closes the popover. Same fix shape as the multi-select. Not fixed as of 2026-09-28.
|
|
123
|
+
- **⚠ OPEN — `SortIcon` has no `aria-*` at all.** Sort direction and position are conveyed by shape only — no `aria-label`, no `aria-sort`. Pre-existing; needs its own ticket.
|
|
124
|
+
- **A unit test cannot prove a CSS change — verify in blox's own Storybook.** JSDOM applies no real layout, so a Vitest test can only assert the modifier class is present; it can never show the triangle actually flipped or that the digit stayed upright. Add a co-located `*.stories.tsx` rendering the states side by side, run `npm run blox` (port 6006), and look at it.
|
|
121
125
|
- **⚠ OPEN — the multi-select filter matches by SUBSTRING, not exactly.** `HeaderFilterMultiselect` comma-joins the picked slugs and `src/api/tableData/getDataTableData.ts` turns that into `where: { or: [ { field: { contains: value } }, … ] }`. Picking "Jon Smith" also matches "Jon Smithson" — it over-includes rows.
|
|
122
126
|
- **⚠ OPEN — an option value containing a comma silently returns zero rows.** It splits into two bogus filter terms, with no error. Both bugs are pre-existing and affect **every** multi-select column including Order Status; both fix in one place — send a list / exact-equals operator instead of a comma-joined `contains`. Not fixed as of 2026-09-15.
|
|
123
127
|
- **⚠ `useTableData` APPENDS `.uuid` to `additionalData.slug` — unless the slug is on a hardcoded allow-list.** The list is exactly `catalogId`, `customerId`, `Items.catalogId`, `SalesOrderItems.uuid`, `SalesOrders.uuid` (verified in `@agilant/toga-blox` **1.1.2-production.144**, the version toga25-supply pins). Consequences for anyone writing a filter slug into config or a `TableLevel` callback:
|
|
@@ -139,6 +143,9 @@ This exists because an unscoped `GET /{recordRoute}` returns *every* row of a lo
|
|
|
139
143
|
- **Filter presentational components must seed their internal state from props.** Filter state survives the URL round-trip and is rehydrated into TanStack `columnFilters` by toga25-supply `useServerTableUrlState`, carried through `PrimaryTableHeaderCell`. But `HeaderFilterSearch`/`HeaderFilterRange` are remounted fresh each time a popover opens, so they must seed local state from props or reopening *shows the wrong state*. **Text (`HeaderFilterSearch`)** now persists as a single readable URL param `<slug>_<col>_filter` = comma-separated `mode:value` pairs (values URL-encoded so `:`/`,` inside a term can't corrupt the delimiters); `getDataTableData` parses it and pushes **one AND WHERE condition per chip** (mode→op: includes→contains, startsWith→starts, endsWith→ends, exactly→eq, excludes→excludes). The component rehydrates the full `tags` array from props. Old base64/legacy single-mode `_starts`/`_ends`/… URLs don't parse under the new scheme (graceful — just re-apply the filter). The browser may percent-encode `:`/`,` in the address bar but it round-trips. **Range (`HeaderFilterRange`)** still persists as key suffixes (`_min`/`_max`/`_eq`/`_between`); it reads mode + value(s) back from `filterParams` via a rehydration effect keyed on `JSON.stringify(filterParams)` (`eq`→Exactly, `min`→MoreThan, `max`→LessThan, `between`→range min/max).
|
|
140
144
|
- **Table/surface meta is cached with `Infinity` staleTime/gcTime** (`useFetchTablePageMeta`), so after a DB metadata change (`TableViewFields`, `Core.RecordFields`) a **hard reload** is required to see it. And the app consumes toga-blox's built **`dist/`**, not `src/` — editing blox source requires `npm run build` before it takes effect at runtime.
|
|
141
145
|
|
|
146
|
+
## Change history
|
|
147
|
+
- 2026-09-28 — multi-select filter "Clear" now actually clears (calls `onClear`, page-1 guard); sort badge now points down for `desc` via CSS rotation (apeterson)
|
|
148
|
+
|
|
142
149
|
## Related
|
|
143
150
|
|
|
144
151
|
- [Primary Table templates](primary-table-templates.md) — the wired table built on this.
|
|
@@ -6,7 +6,7 @@ project: TOGa Blox
|
|
|
6
6
|
client: shared
|
|
7
7
|
type: workflow
|
|
8
8
|
status: active
|
|
9
|
-
updated: 2026-09-
|
|
9
|
+
updated: 2026-09-28
|
|
10
10
|
owners: [jcardinal, apeterson, tcox, rgirish]
|
|
11
11
|
files:
|
|
12
12
|
- toga-blox/.github/workflows/publish.yml
|
|
@@ -110,6 +110,9 @@ Dates: the **August 2026** npm change removed 2FA-bypass for *account and packag
|
|
|
110
110
|
If any one fails, bump on the feature branch instead. Note the bump commit lands *only* on `_sandbox-client` and is never merged back, which is precisely what generates the "N behind" release plumbing.
|
|
111
111
|
- **`gh` is not installed on every dev machine.** The skill's `gh run watch` monitoring step simply does not work there. Fall back to polling the registry: `npm view @agilant/toga-blox@sandbox-client version` (or `npm dist-tag ls @agilant/toga-blox`) until the new version appears — the registry is the authoritative confirmation anyway.
|
|
112
112
|
- **A publish is only half the deploy — and the consumer must depend on the LITERAL dist-tag.** In `toga25-supply/package.json` the dependency is the bare tag string (`"@agilant/toga-blox": "sandbox-client"`), **not** a version range. `npm install @agilant/toga-blox@sandbox-client` rewrites it to a `^` range, which then stops following base bumps — re-set the literal tag, re-run `npm install`, commit `package-lock.json`. Verify what is installed with `node -p "require('./node_modules/@agilant/toga-blox/package.json').version"`. Exact pins drift: branch `TRUE-81379` was pinned to `1.1.3-production.166` while `node_modules` held `1.1.10-sandbox-client.163` — check this first when a fix "works locally but not after deploy". Likewise the feature branch keeps its old base version after a bump lands on `_sandbox-client`; that is expected.
|
|
113
|
+
- **⚠ A CARET on a prerelease pin can NEVER upgrade — it reads as "my fix did not deploy", not as drift.** npm matches `^` on a prerelease only inside the **same** `major.minor.patch`, so `toga25-supply` `_sandbox-client` pinned at `"^1.1.4-sandbox-client.188"` could not pick up `1.1.10-sandbox-client.191` — the bump had to be explicit. This is the concrete failure mode behind the literal-tag rule above: silent never-upgrade, not slow drift. Confirmed 2026-09-28.
|
|
114
|
+
- **`toga25-supply` `_sandbox-client` has NO `amplify.yml`** — it builds from the Amplify **console** build spec. If that spec runs `npm install "@agilant/toga-blox@$MODE"`, the deployed build takes the **channel head** and the `package.json` pin governs local dev only. Check the console spec before chasing a "stale deploy".
|
|
115
|
+
- **Confirm a bump landed by grepping the INSTALLED `dist/`, not by trusting the install.** e.g. `grep -n "sortDropdownBadgeDesc" node_modules/@agilant/toga-blox/dist/components/Table/components/columnFiltersAndSorts/SortIcon.js`. Same technique proves a published channel actually carries a fix (2026-09-28: verified a bug was live in `1.1.4-production.184`'s dist, not just in src).
|
|
113
116
|
- **⚠ Credentials have been found in the local git remote URL.** Verified 2026-08-26: the `origin` remote for the `toga-blox-npm` checkout had a **GitHub PAT embedded in plaintext in the URL** (visible to anything that runs `git remote get-url origin`, and to any tool that reads `.git/config`). Check for this and re-point the remote at SSH or a credential helper; treat any token found this way as compromised and rotate it. This is the local-config sibling of the committed-`.npmrc` violation already recorded in `../../../standards/frontend.md`.
|
|
114
117
|
- **FontAwesome v7 `style`-prop TS2322** (fixed 2026-07-21 in `getFontAwesomeIcon.tsx`, and a reusable pattern for any consumer). FA v7 types the `FontAwesomeIcon` `style` prop as `CSSProperties & CSSVariables`, where `CSSVariables` requires a `--fa-*` custom-property index signature that plain `React.CSSProperties` lacks. Cast the passed style to `React.CSSProperties & Record<\`--${string}\`, string>` — the value type must be `string` (**not** `string | number`; `CSSVariables` requires `string`). Runtime and function signature are unchanged, so it is safe for the ~40 consumers.
|
|
115
118
|
- **The `_production`/`_beta` build copies only `.scss` — a plain `.css` component stylesheet is silently dropped from the tarball.** On `_production` and `_beta` the `build` script is `tsc && copyfiles -u 1 "src/**/*.scss" dist`: `tsc` compiles a component's `import "./Foo.css"` into the emitted JS, but `copyfiles` never ships the `.css`, so the published package contains JS importing a file that isn't in the tarball. Consumers then fail their Vite/Rollup build with `Could not resolve "./Foo.css"` (seen with `EnvironmentBadge` in `@agilant/toga-blox@beta` 1.0.316-beta.108, breaking `toga2-commerce`). **Convention on the prod/beta build: author component stylesheets as `.scss`, never `.css`** (a flat `.css` file is valid SCSS as-is, so renaming is the zero-risk fix — no build-script change). Note the build description in `architecture.md` (copies `*.scss/*.css/*.module.css`) reflects the in-progress `feature-new-table` branch, **not** the current `_production`/`_beta` build; that feature branch is not to be merged to prod/beta just to pick up the wider copy glob.
|
|
@@ -120,6 +123,9 @@ Dates: the **August 2026** npm change removed 2FA-bypass for *account and packag
|
|
|
120
123
|
- **Keep the production base ahead of, or level with, sandbox.** `_production`'s base had drifted **12 patches behind** `_sandbox-client`, so the production channel was publishing *numerically lower* versions than sandbox. The base was bumped `1.0.322` → **1.1.0** to mark the `feature-new-table` release and stop that inversion; CI published `1.1.1-production.139`.
|
|
121
124
|
- **Pushing to a `_*` branch IS the publish — there is no separate step to run afterwards.** The merge push itself triggered a publish (`1.0.323-production.137`) about a minute after the merge landed. Consequence: a merge that is not release-ready still ships. Get the tree right *before* you push to a release branch.
|
|
122
125
|
|
|
126
|
+
## Change history
|
|
127
|
+
- 2026-09-28 — recorded the caret-on-prerelease never-upgrade trap, toga25-supply's missing `amplify.yml`, and dist-grep verification (apeterson)
|
|
128
|
+
|
|
123
129
|
## Related
|
|
124
130
|
|
|
125
131
|
- [Landing a large feature branch](./landing-a-large-feature-branch.md) — the merge procedure that feeds a release branch.
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Where Secrets Live (AWS Secrets Manager, Build Credentials)
|
|
3
|
+
framework: "2.0"
|
|
4
|
+
project: _Underscore
|
|
5
|
+
client: shared
|
|
6
|
+
type: standard
|
|
7
|
+
status: active
|
|
8
|
+
updated: 2026-09-28
|
|
9
|
+
owners: [jcardinal]
|
|
10
|
+
related:
|
|
11
|
+
- frontend-deploy.md
|
|
12
|
+
- ../apps/_underscore/features/cloud-secrets-manager.md
|
|
13
|
+
- ../apps/api2/architecture.md
|
|
14
|
+
- ../apps/toga25-blox/architecture.md
|
|
15
|
+
---
|
|
16
|
+
|
|
17
|
+
Every secret a machine (build, CI, app) reads lives in one place, AWS Secrets Manager in the production account; open before adding, reading, or rotating any credential.
|
|
18
|
+
|
|
19
|
+
## Rule
|
|
20
|
+
|
|
21
|
+
- One place: AWS Secrets Manager, **production account `654654170868`, `us-east-1`**. No secret values in config files, `.env`, source, CI YAML, or committed anywhere.
|
|
22
|
+
- Secrets are **JSON**. Value key is `token` (or named keys when a secret holds several related values).
|
|
23
|
+
- Never write a secret's actual value into a knowledge doc, commit, log, URL, or chat — location and name only.
|
|
24
|
+
|
|
25
|
+
## Naming
|
|
26
|
+
|
|
27
|
+
- Folder-style prefix by **who reads it**: `build/` = anything a build/CI job may read (Amplify, GitHub Actions).
|
|
28
|
+
- Other existing prefixes: `toga/ssl/*` (SAML SP certs), `talos/<env>/*`, `sftp/<client>/*`.
|
|
29
|
+
- Current `build/` secrets:
|
|
30
|
+
- `build/github-read` — fine-grained GitHub token on the shared deployment user `agilantgit`, resource owner org `agilantsolutions`, Contents read-only on `toga25-blox` / `toga25-desk` / `toga25-iq` / `toga2-template`, 1-year expiry — renew before it expires.
|
|
31
|
+
- `build/fontawesome` — FontAwesome Pro package token.
|
|
32
|
+
|
|
33
|
+
## Env var name for local dev / CI
|
|
34
|
+
|
|
35
|
+
Upper-case the secret name, `/` → `_`, append `_<KEY>` upper-cased.
|
|
36
|
+
|
|
37
|
+
- `build/fontawesome` + key `token` → `BUILD_FONTAWESOME_TOKEN`. Read by the app's `.npmrc`. Each developer sets it once: `setx BUILD_FONTAWESOME_TOKEN "<token>"`, then open a new terminal.
|
|
38
|
+
- `build/github-read` → `BUILD_GITHUB_READ_TOKEN`.
|
|
39
|
+
|
|
40
|
+
## Cross-account access (prod-stored, non-prod reader)
|
|
41
|
+
|
|
42
|
+
Secret is encrypted with KMS key `alias/toga-secrets` (key policy lets non-prod account `975050298201` decrypt only via Secrets Manager) plus a resource policy on the secret naming the reader roles. Readers must use the **full ARN**: `arn:aws:secretsmanager:us-east-1:654654170868:secret:<name>`. Same pattern as `toga/ssl/*` (read by non-prod `aws-elasticbeanstalk-ec2-role`).
|
|
43
|
+
|
|
44
|
+
### Build readers today (non-prod `975050298201`)
|
|
45
|
+
|
|
46
|
+
- `amplify-platform-build` — Amplify service role; inline policy `platform-build-secrets-read` grants `secretsmanager:GetSecretValue` on prod `secret:build/*` + `kms:Decrypt` on `toga-secrets` via Secrets Manager. Attached to Amplify app `toga25-desk` (`d311sdoq12ddne`).
|
|
47
|
+
- `github-actions-toga25-blox` — GitHub OIDC provider (`token.actions.githubusercontent.com`), scoped to repo `agilantsolutions/toga25-blox` only, same policy shape. Used by `.github/workflows/apps-build.yml`.
|
|
48
|
+
- **Open item:** production Amplify builds need their own `amplify-platform-build` role in the **prod** account (same-account read — no cross-account sharing needed there).
|
|
49
|
+
|
|
50
|
+
## Adding a secret / a reader
|
|
51
|
+
|
|
52
|
+
- **New build secret:** create under `build/`, encrypt with `alias/toga-secrets`, add the same resource policy (the two non-prod roles above). No IAM change needed — both roles already allow `build/*`.
|
|
53
|
+
- **New non-prod reader role:** add its ARN to each shared secret's resource policy.
|
|
54
|
+
|
|
55
|
+
## Rotation
|
|
56
|
+
|
|
57
|
+
- Rotate at the source (GitHub, FontAwesome, etc.), update the value in Secrets Manager. Builds pick it up next run; developers update their local env var.
|
|
58
|
+
- **Open item:** old FontAwesome + npmjs tokens are committed in `toga25-desk` / `toga25-iq` / `toga2-template` git history — rotate once every app reads `build/fontawesome`. Legacy apps `toga25-supply` / `toga2-commerce` still use the current (unrotated) token, so rotating now would break them first.
|
|
59
|
+
- Any credential that has ever been committed must be treated as compromised and rotated — deleting the line does not remove it from history. See [worker2 committed secrets debt register](../apps/worker2/features/committed-secrets-and-config-debt.md) and [api2 architecture Security note](../apps/api2/architecture.md#security-note-committed-secrets) for the existing backlog this standard is meant to stop growing.
|
|
60
|
+
|
|
61
|
+
## Never
|
|
62
|
+
|
|
63
|
+
- Commit a token.
|
|
64
|
+
- Put a token in a URL, log, or chat.
|
|
65
|
+
- Give a build role access to all secrets — a build runs hundreds of npm packages; scope to `build/*` or narrower.
|
|
66
|
+
|
|
67
|
+
## Related
|
|
68
|
+
|
|
69
|
+
- [2.0 Frontend Deployment (Vite + AWS Amplify)](frontend-deploy.md)
|
|
70
|
+
- [_Cloud Secrets Manager helper](../apps/_underscore/features/cloud-secrets-manager.md) — the app-side (PHP) reader; this standard covers the AWS-side layout and build/CI readers
|
|
71
|
+
- [API (api2) Architecture — Security note](../apps/api2/architecture.md#security-note-committed-secrets)
|
|
72
|
+
- [toga25-blox Architecture](../apps/toga25-blox/architecture.md)
|
|
73
|
+
</content>
|
package/knowledge/INDEX.md
CHANGED
|
@@ -22,7 +22,7 @@ _Auto-generated by `knowledge.js index`. Do not hand-edit._
|
|
|
22
22
|
- **_underscore** (_Underscore) _(framework core)_ — 90 doc(s) → [2.0/apps/_underscore/INDEX.md](2.0/apps/_underscore/INDEX.md)
|
|
23
23
|
- **worker2** (Worker) — 80 doc(s) → [2.0/apps/worker2/INDEX.md](2.0/apps/worker2/INDEX.md)
|
|
24
24
|
- **api2** (API) — 26 doc(s) → [2.0/apps/api2/INDEX.md](2.0/apps/api2/INDEX.md)
|
|
25
|
-
- **dbchanges2** (Database Changes) _(framework core)_ —
|
|
25
|
+
- **dbchanges2** (Database Changes) _(framework core)_ — 22 doc(s) → [2.0/apps/dbchanges2/INDEX.md](2.0/apps/dbchanges2/INDEX.md)
|
|
26
26
|
- **toga2-supply** (TOGa Supply) — 9 doc(s) → [2.0/apps/toga2-supply/INDEX.md](2.0/apps/toga2-supply/INDEX.md)
|
|
27
27
|
- **saml** (SAML SSO Gateway) — 9 doc(s) → [2.0/apps/saml/INDEX.md](2.0/apps/saml/INDEX.md)
|
|
28
28
|
- **apiproxy** (API Proxy) — 1 doc(s) → [2.0/apps/apiproxy/INDEX.md](2.0/apps/apiproxy/INDEX.md)
|
|
@@ -2,4 +2,4 @@
|
|
|
2
2
|
|
|
3
3
|
| Doc | Framework | Summary |
|
|
4
4
|
|-----|-----------|---------|
|
|
5
|
-
| [Adyen](profile.md) | 2.0 | Adyen live TOGa Supply client (`Client_Adyen`) on the 1.0 NetSuite→TOGa Supply sync, status draft; open for the
|
|
5
|
+
| [Adyen](profile.md) | 2.0 | Adyen live TOGa Supply client (`Client_Adyen`) on the 1.0 NetSuite→TOGa Supply sync, status draft; open for the prod tracking-API test orders + SR↔NetSuite matc |
|
|
@@ -11,21 +11,27 @@ project: Worker
|
|
|
11
11
|
client: adyen
|
|
12
12
|
type: profile
|
|
13
13
|
status: draft
|
|
14
|
-
updated: 2026-09-
|
|
14
|
+
updated: 2026-09-28
|
|
15
15
|
owners: ["rgirish"]
|
|
16
16
|
files:
|
|
17
17
|
- worker/crons/toga2/netsuite/sync_togasupply_adyen.php
|
|
18
18
|
- library/app/api/toga2.php
|
|
19
19
|
- _underscore/Model/Client/Item.php
|
|
20
|
+
- _underscore/Trait/Netsuite/SalesOrder.php
|
|
21
|
+
- _underscore/Trait/Netsuite/ItemFulfillment.php
|
|
22
|
+
- dbchanges2/Client_Adyen/2026-09-22a - AdyenApiTestSeed.sql
|
|
23
|
+
- dbchanges2/Client_Adyen/2026-09-28a - LinkTestOrdersToNetsuite.sql
|
|
24
|
+
- dbchanges2/Client_Adyen/2026-09-28b - RemoveUnmatchedTestOrders.sql
|
|
20
25
|
related:
|
|
21
26
|
- ../../1.0/apps/worker/features/netsuite-togasupply-per-client-sync.md
|
|
27
|
+
- ../../1.0/apps/library/features/netsuite-suiteql-api-reference.md
|
|
22
28
|
- ../growrk/features/transfer-order-flow.md
|
|
23
29
|
- ../../2.0/apps/_underscore/features/acl-permission-chain.md
|
|
24
30
|
- ../../2.0/apps/dbchanges2/workflows/client-schema-drift-audit.md
|
|
25
31
|
- ../../2.0/apps/dbchanges2/workflows/nonprod-metadata-drift-repair.md
|
|
26
32
|
---
|
|
27
33
|
|
|
28
|
-
Adyen live TOGa Supply client (`Client_Adyen`) on the 1.0 NetSuite→TOGa Supply sync, status draft; open for the `isFulfillable`
|
|
34
|
+
Adyen live TOGa Supply client (`Client_Adyen`) on the 1.0 NetSuite→TOGa Supply sync, status draft; open for the prod tracking-API test orders + SR↔NetSuite matching, the `isFulfillable` column gap, the beta=client-sandbox env trap, and the ACL findings.
|
|
29
35
|
|
|
30
36
|
## Summary
|
|
31
37
|
|
|
@@ -36,6 +42,7 @@ Adyen live TOGa Supply client (`Client_Adyen`) on the 1.0 NetSuite→TOGa Supply
|
|
|
36
42
|
|
|
37
43
|
- **NetSuite → TOGa Supply sync.** Per-client thin wrapper over the shared engine — full mechanics on [per-client sync](../../1.0/apps/worker/features/netsuite-togasupply-per-client-sync.md).
|
|
38
44
|
- **Transfer orders ENABLED** (`IS_ENABLED_INTEGRATION_TRANSFER_ORDERS = true`). Adyen's NetSuite transfer orders arrive **as NetSuite sales orders**, classified by `TRANSFER_ORDER_DETECTION_MODE__ZERO_DOLLAR_HOLD` — `holdInvoice === true` **AND** `total === 0`, same rule as GroWrk (see [GroWrk transfer order flow](../growrk/features/transfer-order-flow.md)). Mode is now declared explicitly in Adyen's launcher; `App_Api_Toga2::isTransferOrder()` throws if it is missing, so **`library` and `worker` must deploy together** for Adyen.
|
|
45
|
+
- **IDs:** `Core.Clients` id **48**; NetSuite customer **31054** ("4950 Adyen").
|
|
39
46
|
- **`Client_Adyen`** is one of the client DBs with surface-layer / ACL drift noted on the [ACL permission chain](../../2.0/apps/_underscore/features/acl-permission-chain.md) and [surface-layer schema](../../2.0/apps/dbchanges2/features/surface-layer-schema.md) docs.
|
|
40
47
|
|
|
41
48
|
## Gotchas
|
|
@@ -48,6 +55,32 @@ Adyen live TOGa Supply client (`Client_Adyen`) on the 1.0 NetSuite→TOGa Supply
|
|
|
48
55
|
- **⚠ Adyen's working beta calls hit `client-sandbox`, NOT `dev-sandbox`.** Calls go to `api.beta.togahub.com` with a JWT carrying `"env":"beta"`, which maps to the MCP environment **`client-sandbox`**. `Core.Domains` in client-sandbox shows `api.beta.togahub.com` with `env = 'beta'` and **`clientId = NULL`** — a shared API domain, so the tenant comes from the **JWT's client uuid**, not the hostname.
|
|
49
56
|
- Comparing **dev-sandbox against prod** compares two environments the developer is not using — it produced **three consecutive wrong answers** in the 2026-09-08 session. Decode the token's `env` + `id.client.uuid` before any environment diff — recipe in [non-prod metadata drift, step 1](../../2.0/apps/dbchanges2/workflows/nonprod-metadata-drift-repair.md#steps).
|
|
50
57
|
|
|
58
|
+
### Prod tracking-API test orders — linked to real NetSuite (2026-09-28)
|
|
59
|
+
|
|
60
|
+
Adyen tests the tracking API on **prod**. `2026-09-22a - AdyenApiTestSeed.sql` seeded dummy SalesOrders / ItemFulfillments / TrackingNumbers (fake `1ZADYEN…` numbers). Before the NetSuite sync runs they had to become real, or the sync would create **duplicate SOs**.
|
|
61
|
+
|
|
62
|
+
- **`2026-09-28a - LinkTestOrdersToNetsuite.sql` (RAN on prod):** sets `SalesOrders.c_netsuiteInternalSalesOrderId` + `number` = NS `tranid` + `dateOrder`; `ItemFulfillments.c_netsuiteInternalItemFulfillmentId` + `number` + `dateItemFulfillment`; swaps fake tracking for real FedEx numbers, status `PENDING`. Verified: 22 SOs linked, 21 IFs + 42 tracking numbers real.
|
|
63
|
+
- **`2026-09-28b - RemoveUnmatchedTestOrders.sql` (RAN on prod):** deletes the dummy SO/PO/IF chain for SOs with no NS match. ServiceRequests kept.
|
|
64
|
+
- **Why this prevents duplicates:** the sync matches existing rows by `c_netsuiteInternalSalesOrderId` / `c_netsuiteInternalItemFulfillmentId` (`_underscore/Trait/Netsuite/SalesOrder.php`, `ItemFulfillment.php`). It will replace the dummy `SalesOrderItems` (itemId 6).
|
|
65
|
+
- **⚠ Leftovers:** dummy POs `PO-SR%` and vendor `V-TEST-001` still exist for the linked SOs.
|
|
66
|
+
|
|
67
|
+
**⚠ The Adyen NetSuite sync has never run.** `Parameters` cursors stuck at `2026-08-20 00:00:00|0`, the `Customers` row has no `c_netsuiteInternalCustomerId`, no `Locations`. Set these up before expecting sync data.
|
|
68
|
+
|
|
69
|
+
**How to match an Adyen SR to its NetSuite SO — there is NO shared key.**
|
|
70
|
+
|
|
71
|
+
- Adyen's reference number is `ServiceRequests.c_externalReferenceId` (e.g. 48753831); `SalesOrders.serviceRequestId` → `ServiceRequests.id`. NetSuite does not store it.
|
|
72
|
+
- Match SR contact name + ship-to address against NS SO `otherrefnum` (holds the employee name) / shipping attention + addr1.
|
|
73
|
+
- NS Adyen reclaim SOs: memo "Employee Leaving" / "Contractor Leaving", item `SVC-FS-OFFBOARD-ADN`, one IF each with **2 FedEx packages**. First number (87x/875/876) = outbound, 792x = return label — **this outbound/return split is an assumption, not confirmed.**
|
|
74
|
+
- NS tracking-number lookup in SuiteQL: see [SuiteQL reference → `transaction` table](../../1.0/apps/library/features/netsuite-suiteql-api-reference.md).
|
|
75
|
+
|
|
76
|
+
**Known data gaps (2026-09-28):**
|
|
77
|
+
|
|
78
|
+
1. SR100024 duplicates SR100021 (same Adyen ref 48748808) — a lookup by that ref returns **2 SRs**.
|
|
79
|
+
2. SR100026 → NS SO 290879, not fulfilled yet.
|
|
80
|
+
3. SR100027–30 have no NS SO.
|
|
81
|
+
4. NS SO 288261 has no SR in Toga.
|
|
82
|
+
5. SR100000/01/02/25 are Adyen test SRs with no NS order.
|
|
83
|
+
|
|
51
84
|
### Open defect — `Client_Adyen.Items` missing `isFulfillable` (prod AND beta)
|
|
52
85
|
|
|
53
86
|
- Root cause of the 2026-09-08 "works in sandbox, not in production" report; **NOT an ACL problem**. Adyen is the **only** tenant missing the column.
|
|
@@ -91,3 +124,4 @@ The `AclRecordPermissions` prod-vs-dev-sandbox route-name diff from that session
|
|
|
91
124
|
- Formal/legal client name and account owner — recorded here simply as **Adyen**.
|
|
92
125
|
- Which repos beyond the `apps:` list are genuinely in Adyen's scope.
|
|
93
126
|
- Whether Adyen has client-specific models/features that deserve their own docs.
|
|
127
|
+
- Confirm with Adyen/ops: which FedEx package is outbound vs return; whether SR100024 should be closed; who sets up the NetSuite sync (customer id, Locations, cursors).
|
|
@@ -7,4 +7,5 @@
|
|
|
7
7
|
| [Quad: PO + ASN email importer (import_po_and_asn.php)](features/po-asn-email-import.md) | 1.0 | Quad's inbox-driven PO/ASN CSV importer and its 2026-08 failure classes; open when a Quad PO/ASN email jams the mailbox, fails EV-12, or you touch `import_po_an |
|
|
8
8
|
| [Quad Graphics](profile.md) | 2.0 | Quad Graphics is a TOGA 2.0 client on the `_underscore` platform, prod schema `Client_Quad`. |
|
|
9
9
|
| [Quad Graphics — reversing the 30-item catalog fold (storefront-assortment recovery)](workflows/catalog-fold-reversal.md) | 2.0 | How the 2026-08-31 catalog-blind item merge damaged Quad's storefront and how the reversal was built; open when assessing/repairing catalog-fold damage on a two |
|
|
10
|
+
| [Giving a Quad user every persona plus the `Global Admin` role](workflows/granting-all-personas-and-global-admin.md) | 2.0 | The two-table grant for "give internal user X everything on Quad" — every `Personas` row + `Global Admin` — and why it does **not** produce a combined catalogue |
|
|
10
11
|
| [Giving Quad's internal TOGA staff extra toga25-supply nav (the `Agilant - Administrators` role pattern)](workflows/granting-internal-admin-navigation.md) | 2.0 | How to surface extra 2.5 Supply pages to internal TOGA staff on a tenant without showing them to the client's own users — Quad's first internal-admin role; open |
|
|
@@ -23,6 +23,7 @@ related:
|
|
|
23
23
|
- ../../2.0/apps/toga2-commerce/features/storefront-persona-gating-and-home-counts.md
|
|
24
24
|
- ./workflows/catalog-fold-reversal.md
|
|
25
25
|
- ./workflows/granting-internal-admin-navigation.md
|
|
26
|
+
- ./workflows/granting-all-personas-and-global-admin.md
|
|
26
27
|
- ./features/po-asn-email-import.md
|
|
27
28
|
- ./features/enter-po-details-vendor-selection-ev12.md
|
|
28
29
|
- ../../2.0/apps/_underscore/features/sales-order-po-number-sourcing.md
|
|
@@ -63,7 +64,10 @@ Client-specific DB change-sets live in `dbchanges2/Client_Quad/`.
|
|
|
63
64
|
- **Quad is the ONLY storefront tenant with the persona switcher**
|
|
64
65
|
(`determineShouldShowPersonaSwitcher` returns true for QUAD alone). With it on, `getLoginSettings`
|
|
65
66
|
sets `_personaIds` to a **single** persona (the saved default, else the first) — so a Quad user
|
|
66
|
-
never sees a combined view of their personas.
|
|
67
|
+
never sees a combined view of their personas. Granting a user **all** personas therefore only makes
|
|
68
|
+
them **selectable one at a time** in the switcher; it does not widen what they see at once
|
|
69
|
+
([all personas + Global Admin](./workflows/granting-all-personas-and-global-admin.md)).
|
|
70
|
+
`Client_Quad` also has **no persona 24 at all** (not
|
|
67
71
|
on a user, not on a location, the persona does not exist), so the Compass "persona 24" storefront
|
|
68
72
|
hack never touched Quad. See
|
|
69
73
|
[Storefront persona gating](../../2.0/apps/toga2-commerce/features/storefront-persona-gating-and-home-counts.md).
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Giving a Quad user every persona plus the `Global Admin` role
|
|
3
|
+
framework: "2.0"
|
|
4
|
+
repo: dbchanges2
|
|
5
|
+
project: Database Changes
|
|
6
|
+
client: quad
|
|
7
|
+
type: workflow
|
|
8
|
+
status: active
|
|
9
|
+
updated: 2026-09-28
|
|
10
|
+
owners: ["apeterson"]
|
|
11
|
+
files:
|
|
12
|
+
- dbchanges2/Client_Quad/2026-09-28b - GrantAllPersonasToNSchenkel.sql
|
|
13
|
+
related:
|
|
14
|
+
- ../profile.md
|
|
15
|
+
- ./granting-internal-admin-navigation.md
|
|
16
|
+
- ../../compass-usa/workflows/granting-persona-bundle-access.md
|
|
17
|
+
- ../../../2.0/apps/_underscore/features/effective-persona-resolution.md
|
|
18
|
+
- ../../../2.0/apps/toga2-commerce/features/storefront-persona-gating-and-home-counts.md
|
|
19
|
+
- ../../../2.0/apps/dbchanges2/features/rerunnable-additive-inserts.md
|
|
20
|
+
---
|
|
21
|
+
|
|
22
|
+
The two-table grant for "give internal user X everything on Quad" — every `Personas` row + `Global Admin` — and why it does **not** produce a combined catalogue view on this tenant.
|
|
23
|
+
|
|
24
|
+
## Summary
|
|
25
|
+
|
|
26
|
+
A recurring request shape: *give internal user X everything on tenant Y*. On Quad it is **two
|
|
27
|
+
guarded, re-runnable INSERTs**, both entirely inside `Client_Quad` (no `Core` join — separate prod
|
|
28
|
+
clusters):
|
|
29
|
+
|
|
30
|
+
1. **`Users_Personas`** — one row per `Client_Quad.Personas` row the user does not already hold
|
|
31
|
+
(`Users` resolved by `email`, `CROSS JOIN Personas`), uuid from the house inline `RANDOM_BYTES()`
|
|
32
|
+
v4 expression.
|
|
33
|
+
2. **`Users_Roles`** — the tenant's `Global Admin` role.
|
|
34
|
+
|
|
35
|
+
Both are additive: the user keeps every persona and role they already hold (roles resolve as a
|
|
36
|
+
union — [ACL permission chain](../../../2.0/apps/_underscore/features/acl-permission-chain.md)).
|
|
37
|
+
Nothing is removed, so this is not a reversible-by-re-run change; removing access is a separate,
|
|
38
|
+
destructive job.
|
|
39
|
+
|
|
40
|
+
## Steps
|
|
41
|
+
|
|
42
|
+
1. **Guard `Users_Personas` on `(userId, personaId)`** — that *is* the real unique key
|
|
43
|
+
(`userId_personaId_UNIQUE`, verified against
|
|
44
|
+
`dbchanges2/Client/2026-09-22-BLANK_CLIENT_DATABASE.sql:11410`), so a guarded re-run is a true
|
|
45
|
+
no-op rather than a duplicate-key abort.
|
|
46
|
+
2. **Resolve the role BY NAME, not by id** — `INNER JOIN Roles r ON r.name = 'Global Admin'`. On
|
|
47
|
+
Quad that is id **7** today, but ids drift per environment; the name keeps one file correct
|
|
48
|
+
everywhere.
|
|
49
|
+
3. **Name only `uuid`, `userId`, `personaId` / `roleId`.** `Users_Personas` and `Users_Roles` carry
|
|
50
|
+
**no `dtCreated`** — see the gotcha below.
|
|
51
|
+
4. **End with two verify SELECTs** — the user's persona list ordered by
|
|
52
|
+
`CAST(number AS UNSIGNED)` (`Personas.number` is a string column) and their role list.
|
|
53
|
+
5. **Hand the file to the developer to run.** Verify in the app by logging out and back in.
|
|
54
|
+
|
|
55
|
+
### The 2026-09-28 Quad migration
|
|
56
|
+
`dbchanges2/Client_Quad/2026-09-28b - GrantAllPersonasToNSchenkel.sql` — branch `TRUE-82247`,
|
|
57
|
+
**not committed and not yet run in any environment.** Grants `nschenkel@togatech.com` every
|
|
58
|
+
`Client_Quad` persona plus `Global Admin`.
|
|
59
|
+
|
|
60
|
+
Context: NSchenkel is one of the three `@togatech.com` Quad users deliberately left **off** the new
|
|
61
|
+
`Agilant - Administrators` role created the same day
|
|
62
|
+
([internal-admin navigation](./granting-internal-admin-navigation.md) — that role went to
|
|
63
|
+
`devteam@togatech.com` only). This grant gives a different, wider reach: the tenant's own
|
|
64
|
+
`Global Admin`, not the nav-only internal role.
|
|
65
|
+
|
|
66
|
+
## Gotchas
|
|
67
|
+
|
|
68
|
+
- **⚠ On Quad, "all personas" does NOT mean "sees everything at once".** Quad is the **only**
|
|
69
|
+
toga2-commerce tenant with the **persona switcher**
|
|
70
|
+
(`determineShouldShowPersonaSwitcher` returns true for `QUAD` alone), and with it on
|
|
71
|
+
`getLoginSettings` sets `_personaIds` to a **single** persona (the saved default, else the first).
|
|
72
|
+
So this grant makes every persona **selectable, one at a time** — it does not build the combined
|
|
73
|
+
catalogue view the same grant would give on Compass / Compass Canada. Expect the reporter to say
|
|
74
|
+
"the storefront did not change". See
|
|
75
|
+
[Storefront persona gating](../../../2.0/apps/toga2-commerce/features/storefront-persona-gating-and-home-counts.md).
|
|
76
|
+
- **This touches only source 1 of the three effective-persona sources.** `Locations_Personas` on the
|
|
77
|
+
user's own location and on the 5-hop sector are untouched — so a "who has persona N" check still
|
|
78
|
+
has to resolve all three. See
|
|
79
|
+
[Effective persona resolution](../../../2.0/apps/_underscore/features/effective-persona-resolution.md).
|
|
80
|
+
- **`Global Admin` is not an internal-only role on Quad.** Role 7 is held by 7 users, five of them
|
|
81
|
+
`@quad.com` — fine for granting *to* one internal user, never a place to hang an internal-only
|
|
82
|
+
override.
|
|
83
|
+
|
|
84
|
+
## Change history
|
|
85
|
+
- 2026-09-28 — Created; documents the `Users_Personas` + `Users_Roles` all-personas grant written for
|
|
86
|
+
NSchenkel (not yet run) and the Quad persona-switcher caveat (apeterson)
|
|
@@ -16,6 +16,7 @@ related:
|
|
|
16
16
|
- ../../../2.0/apps/toga25-supply/features/side-navigation-and-default-route.md
|
|
17
17
|
- ../../../2.0/apps/_underscore/features/acl-permission-chain.md
|
|
18
18
|
- ../../../2.0/apps/dbchanges2/features/rerunnable-additive-inserts.md
|
|
19
|
+
- ./granting-all-personas-and-global-admin.md
|
|
19
20
|
---
|
|
20
21
|
|
|
21
22
|
How to surface extra 2.5 Supply pages to internal TOGA staff on a tenant without showing them to the client's own users — Quad's first internal-admin role; open for "internal user can't see page X" on any tenant.
|
|
@@ -45,6 +46,8 @@ staff. That is a tenant-wide behavior change dressed up as a per-user fix.
|
|
|
45
46
|
3. **Create a dedicated `Agilant - Administrators` role** in the client DB if the tenant has none,
|
|
46
47
|
and assign only the internal users the developer names. Quad got **`devteam@togatech.com` only**;
|
|
47
48
|
the other three `@togatech.com` Quad users (JWise, NSchenkel, rgreen) were deliberately left off.
|
|
49
|
+
NSchenkel was instead granted the tenant's own `Global Admin` role (a different, wider reach) —
|
|
50
|
+
[all personas + Global Admin](./granting-all-personas-and-global-admin.md).
|
|
48
51
|
4. **Add role-scoped `SurfaceOverrides` rows** (`surfaceId` 39, the element ids, `IS_VISIBLE` = `'1'`,
|
|
49
52
|
`personaId`/`languageId` NULL) for that new role. Nothing else — no `AclRecordPermissions`, no
|
|
50
53
|
`AclFieldPermissions`, so the migration cannot widen data access.
|
|
@@ -77,5 +80,7 @@ Quad's starting state for contrast: **one** nav override total (element 109 `sal
|
|
|
77
80
|
migration there.
|
|
78
81
|
|
|
79
82
|
## Change history
|
|
83
|
+
- 2026-09-28 — NSchenkel (left off this role) granted `Global Admin` + all personas instead, in a
|
|
84
|
+
separate file; linked. (apeterson)
|
|
80
85
|
- 2026-09-28 — Created; Quad's first `Agilant - Administrators` role + Items/Vendor Items nav
|
|
81
86
|
overrides written (not yet run), scoped away from `Global Admin` role 7 (apeterson)
|
package/package.json
CHANGED