toga-ai 1.0.665 → 1.0.666
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/toga-blox/INDEX.md +1 -0
- package/knowledge/2.0/apps/toga-blox/architecture.md +2 -1
- package/knowledge/2.0/apps/toga-blox/features/api-client.md +41 -1
- package/knowledge/2.0/apps/toga-blox/features/blox-authoring-defects.md +129 -0
- package/knowledge/2.0/apps/toga-blox/workflows/dynamic-publish-pipeline.md +32 -1
- package/knowledge/2.0/apps/toga2-commerce/workflows/amplify-build-and-deploy.md +21 -1
- package/knowledge/2.0/apps/toga25-supply/INDEX.md +1 -0
- package/knowledge/2.0/apps/toga25-supply/features/sso-redirect-and-session-gating.md +100 -0
- package/knowledge/2.0/apps/toga25-supply/workflows/amplify-deployment.md +26 -2
- package/knowledge/2.0/standards/frontend.md +35 -4
- package/knowledge/INDEX.md +3 -3
- package/knowledge/clients/compass-usa/INDEX.md +1 -0
- package/knowledge/clients/compass-usa/profile.md +2 -1
- package/knowledge/clients/compass-usa/workflows/granting-navigation-access.md +158 -0
- package/knowledge/clients/compass-usa/workflows/granting-persona-bundle-access.md +1 -0
- package/package.json +1 -1
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
| [AdvancedSelect (virtualized single/multi select)](features/advanced-select.md) | `AdvancedSelect<T>` is a **fully controlled, virtualized** single/multi select built from scratch (not react-select) on **`@tanstack/react-virtual`**, for large | toga-blox/src/components/AdvancedSelect/AdvancedSelect.tsx, toga-blox/src/components/AdvancedSelect/AdvancedSelect.types.ts, toga-blox/src/components/AdvancedSelect/AdvancedSelect.module.css |
|
|
8
8
|
| [API client (axios wrapper, auth, table-data fetchers)](features/api-client.md) | `src/api/` is a thin **axios** wrapper that standardizes the **2.0 API envelope**, manages auth (Bearer + refresh), serializes complex query options, and provid | toga-blox/src/reactQuery/queryHelpers.ts, toga-blox/src/api/index.ts, toga-blox/src/api/axiosInstance.ts, toga-blox/src/api/apiFunctions.ts, toga-blox/src/api/auth.ts, toga-blox/src/api/genericApi.ts, toga-blox/src/api/types.ts, toga-blox/src/api/tableData |
|
|
9
9
|
| [BaseInput (react-hook-form field factory)](features/base-input.md) | `BaseInput` is a **form-field factory** driven by react-hook-form. | toga-blox/src/components/BaseInput/BaseInput.tsx, toga-blox/src/components/BaseInput/BaseInput.types.ts, toga-blox/src/components/BaseInput/BaseInput.module.css, toga-blox/src/components/BaseInput/components |
|
|
10
|
+
| [blox authoring defect backlog (fix IN blox, not around it)](features/blox-authoring-defects.md) | The standing list of **defects to fix inside `@agilant/toga-blox`**. | toga-blox/src/api/auth.ts, toga-blox/src/api/axiosInstance.ts, toga-blox/src/reactQuery/queryHelpers.ts, toga-blox/src/components/BaseInput, toga-blox/tsconfig.json, toga-blox/package.json |
|
|
10
11
|
| [Primary Table templates (server/client, sizing, virtualization)](features/primary-table-templates.md) | `src/templates/PrimaryTable/` is the **production, wired-up table** built on the [Table component](table.md). | toga-blox/src/components/Table/themeConfig/toga.module.css, toga-blox/src/templates/PrimaryTable/PrimaryTable.tsx, toga-blox/src/templates/PrimaryTable/PrimaryTableServerTemplate.tsx, toga-blox/src/templates/PrimaryTable/PrimaryTableClientTemplate.tsx, toga-blox/src/templates/PrimaryTable/PrimaryTableHeaderCell.tsx, toga-blox/src/templates/PrimaryTable/PrimaryTableBodyCell.tsx, toga-blox/src/templates/PrimaryTable/PrimaryTableRow.tsx, toga-blox/src/templates/PrimaryTable/PrimaryTableExpandableRow.tsx, toga-blox/src/templates/PrimaryTable/types.ts |
|
|
11
12
|
| [TableRecordModal (record-detail modal shell)](features/table-record-modal.md) | `TableRecordModal` is a **generic, presentational modal shell** for showing a single table record (row) in detail — typically opened from a table row click. | toga-blox/src/components/TableRecordModal/TableRecordModal.tsx, toga-blox/src/components/TableRecordModal/index.ts, toga-blox/src/components/TableRecordModal/tableRecordModal.module.css |
|
|
12
13
|
| [Table component (cells, action cells, filters & sorts, hooks, theming)](features/table.md) | The `Table` component (`src/components/Table/`) is the **TanStack Table v8** building block behind the [Primary Table templates](primary-table-templates.md). | toga-blox/src/components/Table/hooks/useFetchTablePageMeta.ts, toga-blox/src/api/types.ts, toga-blox/src/components/Table/index.ts, toga-blox/src/components/Table/types.ts, toga-blox/src/components/Table/utils/buildTanstackColumns.tsx, toga-blox/src/components/Table/utils/resolveCellType.tsx, toga-blox/src/components/Table/components/cellTypes, toga-blox/src/components/Table/components/actionCells, toga-blox/src/components/Table/components/columnFiltersAndSorts, toga-blox/src/components/Table/hooks, toga-blox/src/components/Table/themeConfig |
|
|
@@ -6,7 +6,7 @@ project: TOGa Blox
|
|
|
6
6
|
client: shared
|
|
7
7
|
type: architecture
|
|
8
8
|
status: active
|
|
9
|
-
updated: 2026-
|
|
9
|
+
updated: 2026-08-27
|
|
10
10
|
owners: [apeterson]
|
|
11
11
|
files:
|
|
12
12
|
- toga-blox/package.json
|
|
@@ -20,6 +20,7 @@ related:
|
|
|
20
20
|
- features/base-input.md
|
|
21
21
|
- features/advanced-select.md
|
|
22
22
|
- features/api-client.md
|
|
23
|
+
- features/blox-authoring-defects.md
|
|
23
24
|
---
|
|
24
25
|
|
|
25
26
|
## Summary
|
|
@@ -6,7 +6,7 @@ project: TOGa Blox
|
|
|
6
6
|
client: shared
|
|
7
7
|
type: feature
|
|
8
8
|
status: active
|
|
9
|
-
updated: 2026-08-
|
|
9
|
+
updated: 2026-08-27
|
|
10
10
|
owners: [apeterson, tcox]
|
|
11
11
|
files:
|
|
12
12
|
- toga-blox/src/reactQuery/queryHelpers.ts
|
|
@@ -118,8 +118,48 @@ Filterable`, `hyperlinkField`, `imageUrlField`, `sticky`, `precision`, …), `Da
|
|
|
118
118
|
paren pair so the group re-wrap doesn't double-wrap. Any raw pre-formatted where clause was
|
|
119
119
|
100% broken before this. If you add a new raw-string clause path, route it through this string
|
|
120
120
|
branch — do **not** feed a raw clause into an object-expecting serializer.
|
|
121
|
+
- **⚠ AUTHORING DEFECT — `handleClientAuthentication(uuid)` collides by name with app-local SSO
|
|
122
|
+
helpers and is not interchangeable with them.** blox's version `POST`s
|
|
123
|
+
`/auth/encrypted-user-uuid` with an **empty payload** (`{client:"",user:""}`), writes
|
|
124
|
+
access/refresh tokens to `localStorage`, and returns a `/users` payload. `toga2-supply` has a
|
|
125
|
+
local util of the **same name** that resolves a client's SSO URL — a completely different
|
|
126
|
+
function. `toga25-supply` called blox's by mistake and SSO silently never fired for ~any data,
|
|
127
|
+
because the returned object has no `singleSignOnServiceUrl` property (masked by an `as any`).
|
|
128
|
+
See [toga25-supply SSO redirect & session gating](../../toga25-supply/features/sso-redirect-and-session-gating.md).
|
|
129
|
+
- **⚠ AUTHORING DEFECT — `fetchPublicToken()` PERSISTS the public token to `localStorage`**
|
|
130
|
+
(`accessToken` + `refreshToken`), so a logged-**out** visitor is indistinguishable from a
|
|
131
|
+
logged-in one if you gate on `accessToken`. The correct discriminator is `localStorage`'s
|
|
132
|
+
**`user`** key — which this module's own request interceptor already uses
|
|
133
|
+
(`userString ? accessToken : fetchPublicToken(baseURL)`). Corollary: `localStorage` repopulating
|
|
134
|
+
seconds after logout is a **fresh public token**, not a surviving session, and no app-side code
|
|
135
|
+
can stop it while this behavior stands.
|
|
136
|
+
- **⚠ AUTHORING DEFECT — `performLogout` hardcodes `window.location.href = "/"`.** On a
|
|
137
|
+
single-sign-on client, `/` is exactly the route that triggers the IdP redirect, so logging out
|
|
138
|
+
bounces the user straight back into SSO and `/login` becomes unreachable. Apps can pass
|
|
139
|
+
`onLogout` to `createAxiosInstance` **and** stop calling `performLogout` from their own logout
|
|
140
|
+
action — **both**, since the Logout button and the 401-interceptor path are separate — but the
|
|
141
|
+
real fix is blox-authoring work.
|
|
142
|
+
- **`GET /domains` requires a Bearer token.** The request interceptor transparently supplies a
|
|
143
|
+
*public* one when there is no `user` in `localStorage`, which is why a logged-out domain lookup
|
|
144
|
+
works in-app but **401s from a raw `curl`**. Reproduce out-of-app by minting a public token first.
|
|
145
|
+
- **Verifying a domain is registered without DB access:** `POST /auth/public` with an explicit
|
|
146
|
+
`Origin` header. `Origin: https://compass.togasupply.com` → **201** with an audience of
|
|
147
|
+
`{client: "Compass Group", app: "TOGa Supply"}`; no `Origin` at all → **401 EN-6**.
|
|
148
|
+
- **OPEN QUESTION (unresolved 2026-08-27): `EV-14` from a filtered `/domains` lookup.** `EV-14` is a
|
|
149
|
+
**404 "expected exactly one record"**. A public-token `/domains` lookup returned it for every
|
|
150
|
+
environment slug tried (`PRODUCTION`, `PROD`, `LIVE`). It was **not** determined whether the
|
|
151
|
+
app's own in-browser request differs (different token scope, different filter). Treat as an open
|
|
152
|
+
thread, not a settled finding.
|
|
121
153
|
|
|
122
154
|
## Change history
|
|
155
|
+
- 2026-08-27 — Recorded three blox **authoring defects** surfaced while debugging toga25-supply SSO:
|
|
156
|
+
(1) `handleClientAuthentication` collides by name with app-local SSO helpers and returns a
|
|
157
|
+
`/users` payload with no `singleSignOnServiceUrl`; (2) `fetchPublicToken` persists the public
|
|
158
|
+
token to `localStorage`, making logged-out visitors look authenticated unless you gate on the
|
|
159
|
+
`user` key; (3) `performLogout`'s hardcoded `"/"` redirect traps SSO clients in a logout→IdP loop
|
|
160
|
+
and makes `/login` unreachable. Also documented that `/domains` needs a Bearer token (public one
|
|
161
|
+
auto-supplied in-app, hence raw-curl 401s), the `Origin`-header trick for confirming a domain is
|
|
162
|
+
registered, and left the public-token `/domains` **EV-14** result as an open question. (apeterson)
|
|
123
163
|
- 2026-08-17 — Fixed `assembleOptionsWhere` spreading a raw-string `apiWhereClause` character-by-
|
|
124
164
|
character into invalid `index:0:char` conditions (api2 `Invalid operator '0'` / EO-1 500 opening
|
|
125
165
|
the Compass USA Item Fulfillment modal). String entries now pass through verbatim (single outer
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: blox authoring defect backlog (fix IN blox, not around it)
|
|
3
|
+
framework: "2.0"
|
|
4
|
+
repo: toga-blox
|
|
5
|
+
project: TOGa Blox
|
|
6
|
+
client: shared
|
|
7
|
+
type: feature
|
|
8
|
+
status: active
|
|
9
|
+
updated: 2026-08-27
|
|
10
|
+
owners: [apeterson]
|
|
11
|
+
files:
|
|
12
|
+
- toga-blox/src/api/auth.ts
|
|
13
|
+
- toga-blox/src/api/axiosInstance.ts
|
|
14
|
+
- toga-blox/src/reactQuery/queryHelpers.ts
|
|
15
|
+
- toga-blox/src/components/BaseInput
|
|
16
|
+
- toga-blox/tsconfig.json
|
|
17
|
+
- toga-blox/package.json
|
|
18
|
+
related:
|
|
19
|
+
- ../architecture.md
|
|
20
|
+
- api-client.md
|
|
21
|
+
- base-input.md
|
|
22
|
+
- advanced-select.md
|
|
23
|
+
- ../../../standards/frontend.md
|
|
24
|
+
- ../../toga25-supply/features/sso-redirect-and-session-gating.md
|
|
25
|
+
---
|
|
26
|
+
|
|
27
|
+
## What it is
|
|
28
|
+
|
|
29
|
+
The standing list of **defects to fix inside `@agilant/toga-blox`**. This is the counterpart to
|
|
30
|
+
[frontend standard §22](../../../standards/frontend.md), which tells a *consuming app* how to work
|
|
31
|
+
**around** the same defects. §22 is defensive advice for app authors; **this doc is the work list
|
|
32
|
+
for whoever edits blox itself**, and it is where §22's long-standing "a future `blox-authoring.md`"
|
|
33
|
+
pointer resolves.
|
|
34
|
+
|
|
35
|
+
Nothing here is scheduled. Treat it as a backlog with evidence attached, so a fix can be picked up
|
|
36
|
+
without re-deriving the mechanism. **When one of these is fixed and published, update §22 in the
|
|
37
|
+
same pass** — otherwise consuming apps keep carrying dead workarounds.
|
|
38
|
+
|
|
39
|
+
## How it works (how to use this list)
|
|
40
|
+
|
|
41
|
+
Each entry carries three things deliberately: the **mechanism** (what the code actually does), the
|
|
42
|
+
**consumer-facing symptom** (how it presents in an app, which is usually nothing like the
|
|
43
|
+
mechanism), and a **fix direction** (a suggestion, not a decision). Every one of these is a
|
|
44
|
+
**breaking-ish change for ~40 consumers**, so a fix needs a deprecation path or a coordinated bump
|
|
45
|
+
— see the [publish pipeline](../workflows/dynamic-publish-pipeline.md).
|
|
46
|
+
|
|
47
|
+
## Auth-layer defects (found 2026-08-27, toga25-supply SSO debugging)
|
|
48
|
+
|
|
49
|
+
### 1. `handleClientAuthentication` — misleading name, untyped return
|
|
50
|
+
|
|
51
|
+
- **Mechanism.** Despite the name, it is not a client-authentication *flow driver*: it is an
|
|
52
|
+
**encrypted-uuid token exchange**. It `POST`s `/auth/encrypted-user-uuid` with an empty payload
|
|
53
|
+
(`{client:"",user:""}`), writes access/refresh tokens to `localStorage`, and returns a `/users`
|
|
54
|
+
payload. Its return type is not modeled, so consumers reach into it untyped.
|
|
55
|
+
- **Consumer symptom.** `toga2-supply` has an app-local util of the **exact same name** that
|
|
56
|
+
resolves a client's SSO URL. `toga25-supply` called blox's by mistake; the returned object has no
|
|
57
|
+
`singleSignOnServiceUrl` property, so the SSO redirect evaluated to `null` under **any** data and
|
|
58
|
+
silently fell through to `/login` forever. An `as any` cast is what let it compile and ship.
|
|
59
|
+
- **Fix direction.** **Rename it to describe what it does** (e.g. an `exchangeEncryptedUserUuid`
|
|
60
|
+
shape) so it cannot collide with app-level SSO helpers, and **declare a return type** so a shape
|
|
61
|
+
mismatch is a compile error at the call site instead of a silent `undefined`.
|
|
62
|
+
|
|
63
|
+
### 2. `fetchPublicToken` — persists the public token to `localStorage`
|
|
64
|
+
|
|
65
|
+
- **Mechanism.** It writes both `accessToken` and `refreshToken` to `localStorage` for
|
|
66
|
+
**logged-out** requests, using the same keys a real session uses.
|
|
67
|
+
- **Consumer symptom.** A public token becomes **indistinguishable from a user session** for any
|
|
68
|
+
consumer that gates on `accessToken` — which is the obvious thing to gate on. Bugs present as
|
|
69
|
+
intermittent and environment-dependent (clean storage behaves one way, a browser that has already
|
|
70
|
+
touched the API behaves another). It also makes `localStorage` repopulate seconds after logout,
|
|
71
|
+
which reads as "logout is broken" when it is a fresh public token. The only current discriminator
|
|
72
|
+
is the `user` key — which blox's own request interceptor already relies on
|
|
73
|
+
(`userString ? accessToken : fetchPublicToken(baseURL)`), so the library is internally aware of a
|
|
74
|
+
distinction it does not expose.
|
|
75
|
+
- **Fix direction.** **Hold the public token in memory** (module scope) rather than persisting it,
|
|
76
|
+
so `localStorage` means "a real session exists" and nothing else. If persistence is genuinely
|
|
77
|
+
needed for reloads, use a **separate key** (`publicAccessToken`) so the two can never be confused.
|
|
78
|
+
|
|
79
|
+
### 3. `performLogout` — hardcoded `window.location.href = "/"`
|
|
80
|
+
|
|
81
|
+
- **Mechanism.** The logout destination is a literal `"/"`, not configurable by the caller.
|
|
82
|
+
- **Consumer symptom.** On any **SSO client**, `/` is precisely the route that triggers the IdP
|
|
83
|
+
redirect — so signing out bounces the user straight back into SSO and the local `/login` becomes
|
|
84
|
+
**unreachable**. Worse, an app cannot fully patch around it from one place: the Logout button goes
|
|
85
|
+
through the app's own auth context while an expired token goes through blox's 401 interceptor, so
|
|
86
|
+
a workaround must cover **both** paths. `toga25-supply` built that workaround and then reverted
|
|
87
|
+
it, so the loop is live there today.
|
|
88
|
+
- **Fix direction.** Make the destination **caller-supplied** (a `redirectTo` option, defaulting to
|
|
89
|
+
today's `"/"` for compatibility), or have `performLogout` simply not navigate and let the caller's
|
|
90
|
+
`onLogout` own it.
|
|
91
|
+
|
|
92
|
+
## Already-known authoring items (carried over from §22)
|
|
93
|
+
|
|
94
|
+
These are long-standing and were previously recorded only as consumer-side advice:
|
|
95
|
+
|
|
96
|
+
- **Empty `where` serializes to a literal `()`** in `reactQuery/queryHelpers.ts` — apps must avoid
|
|
97
|
+
emitting an empty filter object. Fix direction: omit the clause entirely when it has no entries.
|
|
98
|
+
- **`dist/main.css` is not compiled** — it still contains raw `@tailwind` directives, so importing
|
|
99
|
+
it does nothing useful. Fix direction: either build it properly or stop shipping it, because its
|
|
100
|
+
presence invites consumers to import it.
|
|
101
|
+
- **Two select stacks** (`react-select` + the custom `AdvancedSelect`) and **two table stacks**
|
|
102
|
+
(`react-table` v7 + `@tanstack/react-table` v8) coexist. This is real dependency weight pushed
|
|
103
|
+
onto every consumer, and the v7 stack is what forces apps to declare `react-table` /
|
|
104
|
+
`react-table-sticky` / `react-multi-select-component` peers. Fix direction: converge on one of
|
|
105
|
+
each and retire the legacy stack.
|
|
106
|
+
- **`BaseInput` has a ~85-prop className-string styling contract** and **no custom-validator
|
|
107
|
+
passthrough** — the latter is why every consuming app rebuilds an app-side validator layer. Fix
|
|
108
|
+
directions: drive styling from host tokens rather than per-prop strings, and accept a validator
|
|
109
|
+
passthrough.
|
|
110
|
+
- **`strict: false` in blox's own tsconfig.** The library ships looser types than the apps that
|
|
111
|
+
consume it, which is how untyped returns like defect 1 survive review. Fix direction: enable
|
|
112
|
+
strict incrementally.
|
|
113
|
+
- **React-18-only peers.** Blocks every consumer from React 19; unscheduled and consumer-wide.
|
|
114
|
+
|
|
115
|
+
## Gotchas
|
|
116
|
+
|
|
117
|
+
- **Do not "fix" these in an app.** Working around them in a consumer is §22's job and is
|
|
118
|
+
legitimate; changing app code to *imitate* the defect is not.
|
|
119
|
+
- **A fix here is a fleet-wide change.** blox is unbundled and consumed by ~40 call sites; anything
|
|
120
|
+
touching auth or `localStorage` keys changes live session behavior on the next channel head that
|
|
121
|
+
a consumer picks up — and `toga2-commerce` picks up the channel head **automatically at build
|
|
122
|
+
time**, with no PR. Sequence auth fixes deliberately.
|
|
123
|
+
|
|
124
|
+
## Change history
|
|
125
|
+
- 2026-08-27 — Created the doc §22 had been pointing at since 2026-08-18. Seeded with the three auth
|
|
126
|
+
defects found while debugging toga25-supply SSO (`handleClientAuthentication`'s misleading name +
|
|
127
|
+
untyped return, `fetchPublicToken` persisting the public token to `localStorage`,
|
|
128
|
+
`performLogout`'s hardcoded `"/"`), each with mechanism / consumer symptom / fix direction, plus
|
|
129
|
+
the previously consumer-side-only items carried over from §22. (apeterson)
|
|
@@ -6,7 +6,7 @@ project: TOGa Blox
|
|
|
6
6
|
client: shared
|
|
7
7
|
type: workflow
|
|
8
8
|
status: active
|
|
9
|
-
updated: 2026-08-
|
|
9
|
+
updated: 2026-08-27
|
|
10
10
|
owners: [jcardinal, apeterson]
|
|
11
11
|
files:
|
|
12
12
|
- toga-blox/.github/workflows/publish.yml
|
|
@@ -167,8 +167,39 @@ environment needs its own maintained `_<mode>` branch in `toga-blox-npm`.
|
|
|
167
167
|
- **Secret hygiene done right.** `publish.yml` writes a transient `.npmrc` at build time from
|
|
168
168
|
GitHub secrets (`${NODE_AUTH_TOKEN}` / FontAwesome token) — it never commits tokens. This is
|
|
169
169
|
the pattern the consuming apps should follow (see the commerce workflow's secret-hygiene note).
|
|
170
|
+
- **⚠ Sibling to the `.scss`-only gotcha, DIFFERENT mechanism: a stylesheet whose import path
|
|
171
|
+
ESCAPES `dist/` is never in the tarball.** After the `feature-new-table` → `_production` merge,
|
|
172
|
+
`global.css` sat at the **repo root** with `import "../global.css"` in `src/index.ts`. `tsc`
|
|
173
|
+
emits that specifier **verbatim**, so `dist/index.js` pointed *outside* `dist/`, and
|
|
174
|
+
`"files": ["dist","README.md"]` meant the stylesheet was never packed. Every consumer failed with
|
|
175
|
+
`Could not resolve "../global.css"`. Shipped broken as **1.0.323-production.137** and
|
|
176
|
+
**1.1.1-production.138**. Fix: move it to `src/global.css` with `import "./global.css"` so
|
|
177
|
+
`copyfiles` ships it into `dist/` — which is exactly what `_sandbox-client` had always published
|
|
178
|
+
(identical blob, different location). **This was a merge-resolution failure, not new code** —
|
|
179
|
+
when landing a long-lived branch, diff the *published tarballs* of the two channels, not just the
|
|
180
|
+
source. Same failure family as the `.scss` gotcha above (JS importing an unshipped stylesheet),
|
|
181
|
+
but caused by the path + the `files` allowlist rather than the copy glob.
|
|
182
|
+
- **There is no base version that publishes as itself.** CI always does **patch + 1** on the base,
|
|
183
|
+
so a base of `1.1.0` can never publish as `1.1.0` — it publishes `1.1.1-production.<run>`. The
|
|
184
|
+
developer controls only the base; plan release numbering around that.
|
|
185
|
+
- **Keep the production base ahead of, or level with, sandbox.** `_production`'s base had drifted
|
|
186
|
+
**12 patches behind** `_sandbox-client`, so the production channel was publishing *numerically
|
|
187
|
+
lower* versions than sandbox. The base was bumped `1.0.322` → **1.1.0** to mark the
|
|
188
|
+
`feature-new-table` release and stop that inversion; CI published `1.1.1-production.139`.
|
|
189
|
+
- **Pushing to a `_*` branch IS the publish — there is no separate step to run afterwards.** The
|
|
190
|
+
merge push itself triggered a publish (`1.0.323-production.137`) about a minute after the merge
|
|
191
|
+
landed. Consequence: a merge that is not release-ready still ships. Get the tree right *before*
|
|
192
|
+
you push to a release branch.
|
|
170
193
|
|
|
171
194
|
## Change history
|
|
195
|
+
- 2026-08-27 — Released blox **1.1.0** on `_production` (published `1.1.1-production.139`) and fixed
|
|
196
|
+
a shipped-broken tarball: the `feature-new-table` → `_production` merge left `global.css` at the
|
|
197
|
+
repo root with `import "../global.css"`, so the emitted `dist/index.js` referenced a path outside
|
|
198
|
+
`dist/` that `"files": ["dist"]` never packed — breaking every consumer on
|
|
199
|
+
`1.0.323-production.137` / `1.1.1-production.138`. Moved to `src/global.css` + `./global.css`.
|
|
200
|
+
Also recorded that CI's patch+1 means no base ever publishes as itself, that the prod base had
|
|
201
|
+
fallen 12 patches behind sandbox (hence the `1.1.0` bump), and that pushing a `_*` branch **is**
|
|
202
|
+
the publish. (apeterson)
|
|
172
203
|
- 2026-08-26 — Deployed `1.0.334-sandbox-client.136` (`feature-new-table` → `_sandbox-client`, 6
|
|
173
204
|
files, no conflicts) and recorded three workflow gotchas from it: (1) a feature branch that never
|
|
174
205
|
touches the `version` line merges without a version conflict, which is why the bump lands on
|
|
@@ -6,7 +6,7 @@ project: TOGa Commerce
|
|
|
6
6
|
client: shared
|
|
7
7
|
type: workflow
|
|
8
8
|
status: active
|
|
9
|
-
updated: 2026-08-
|
|
9
|
+
updated: 2026-08-27
|
|
10
10
|
owners: ["jcardinal", "apeterson"]
|
|
11
11
|
files:
|
|
12
12
|
- toga2-commerce/amplify.yml
|
|
@@ -129,8 +129,28 @@ full cross-repo publish-then-build model. **The channel must exist before the ap
|
|
|
129
129
|
environment variables and write a **transient `.npmrc` at build time** from
|
|
130
130
|
`$FONTAWESOME_NPM_AUTH_TOKEN` — exactly what blox's `publish.yml` already does. Never document the
|
|
131
131
|
token values.
|
|
132
|
+
- **⚠ The `package.json` blox pin governs LOCAL DEV ONLY — the deployed build always takes the
|
|
133
|
+
channel head.** `amplify.yml` runs `npm install "@agilant/toga-blox@$MODE"`, which **overwrites**
|
|
134
|
+
whatever version `package.json` pins. So a production deploy silently picks up a newly published
|
|
135
|
+
blox release **with no PR and no pin change at all** — and conversely, "bump the pin" accomplishes
|
|
136
|
+
much less here than it does in `toga25-supply`, which installs blox as a normal pinned dependency.
|
|
137
|
+
Reason about commerce's deployed blox version from the **dist-tag**, not the manifest.
|
|
138
|
+
- **A caret on a PRERELEASE is inert, not floating.** `^1.0.322-sandbox-client.117` resolves to
|
|
139
|
+
**exactly** `1.0.322-sandbox-client.117`: npm only matches prereleases sharing the same
|
|
140
|
+
`major.minor.patch`. Verified with `semver.maxSatisfying`. It is still a
|
|
141
|
+
[frontend standard](../../../standards/frontend.md) §13(a) violation and confusing, but it is
|
|
142
|
+
**not** a drift risk — don't chase it as one.
|
|
143
|
+
- **Commerce's dependency health is better than supply's**, verified 2026-08-27: all 19 blox peers
|
|
144
|
+
satisfied (none missing) and `resolve.dedupe` is committed on `TRUE-80707`.
|
|
132
145
|
|
|
133
146
|
## Change history
|
|
147
|
+
- 2026-08-27 — Replaced the caret sandbox blox pin `^1.0.322-sandbox-client.117` with the exact
|
|
148
|
+
production-channel `1.1.1-production.139` per frontend standard §13(a), verified with the same
|
|
149
|
+
chain Amplify runs (`tsc -b` + `vite build --mode production`, green). **Working-tree only /
|
|
150
|
+
uncommitted on `TRUE-80707`.** Recorded two findings: `amplify.yml`'s
|
|
151
|
+
`npm install "@agilant/toga-blox@$MODE"` **overwrites** the manifest pin, so the pin governs local
|
|
152
|
+
dev only and a prod deploy takes the channel head with no PR; and a caret on a prerelease is
|
|
153
|
+
**inert**, resolving to that exact version rather than floating. (apeterson)
|
|
134
154
|
|
|
135
155
|
- 2026-08-26 — Dependency-resolution findings from a local commerce build: root-caused an
|
|
136
156
|
`ERESOLVE` to the **missing `legacy-peer-deps=true` in commerce's tracked `.npmrc`** (supply had
|
|
@@ -10,6 +10,7 @@
|
|
|
10
10
|
| [Force Logout on Deployment (useDeploymentGuard)](features/force-logout-on-deployment.md) | On large deployments the backend bumps the Core parameter `META_LAST_REFRESH_DATETIME`. | toga25-supply/src/hooks/useDeploymentGuard.tsx, toga25-supply/src/App.tsx |
|
|
11
11
|
| [Meta-Driven Page & Table Setup](features/meta-driven-table-data.md) | A page in this app is **meta-driven end to end**: the page view model fetches *page meta* (labels, sections, ACL) and *table meta* (the columns/fields + table s | toga25-supply/src/pages/SalesOrders/viewModel/useSalesOrdersPageViewModel.tsx, toga25-supply/src/pages/SalesOrders/hooks/useSalesOrdersTableState.ts, toga25-supply/src/hooks/useTablePageMeta.ts, toga-blox-npm/dist/hooks/useFetchPageMeta.d.ts, toga-blox-npm/dist/hooks/useFetchTablePageMeta.d.ts, toga-blox-npm/dist/hooks/useAssignTableFieldLabels.d.ts, toga-blox-npm/dist/components/Table/hooks/useTableData.d.ts |
|
|
12
12
|
| [Record Modals & Nested Tables](features/record-modals-and-nested-tables.md) | The repo's family of modal + nested-table patterns layered over toga-blox `TableRecordModal` and `PrimaryTable*Layout`. | toga25-supply/src/pages/SalesOrders/view/SalesOrderRecordModalLayout/hooks/usePurchaseOrderDetails.ts, toga25-supply/src/pages/SalesOrders/view/SalesOrderRecordModalLayout/viewModel/useSalesOrderRecordModalLayoutModel.tsx, toga25-supply/src/pages/SalesOrders/view/SalesOrderRecordModalLayout/viewModel/FIELDS/apiFields.json, toga25-supply/src/pages/SalesOrders/helpers/surfaceBundleToTenantFields.ts, toga25-supply/src/layout/PrimaryTableServerLayout/PrimaryTableServerLayout.tsx, toga25-supply/src/layout/PrimaryTableServerLayout/types.ts, toga25-supply/src/layout/ItemRecordModalLayout/, toga25-supply/src/layout/SalesOrderRecordModalLayout/, toga25-supply/src/layout/SalesOrderItemsTableLayout/, toga25-supply/src/layout/ItemFulfillmentModal/, toga25-supply/src/layout/GenericNestedTables/GenericNestedTables.tsx, toga25-supply/src/layout/GenericNestedTables/GenericTableLayout.tsx, toga25-supply/src/pages/Inventory/viewModel/useInventoryPageViewModel.tsx, toga25-supply/src/pages/Inventory/viewModel/FIELDS/DEFAULT/inventoryGroupings.json, toga25-supply/src/hooks/useTableCellInteractions.ts, toga25-supply/src/hooks/useServerTableUrlState.ts, toga25-supply/src/layout/RecordApprovalModal/helpers/handleFormatApprovalWorkflowPayload.ts, toga25-supply/src/layout/RecordApprovalModal/api/approvalDecisionsApi.ts |
|
|
13
|
+
| [SSO redirect & public-vs-user session gating (useAuthenticationFlow)](features/sso-redirect-and-session-gating.md) | How 2.5 Supply decides, on every navigation, whether an anonymous visitor should be bounced to their client's SSO IdP instead of the local `/login` form. | toga25-supply/src/hooks/useAuthenticationFlow.ts, toga25-supply/src/routes.tsx, toga25-supply/src/contexts/AuthContext.tsx, toga25-supply/src/api/api.ts |
|
|
13
14
|
| [Surface Frontend (DB-driven UI consumption, src/surface/)](features/surface-frontend.md) | The frontend consumer of the platform-wide Surface layer — DB-driven UI config fetched from `GET /v2/surfaces/meta?slug=<slug>` instead of statically-imported J | toga25-supply/src/App.tsx, toga25-supply/src/contexts/AuthContext.tsx, toga25-supply/src/surface/applyColSpan.ts, toga25-supply/src/pages/SalesOrders/view/SalesOrderRecordModalLayout/helpers/sectionRenderers.tsx, toga25-supply/src/pages/SalesOrders/view/SalesOrderRecordModalLayout/helpers/getVisibleSections.ts, toga25-supply/src/pages/SalesOrders/view/SalesOrderRecordModalLayout/view/layoutComponents/SalesOrderApprovalSummaryGrid.tsx, toga25-supply/src/surface/useFetchSurfaceMeta.ts, toga25-supply/src/pages/SalesOrders/helpers/surfaceBundleToTenantFields.ts, toga25-supply/src/pages/ServiceRequests/view/ServiceRequestRecordModalLayout/ServiceRequestRecordModalLayout.tsx, toga25-supply/src/pages/ServiceRequests/view/ServiceRequestRecordModalLayout/view/ServiceRequestsView.tsx, toga25-supply/src/pages/ServiceRequests/view/ServiceRequestRecordModalLayout/viewModel/useServiceRequestRecordModalLayoutModel.tsx, toga25-supply/src/pages/ServiceRequests/view/ServiceRequestRecordModalLayout/viewModel/FIELDS/apiFields.json, toga25-supply/src/pages/SalesOrders/view/SalesOrderRecordModalLayout/view/sections/SalesOrderTopBar.tsx, toga25-supply/src/pages/SalesOrders/helpers/buildPatchedTenantFields.ts, toga25-supply/src/pages/SalesOrders/view/SalesOrderRecordModalLayout/viewModel/useSalesOrderRecordModalLayoutModel.tsx, toga25-supply/src/pages/SalesOrders/view/SalesOrderRecordModalLayout/view/SalesOrderView.tsx, toga25-supply/src/pages/SalesOrders/view/SalesOrderRecordModalLayout/view/layoutComponents/SalesOrderSummaryGrid.tsx, toga25-supply/src/pages/SalesOrders/view/SalesOrderRecordModalLayout/helpers/getDetailSections.tsx, toga25-supply/src/pages/SalesOrders/view/SalesOrderRecordModalLayout/view/sections/SalesOrderNotesSection.tsx, toga25-supply/src/pages/SalesOrders/helpers/cleanOrder.ts, toga25-supply/src/pages/SalesOrders/view/SalesOrderRecordModalLayout/view/sections/AdminNotesSection.tsx, toga25-supply/src/pages/SalesOrders/view/SalesOrderRecordModalLayout/helpers/getAdminNotes.ts, toga25-supply/src/pages/SalesOrders/view/SalesOrderRecordModalLayout/helpers/index.ts, toga25-supply/src/surface/evaluateSurfaceRule.ts, toga25-supply/src/surface/resolveElementState.ts, toga25-supply/src/pages/SalesOrders/helpers/evaluateEnableRule.ts, toga25-supply/src/pages/SalesOrders/hooks/useSalesOrderRowRecordState.ts, toga25-supply/src/surface/actionRegistry.ts, toga25-supply/src/surface/componentRegistry.tsx, toga25-supply/src/surface/SurfaceActionBar.tsx, toga25-supply/src/surface/SurfaceSection.tsx, toga25-supply/src/surface/resolve.ts, toga25-supply/src/surface/types.ts, toga25-supply/src/surface/index.ts, toga25-supply/src/pages/Login/LoginPage.tsx, toga25-supply/src/pages/SalesOrders/SalesOrders.tsx, toga25-supply/src/pages/SalesOrders/view/SurfaceRowActions.tsx, toga25-supply/src/pages/SalesOrders/hooks/useSalesOrderVip.ts, toga25-supply/src/pages/SalesOrders/view/SalesOrderRecordModalLayout/view/sections/SalesOrderTopBar.tsx, toga25-supply/src/pages/Items/ItemsPage.tsx, toga25-supply/src/pages/Items/viewModel/useItemsPageViewModel.tsx, toga25-supply/src/pages/VendorItems/VendorItemsPage.tsx, toga25-supply/src/pages/VendorItems/viewModel/useVendorItemsPageViewModel.tsx, toga25-supply/src/pages/Inventory/Inventory.tsx, toga25-supply/src/pages/Inventory/viewModel/useInventoryPageViewModel.tsx, toga25-supply/src/pages/Inventory/viewModel/FIELDS/index.ts, toga25-supply/src/fieldsConfig/index.ts, toga25-supply/src/layout/ItemRecordModalLayout/helpers/surfaceBundleToItemFields.ts, toga25-supply/src/layout/ItemRecordModalLayout/helpers/index.ts, toga25-supply/src/layout/ItemRecordModalLayout/viewModel/useItemRecordModalViewModel.tsx, toga25-supply/src/layout/ItemRecordModalLayout/ItemRecordModalLayout.tsx, toga25-supply/src/layout/ItemRecordModalLayout/components/ItemRecordView.tsx, toga25-supply/src/surface/useStatusColors.ts, toga25-supply/src/surface/SurfaceHeader.tsx, toga25-supply/src/pages/SalesOrders/view/SalesOrderApprovalModalsLayout/helpers/surfaceBundlesToDecisionFields.ts, toga25-supply/src/pages/SalesOrders/view/SalesOrderApprovalModalsLayout/viewModel/useApprovalModalViewModel.tsx |
|
|
14
15
|
| [Talos Integration (AppLayout host + adapter wiring)](features/talos-integration.md) | toga25-supply is the first host of the shared blox [Talos assistant](../../toga-blox/features/talos-assistant.md). | toga25-supply/src/layout/AppLayout/AppLayout.tsx, toga25-supply/src/components/Header/Header.tsx, toga25-supply/src/components/Header/Header.module.css, toga25-supply/src/index.css, toga25-supply/src/assets/talos-owl.png |
|
|
15
16
|
| [AWS Amplify Multi-Environment Deployment](workflows/amplify-deployment.md) | How `toga25-supply` deploys to **all** of its environments on AWS Amplify from a **single shared `amplify.yml`**. | toga25-supply/amplify.yml, toga25-supply/src/api/api.ts, toga25-supply/src/hooks/useAuthenticationFlow.ts, toga25-supply/vite.config.ts, toga25-supply/package.json |
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: SSO redirect & public-vs-user session gating (useAuthenticationFlow)
|
|
3
|
+
framework: "2.0"
|
|
4
|
+
repo: toga25-supply
|
|
5
|
+
project: TOGa 2.5 Supply
|
|
6
|
+
client: shared
|
|
7
|
+
type: feature
|
|
8
|
+
status: active
|
|
9
|
+
updated: 2026-08-27
|
|
10
|
+
owners: [apeterson]
|
|
11
|
+
files:
|
|
12
|
+
- toga25-supply/src/hooks/useAuthenticationFlow.ts
|
|
13
|
+
- toga25-supply/src/routes.tsx
|
|
14
|
+
- toga25-supply/src/contexts/AuthContext.tsx
|
|
15
|
+
- toga25-supply/src/api/api.ts
|
|
16
|
+
related:
|
|
17
|
+
- ../../toga-blox/features/api-client.md
|
|
18
|
+
- ../../api2/features/encrypted-user-uuid-auth-handoff.md
|
|
19
|
+
- ../../../standards/frontend.md
|
|
20
|
+
---
|
|
21
|
+
|
|
22
|
+
## Summary
|
|
23
|
+
|
|
24
|
+
How 2.5 Supply decides, on every navigation, whether an anonymous visitor should be bounced to
|
|
25
|
+
their client's SSO IdP instead of the local `/login` form. `useAuthenticationFlow` resolves the
|
|
26
|
+
client from the current domain, asks api2 for that client's `singleSignOnServiceUrl`, and
|
|
27
|
+
redirects there if one exists. This mirrors `toga2-supply`, which invokes the equivalent flow from
|
|
28
|
+
`App.tsx` on every navigation.
|
|
29
|
+
|
|
30
|
+
Confirmed working in production for Compass USA / Compass Canada as of 2026-08-27.
|
|
31
|
+
|
|
32
|
+
## How it works
|
|
33
|
+
|
|
34
|
+
1. **The hook must be mounted inside the router.** It uses `useNavigate` / `useLocation`, so it is
|
|
35
|
+
mounted as an `AuthenticationFlowRoot` **route element** that wraps *both* the `PrivateRoute`
|
|
36
|
+
branch and `/login` in `routes.tsx`. Mounting it above the router, or not at all, silently
|
|
37
|
+
disables SSO.
|
|
38
|
+
2. **Resolve the SSO URL from the scripted API, not from a login helper.** The correct call is
|
|
39
|
+
`GET /client-authentications/singleSignOnServiceUrl` with `{ uuid, domainUuid }`, reading
|
|
40
|
+
`data.clientAuthentications.singleSignOnServiceUrl`. A typed `SsoUrlResponse` interface holds
|
|
41
|
+
that shape so a mismatch is a compile error.
|
|
42
|
+
3. **Redirect or fall through.** A non-null URL → `window.location` to the IdP; null → local
|
|
43
|
+
`/login`.
|
|
44
|
+
4. **Gate on a real user session, not on a token.** The "is this visitor already signed in?" check
|
|
45
|
+
reads `localStorage.getItem("user")` — **not** `accessToken` (see gotchas).
|
|
46
|
+
|
|
47
|
+
## Gotchas
|
|
48
|
+
|
|
49
|
+
- **⚠ blox exports a `handleClientAuthentication(uuid)` that is NOT `toga2-supply`'s util of the
|
|
50
|
+
same name.** This is a **name collision between a shared-lib export and an app-local helper**,
|
|
51
|
+
and it was the root cause of SSO never firing here. blox's version `POST`s
|
|
52
|
+
`/auth/encrypted-user-uuid` with an **empty payload** (`{client:"",user:""}`), writes
|
|
53
|
+
access/refresh tokens to `localStorage`, and returns a `/users` payload that has **no
|
|
54
|
+
`singleSignOnServiceUrl` property at all**. The old code did
|
|
55
|
+
`(ssoData as any)?.singleSignOnServiceUrl ?? null`, so the result was **always null under any
|
|
56
|
+
data** and always fell through to `/login`. The `as any` cast is precisely what let this ship —
|
|
57
|
+
when consuming a shared-lib function whose name matches a local one, type the response and
|
|
58
|
+
verify the endpoint it actually calls.
|
|
59
|
+
- **⚠ A token in `localStorage` does not mean a user is signed in.** blox's `fetchPublicToken`
|
|
60
|
+
writes `accessToken` **and** `refreshToken` for *logged-out* requests. Gating on
|
|
61
|
+
`localStorage.getItem("accessToken")` (which both `hasTokenInStorage` in the hook and
|
|
62
|
+
`AuthContext`'s initial `isAuthenticated` did) makes any visitor who has already touched the API
|
|
63
|
+
look authenticated, so SSO is **skipped**. Symptom is maddeningly intermittent: clean storage →
|
|
64
|
+
SSO fires; any prior request → no SSO. **`localStorage.getItem("user")` is the correct
|
|
65
|
+
discriminator** — only a real session writes `"user"`, and blox's own axios request interceptor
|
|
66
|
+
already uses exactly that distinction (`userString ? accessToken : fetchPublicToken(baseURL)`).
|
|
67
|
+
*(Status: fix written but **uncommitted** as of 2026-08-27.)*
|
|
68
|
+
- **Tokens reappearing right after logout are not a surviving session.** They are a **fresh public
|
|
69
|
+
token** minted by `fetchPublicToken` on the next request. No app-side logout code can prevent
|
|
70
|
+
this while blox persists public tokens to `localStorage`.
|
|
71
|
+
- **⚠ Known live defect: `/login` is currently unreachable on an SSO client.** blox's
|
|
72
|
+
`performLogout` hardcodes `window.location.href = "/"`, and `/` is exactly what triggers the SSO
|
|
73
|
+
redirect — so signing out bounces straight back to the IdP. A fix landing on `/login` from both
|
|
74
|
+
paths (`AuthContext.logout` no longer calling `performLogout`, **plus** an `onLogout` passed
|
|
75
|
+
through `api.ts` so blox's 401-interceptor path matches — **both** are required, since the Logout
|
|
76
|
+
button goes through `AuthContext` and an expired token goes through the interceptor) was made in
|
|
77
|
+
`e886afb` and then **reverted at the developer's request** in `09f8dd5` (a revert, not a history
|
|
78
|
+
rewrite, because `e886afb` was already pushed and merged). The revert was **not pushed** as of
|
|
79
|
+
2026-08-27. The underlying defect is blox's hardcoded `"/"`, and the app-side workaround was
|
|
80
|
+
rejected — so this is **blox-authoring work**.
|
|
81
|
+
- **A `?local=1` sessionStorage opt-out is unnecessary — don't rebuild it.** It was built and
|
|
82
|
+
discarded: once you submit the login form you hold a real token, so SSO stops firing on its own.
|
|
83
|
+
|
|
84
|
+
### Diagnosing whether a hook is even running: grep the deployed bundle
|
|
85
|
+
|
|
86
|
+
Search the built bundle for the endpoint **string literals** the code should contain. Before the
|
|
87
|
+
fix: `client-authentications` 0 occurrences, `encrypted-user-uuid` 1. After: inverted. Because an
|
|
88
|
+
unmounted hook is tree-shaken out entirely, the absence of its literals also proves it is never
|
|
89
|
+
mounted — a cheap way to confirm a deploy actually carries the change.
|
|
90
|
+
|
|
91
|
+
## Change history
|
|
92
|
+
- 2026-08-27 — Fixed SSO never redirecting: mounted the previously-callerless
|
|
93
|
+
`useAuthenticationFlow` as an in-router `AuthenticationFlowRoot`, and replaced the call to blox's
|
|
94
|
+
same-named `handleClientAuthentication` (empty-payload `/auth/encrypted-user-uuid`, no
|
|
95
|
+
`singleSignOnServiceUrl` in its response) with the scripted
|
|
96
|
+
`GET /client-authentications/singleSignOnServiceUrl`, typed via `SsoUrlResponse` instead of
|
|
97
|
+
`as any`. Committed, pushed, confirmed working. Also recorded (fix **uncommitted**) that public
|
|
98
|
+
tokens are indistinguishable from user tokens when gating on `accessToken` — gate on `"user"` —
|
|
99
|
+
and that the logout→`/`→SSO loop, briefly fixed in `e886afb`, was **reverted** (`09f8dd5`,
|
|
100
|
+
unpushed) so `/login` is currently unreachable on Compass. (apeterson)
|
|
@@ -6,8 +6,8 @@ project: TOGa 2.5 Supply
|
|
|
6
6
|
client: shared
|
|
7
7
|
type: workflow
|
|
8
8
|
status: active
|
|
9
|
-
updated: 2026-
|
|
10
|
-
owners: [jcardinal]
|
|
9
|
+
updated: 2026-08-27
|
|
10
|
+
owners: [jcardinal, apeterson]
|
|
11
11
|
files:
|
|
12
12
|
- toga25-supply/amplify.yml
|
|
13
13
|
- toga25-supply/src/api/api.ts
|
|
@@ -111,8 +111,32 @@ and are the single source of truth for environment API endpoints.
|
|
|
111
111
|
build is required. The `.env.<mode>` files are the single source of truth.
|
|
112
112
|
- **`npm run build` ships production everywhere** — always build with an explicit
|
|
113
113
|
`--mode "$VITE_MODE"`; the per-mode `package.json` scripts are dev servers, not builds.
|
|
114
|
+
- **⚠ A symlinked shared-lib checkout MASKS both a missing dependency declaration and missing peer
|
|
115
|
+
deps.** Verified 2026-08-27: `_production` did **not** list `@agilant/toga-blox` in
|
|
116
|
+
`package.json` at all and had **no lock entry**, despite ~200 imports across `src/`. It only
|
|
117
|
+
resolved locally because `node_modules/@agilant/toga-blox` was a **symlink to the local checkout**
|
|
118
|
+
(`toga-blox-npm`), so resolution fell through to the linked package's own `node_modules`. The
|
|
119
|
+
Amplify `preBuild` runs **`npm ci`**, which would have failed outright. Installing blox properly
|
|
120
|
+
from the registry then surfaced **four** peers the symlink had been hiding — because
|
|
121
|
+
`legacy-peer-deps=true` means npm does **not** auto-install peers, and blox's barrel import pulls
|
|
122
|
+
in components the app never renders:
|
|
123
|
+
`@fortawesome/free-solid-svg-icons@^6.7.2` (blox `Input.tsx` / `faCircleInfo`),
|
|
124
|
+
`react-multi-select-component@^4.3.4`, `react-table@^7.8.0`, `react-table-sticky@^1.1.3`
|
|
125
|
+
(the legacy v7 table stack). All three `react-table` v7 packages were **already** declared at
|
|
126
|
+
identical versions on `_sandbox-client` — `_production` was the outlier. Fixed in `92644c8`
|
|
127
|
+
(pushed), verified with a clean vite build (1761 modules).
|
|
128
|
+
- **The audit that catches the above — reusable for any blox consumer.** Enumerate the shared lib's
|
|
129
|
+
`peerDependencies` from `node_modules/<pkg>/package.json`, then check each one for a **real
|
|
130
|
+
directory** (not a symlink passthrough) in the *app's* `node_modules`. Do this whenever a repo
|
|
131
|
+
has ever been developed against a linked local checkout.
|
|
114
132
|
|
|
115
133
|
## Change history
|
|
134
|
+
- 2026-08-27 — Fixed `_production` never declaring `@agilant/toga-blox` (no dependency entry, no
|
|
135
|
+
lock entry, ~200 imports) — it resolved only via a local **symlink**, and `npm ci` in `preBuild`
|
|
136
|
+
would have failed. Added blox plus the four peers the symlink had masked
|
|
137
|
+
(`@fortawesome/free-solid-svg-icons`, `react-multi-select-component`, `react-table`,
|
|
138
|
+
`react-table-sticky`), which `_sandbox-client` already carried. Committed `92644c8`, pushed.
|
|
139
|
+
Recorded the reusable peer-audit procedure. (apeterson)
|
|
116
140
|
- 2026-07-17 — Documented the single shared `amplify.yml` multi-environment deploy: `_<mode>`
|
|
117
141
|
branch → `--mode` derivation, fail-loud guardrails, runtime `import.meta.env.MODE` identity,
|
|
118
142
|
and the critical never-set-`VITE_API`-in-the-Amplify-console gotcha (console vars override
|
|
@@ -5,7 +5,7 @@ project: _Underscore
|
|
|
5
5
|
client: shared
|
|
6
6
|
type: standard
|
|
7
7
|
status: active
|
|
8
|
-
updated: 2026-08-
|
|
8
|
+
updated: 2026-08-27
|
|
9
9
|
owners: [jcardinal, apeterson]
|
|
10
10
|
files: []
|
|
11
11
|
related:
|
|
@@ -422,6 +422,15 @@ on the back end. A consuming app **MUST** meet every requirement below.
|
|
|
422
422
|
Verified 2026-08-26: `toga2-commerce` violates this with `"^1.0.322-sandbox-client.117"`. Also audit
|
|
423
423
|
for **pin drift** between branches (commerce's `TRUE-80707` was 11 versions behind `_sandbox-client`).
|
|
424
424
|
|
|
425
|
+
**Nuance (verified 2026-08-27): a caret on a PRERELEASE is inert, not floating.** npm only matches
|
|
426
|
+
prereleases sharing the same `major.minor.patch`, so `^1.0.322-sandbox-client.117` resolves to
|
|
427
|
+
**exactly** `1.0.322-sandbox-client.117` (confirmed with `semver.maxSatisfying`). It is still a
|
|
428
|
+
violation — it misleads every reader about intent — but do **not** justify it as a drift risk. The
|
|
429
|
+
real floating risk lives elsewhere: `toga2-commerce`'s `amplify.yml` runs
|
|
430
|
+
`npm install "@agilant/toga-blox@$MODE"`, which **overwrites the manifest pin at build time**, so
|
|
431
|
+
commerce's deployed build always takes the channel head and its pin governs local dev only.
|
|
432
|
+
Commerce's caret is now the exact `1.1.1-production.139` (working-tree only on `TRUE-80707`).
|
|
433
|
+
|
|
425
434
|
**(b) Declare blox's peer deps that do not hoist into your app.** blox lists them as peers, so npm
|
|
426
435
|
will not necessarily install them for you: verified 2026-08-18 they include `react-hook-form`,
|
|
427
436
|
`@tanstack/react-query`, `@tanstack/react-table`, `framer-motion`, and `axios` (pinned exactly by
|
|
@@ -597,9 +606,24 @@ Work **around** these shared-lib defects; do **not** imitate them in app code:
|
|
|
597
606
|
- A **~85-prop className-string styling contract on `BaseInput`** — verbose by design; drive it from
|
|
598
607
|
tokens, not per-instance strings.
|
|
599
608
|
- **`strict: false` in blox's own tsconfig**, and **React-18-only** peers.
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
609
|
+
- **⚠ `handleClientAuthentication(uuid)` collides by name with app-local SSO helpers.** blox's
|
|
610
|
+
version POSTs `/auth/encrypted-user-uuid` with an empty payload and returns a `/users` payload
|
|
611
|
+
with **no `singleSignOnServiceUrl`** — `toga2-supply` has an unrelated util of the same name.
|
|
612
|
+
Cost `toga25-supply` a silently-dead SSO redirect, masked by an `as any`. **Type shared-lib
|
|
613
|
+
responses; never `as any` a cross-package result.**
|
|
614
|
+
- **⚠ `fetchPublicToken` persists the public token to `localStorage`**, so gating auth on
|
|
615
|
+
`accessToken` makes logged-out visitors look signed in (intermittent-looking bugs: clean storage
|
|
616
|
+
works, a warm one doesn't). Gate on the **`user`** key — blox's own interceptor already does.
|
|
617
|
+
- **⚠ `performLogout` hardcodes `window.location.href = "/"`.** On an SSO client `/`
|
|
618
|
+
re-triggers the IdP, so logout loops and `/login` is unreachable. An app-side override needs
|
|
619
|
+
**both** the logout action and `createAxiosInstance({ onLogout })` (the 401 path is separate) —
|
|
620
|
+
but that workaround was rejected in supply, so this one is blocking real behavior until fixed in
|
|
621
|
+
blox.
|
|
622
|
+
|
|
623
|
+
**Fixing these is blox-authoring work, tracked separately — see
|
|
624
|
+
[blox authoring defect backlog](../apps/toga-blox/features/blox-authoring-defects.md), which
|
|
625
|
+
carries the mechanism, consumer symptom and fix direction for each.** This is **not** app work. In a
|
|
626
|
+
consuming app, guard against them and move on.
|
|
603
627
|
|
|
604
628
|
---
|
|
605
629
|
|
|
@@ -640,6 +664,13 @@ app work.** In a consuming app, guard against them and move on.
|
|
|
640
664
|
|
|
641
665
|
## Change history
|
|
642
666
|
|
|
667
|
+
- 2026-08-27 — §13(a): corrected the caret rationale — a caret on a **prerelease is inert**, not
|
|
668
|
+
floating; the real floating risk is `toga2-commerce`'s `amplify.yml` overwriting the manifest pin
|
|
669
|
+
at build time. §22: added three blox auth gotchas found debugging toga25-supply SSO
|
|
670
|
+
(`handleClientAuthentication` name collision, `fetchPublicToken` persisting the public token to
|
|
671
|
+
`localStorage`, `performLogout`'s hardcoded `"/"`), and resolved the dangling
|
|
672
|
+
"a future `blox-authoring.md`" pointer to the new
|
|
673
|
+
[blox authoring defect backlog](../apps/toga-blox/features/blox-authoring-defects.md). (apeterson)
|
|
643
674
|
- 2026-08-26 — Recorded commerce's missing `resolve.dedupe` (+ the blox-symlink second-React
|
|
644
675
|
hazard), its caret blox pin and cross-branch pin drift, the barrel-import missing-peer trigger,
|
|
645
676
|
and the removal of blox's phantom `react-router-dom` peer. The blox half is now merged and
|
package/knowledge/INDEX.md
CHANGED
|
@@ -21,7 +21,7 @@ _Auto-generated by `knowledge.js index`. Do not hand-edit._
|
|
|
21
21
|
- **_underscore** (_Underscore) _(framework core)_ — 69 doc(s) → [2.0/apps/_underscore/INDEX.md](2.0/apps/_underscore/INDEX.md)
|
|
22
22
|
- **worker2** (Worker) — 57 doc(s) → [2.0/apps/worker2/INDEX.md](2.0/apps/worker2/INDEX.md)
|
|
23
23
|
- **api2** (API) — 25 doc(s) → [2.0/apps/api2/INDEX.md](2.0/apps/api2/INDEX.md)
|
|
24
|
-
- **dbchanges2** (Database Changes) _(framework core)_ —
|
|
24
|
+
- **dbchanges2** (Database Changes) _(framework core)_ — 12 doc(s) → [2.0/apps/dbchanges2/INDEX.md](2.0/apps/dbchanges2/INDEX.md)
|
|
25
25
|
- **toga2-supply** (TOGa Supply) — 9 doc(s) → [2.0/apps/toga2-supply/INDEX.md](2.0/apps/toga2-supply/INDEX.md)
|
|
26
26
|
- **saml** (SAML SSO Gateway) — 4 doc(s) → [2.0/apps/saml/INDEX.md](2.0/apps/saml/INDEX.md)
|
|
27
27
|
- **toga2-view** (TOGa View Frontend) — 12 doc(s) → [2.0/apps/toga2-view/INDEX.md](2.0/apps/toga2-view/INDEX.md)
|
|
@@ -30,8 +30,8 @@ _Auto-generated by `knowledge.js index`. Do not hand-edit._
|
|
|
30
30
|
- **voice-to-voice** (TOGa Voice) — 4 doc(s) → [2.0/apps/voice-to-voice/INDEX.md](2.0/apps/voice-to-voice/INDEX.md)
|
|
31
31
|
- **ai-bdr** (AI-BDR) — 13 doc(s) → [2.0/apps/ai-bdr/INDEX.md](2.0/apps/ai-bdr/INDEX.md)
|
|
32
32
|
- **toga2-commerce** (TOGa Commerce) — 19 doc(s) → [2.0/apps/toga2-commerce/INDEX.md](2.0/apps/toga2-commerce/INDEX.md)
|
|
33
|
-
- **toga25-supply** (TOGa 2.5 Supply) —
|
|
34
|
-
- **toga-blox** (TOGa Blox) —
|
|
33
|
+
- **toga25-supply** (TOGa 2.5 Supply) — 13 doc(s) → [2.0/apps/toga25-supply/INDEX.md](2.0/apps/toga25-supply/INDEX.md)
|
|
34
|
+
- **toga-blox** (TOGa Blox) — 13 doc(s) → [2.0/apps/toga-blox/INDEX.md](2.0/apps/toga-blox/INDEX.md)
|
|
35
35
|
- **bdr** (BDR) — 0 doc(s) → [2.0/apps/bdr/INDEX.md](2.0/apps/bdr/INDEX.md)
|
|
36
36
|
|
|
37
37
|
## standalone framework
|
|
@@ -21,6 +21,7 @@
|
|
|
21
21
|
| [Stranded Approval Reassignment (repointing approvals off dead duplicate Compass Users rows)](features/stranded-approval-reassignment.md) | 2.0 | A Compass employee who leaves and comes back after more than `CONTACT_UNLINK_GRACE_DAYS` (5) is **inserted as a brand-new `Users` row** by the PEOPLE importer i | _underscore/Model/Compass/ApprovalDecision.php, worker2/Worker/Client/Compass/ApprovalReassignment.php, worker2/Worker/Client/Compass/PeopleFile.php |
|
|
22
22
|
| [Compass USA](profile.md) | 2.0 | Compass USA is a TOGA client running a multi-tier supply-chain commerce operation. | |
|
|
23
23
|
| [Compass Cross-Kit Bundle Corruption — Detection & Repair](workflows/cross-kit-bundle-corruption.md) | 2.0 | A frontend regression in `toga2-commerce`'s edit-order bundle submission mis-attributed bundle (kit) line items and **fees/warranties** to the **wrong kit**, pe | src/api/syncSalesOrderItemsFromLocalStorageCartToApi.ts |
|
|
24
|
+
| [Granting a Compass user the same toga25-supply navigation as another user (role vs. SurfaceOverride)](workflows/granting-navigation-access.md) | 2.0 | "Give user X the same menu items user Y has" is the **navigation analog** of the recurring [bundle/persona grant](./granting-persona-bundle-access.md) request. | dbchanges2/Client_Compass/2026-08-27a - GrantAgilantAdministratorsRoleToInternalUser.sql |
|
|
24
25
|
| [Granting a Compass user access to a bundle (persona grant) and the SuperUser role](workflows/granting-persona-bundle-access.md) | 2.0 | "Give user X sight of kit N" is a **recurring** Compass request, usually paired with "and make them a super user". | dbchanges2/Client_Compass/2026-08-26a - CompassCreativeStudioPersona.sql |
|
|
25
26
|
| [Compass Office Depot Duplicate PO-Line Cleanup (dual-catalog SKU)](workflows/odp-duplicate-po-line-cleanup.md) | 1.0 | The NetSuite fulfillment-sales-order importer duplicated Office Depot purchase-order lines on Compass USA because it reconciled the fulfillment SO against the e | library/app/api/toga2.php, dbchanges2/Client_Compass/2026-07-22a - CleanupOfficeDepotDuplicatePurchaseOrderItems.sql |
|
|
26
27
|
| [Recovering a Lost Compass ODP EDI 850 Import (re-drop from Logs.FileLog)](workflows/odp-edi-import-recovery.md) | 1.0 | How to recover a Compass **Office Depot EDI 850** import that failed partway — the case where cron **3a** created the ODP SalesOrder header, the follow-up item | worker/crons/toga2/compass/workflow/3a_import_office_depot_purchase_orders.php, worker/crons/toga2/compass/workflow/5_create_netsuite_sales_orders_from_office_depot_purchase_orders.php, worker/schedules/cron.worker.sync.json |
|
|
@@ -18,7 +18,7 @@ project: _Underscore
|
|
|
18
18
|
client: compass-usa
|
|
19
19
|
type: profile
|
|
20
20
|
status: active
|
|
21
|
-
updated: 2026-08-
|
|
21
|
+
updated: 2026-08-27
|
|
22
22
|
owners: [jcardinal, bala, tcox, apeterson, dfranks]
|
|
23
23
|
files: []
|
|
24
24
|
related:
|
|
@@ -28,6 +28,7 @@ related:
|
|
|
28
28
|
- workflows/persona-refactor-migration.md
|
|
29
29
|
- workflows/persona-population-env-comparison.md
|
|
30
30
|
- workflows/granting-persona-bundle-access.md
|
|
31
|
+
- workflows/granting-navigation-access.md
|
|
31
32
|
- features/mits-sales-order-transmission-alerting.md
|
|
32
33
|
- features/asn-to-item-fulfillment.md
|
|
33
34
|
- features/order-fulfillment-status-per-line.md
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Granting a Compass user the same toga25-supply navigation as another user (role vs. SurfaceOverride)
|
|
3
|
+
framework: "2.0"
|
|
4
|
+
repo: dbchanges2
|
|
5
|
+
project: Database Changes
|
|
6
|
+
client: compass-usa
|
|
7
|
+
type: workflow
|
|
8
|
+
status: active
|
|
9
|
+
updated: 2026-08-27
|
|
10
|
+
owners: [apeterson]
|
|
11
|
+
files:
|
|
12
|
+
- dbchanges2/Client_Compass/2026-08-27a - GrantAgilantAdministratorsRoleToInternalUser.sql
|
|
13
|
+
related:
|
|
14
|
+
- ./granting-persona-bundle-access.md
|
|
15
|
+
- ../profile.md
|
|
16
|
+
- ../../../2.0/apps/_underscore/features/surface-resolver.md
|
|
17
|
+
- ../../../2.0/apps/dbchanges2/features/surface-layer-schema.md
|
|
18
|
+
- ../../../2.0/apps/toga25-supply/features/surface-frontend.md
|
|
19
|
+
- ../../../2.0/apps/dbchanges2/features/rerunnable-additive-inserts.md
|
|
20
|
+
- ../../../2.0/apps/_underscore/features/acl-permission-chain.md
|
|
21
|
+
---
|
|
22
|
+
|
|
23
|
+
## Summary
|
|
24
|
+
|
|
25
|
+
"Give user X the same menu items user Y has" is the **navigation analog** of the recurring
|
|
26
|
+
[bundle/persona grant](./granting-persona-bundle-access.md) request. On Compass it is *not* an ACL
|
|
27
|
+
question at all — the `navigation` TAB_STRIP surface is gated **purely** by role-scoped
|
|
28
|
+
`Client_Compass.SurfaceOverrides` rows. Nothing about `AclActionPermissions` participates.
|
|
29
|
+
|
|
30
|
+
The trap is scope. The two users' role sets will differ by several roles, but usually only **one** of
|
|
31
|
+
those roles carries navigation overrides. Granting the whole role diff hands over far more than
|
|
32
|
+
navigation.
|
|
33
|
+
|
|
34
|
+
All facts below were verified read-only against live `Client_Compass` on **2026-08-27**.
|
|
35
|
+
|
|
36
|
+
## How Compass navigation is actually gated
|
|
37
|
+
|
|
38
|
+
Two facts, both verified, that together mean the surface-override table is the *only* lever:
|
|
39
|
+
|
|
40
|
+
1. **Every one of the six nav elements is seeded Core-base `isVisible = 0`.** Nothing is visible by
|
|
41
|
+
default; every visible menu item exists because some role turned it on.
|
|
42
|
+
2. **Every one of them has `aclActionId = NULL`.** The element-level ACL gate inside
|
|
43
|
+
`resolve()` (`_Model_Client_AclActionPermission` → `aclActionId`, see
|
|
44
|
+
[Surface Resolver](../../../2.0/apps/_underscore/features/surface-resolver.md)) is therefore a
|
|
45
|
+
**no-op for navigation**. Do not go looking for a missing `AclActionPermissions` row; there was
|
|
46
|
+
never one to find.
|
|
47
|
+
|
|
48
|
+
So visibility = exactly one thing: a `Client_Compass.SurfaceOverrides` row with
|
|
49
|
+
`attribute = 'IS_VISIBLE'`, `value = 1`, scoped to a `roleId` the user holds.
|
|
50
|
+
|
|
51
|
+
### `Core.SurfaceElements` ids for surface slug `navigation`
|
|
52
|
+
|
|
53
|
+
| Element id | `config.path` | Nav item | `sortOrder` |
|
|
54
|
+
|---|---|---|---|
|
|
55
|
+
| 109 | `sales-orders` | Sales Orders | 10 |
|
|
56
|
+
| 110 | `inventory` | Inventory | 20 |
|
|
57
|
+
| 111 | `items` | Items | 30 |
|
|
58
|
+
| 112 | `vendor-items` | Vendor Items | 40 |
|
|
59
|
+
| 113 | `bundles` | Bundles | 50 |
|
|
60
|
+
| 114 | `service-requests` | Service Requests | 60 |
|
|
61
|
+
|
|
62
|
+
> **⚠ These ids sit OUTSIDE the team-reserved blocks.** The reserved-id table in
|
|
63
|
+
> [Surface Resolver](../../../2.0/apps/_underscore/features/surface-resolver.md) and
|
|
64
|
+
> [surface-layer-schema](../../../2.0/apps/dbchanges2/features/surface-layer-schema.md) lists
|
|
65
|
+
> `Core.Surfaces` **41–43** / `Core.SurfaceElements` **125–146**. The `navigation` surface predates
|
|
66
|
+
> those blocks and lives at elements **109–114**. Read the reserved blocks as "the decision-surface
|
|
67
|
+
> reseed", not as an inventory of all surface ids.
|
|
68
|
+
|
|
69
|
+
### The Compass override map (all `IS_VISIBLE`, `value = 1`, `personaId` NULL, `languageId` NULL)
|
|
70
|
+
|
|
71
|
+
| Element | Roles granting visibility |
|
|
72
|
+
|---|---|
|
|
73
|
+
| 109 Sales Orders | **4** (Admin), **8** (Manager), **9** (Agilant - Administrators) |
|
|
74
|
+
| 111 Items | **9** only |
|
|
75
|
+
| 112 Vendor Items | **9** only |
|
|
76
|
+
| 113 Bundles | **9** only |
|
|
77
|
+
| 110 Inventory | **none — no override row for any role** |
|
|
78
|
+
| 114 Service Requests | **none — no override row for any role** |
|
|
79
|
+
|
|
80
|
+
## The diagnostic recipe ("same navigation as user Y")
|
|
81
|
+
|
|
82
|
+
1. **Diff the two users' `Users_Roles` rows.** Here 289067 held roles 1, 4, 7, 12; the reference user
|
|
83
|
+
127046 held 1, 4, 5, 7, 9, 12, 15, 16 — a **four-role** gap (5, 9, 15, 16).
|
|
84
|
+
2. **Intersect that diff against the roles that actually appear in the nav override map.** Only role
|
|
85
|
+
**9** did. Roles 5, 15 and 16 carry **no** nav overrides and were irrelevant to the request. A
|
|
86
|
+
four-role gap collapsed to a one-role decision. **Never grant the whole role diff.**
|
|
87
|
+
3. **Identify whose bundle you are looking at by counting visible elements.** If a developer pastes a
|
|
88
|
+
surface bundle without saying which user it came from, match the visible set against the override
|
|
89
|
+
map. A bundle showing **4** visible items (sales-orders / items / vendor-items / bundles) is role
|
|
90
|
+
9's exact signature, so it belonged to 127046 — not to 289067, who holds only role 4 of the
|
|
91
|
+
nav-granting roles and therefore sees **Sales Orders alone**. Settling attribution first avoids
|
|
92
|
+
diagnosing the wrong user.
|
|
93
|
+
4. **Pick the lever** (below), write the migration, hand it to the developer to run.
|
|
94
|
+
|
|
95
|
+
### Two levers, and the tradeoff
|
|
96
|
+
|
|
97
|
+
| Lever | Blast radius | Use when |
|
|
98
|
+
|---|---|---|
|
|
99
|
+
| **(a) Grant the missing role** — one additive `Users_Roles` row | affects only this user, but confers **all** of that role's record-CRUD, field and action grants, not just nav | the user genuinely *is* that persona |
|
|
100
|
+
| **(b) Add role-scoped `SurfaceOverrides` rows** for a role the user already holds | surgical on nav, but widens nav for **every holder** of that role | the role is narrow and the nav change is intended tenant-wide |
|
|
101
|
+
|
|
102
|
+
Lever (b) against a broad role such as 4 (Admin) is a **tenant-wide behavior change** dressed up as a
|
|
103
|
+
per-user fix. For user 289067 the developer confirmed an internal Agilant user, so **(a)** was
|
|
104
|
+
correct: grant `Agilant - Administrators` additively.
|
|
105
|
+
|
|
106
|
+
## The migration
|
|
107
|
+
|
|
108
|
+
`dbchanges2/Client_Compass/2026-08-27a - GrantAgilantAdministratorsRoleToInternalUser.sql` — additive
|
|
109
|
+
`Users_Roles` insert for user 289067; roles 1, 4, 7, 12 are left intact.
|
|
110
|
+
|
|
111
|
+
Three house-style points it demonstrates:
|
|
112
|
+
|
|
113
|
+
- **Resolve the role by name subselect, not a hardcoded id.** `Roles.id` differs per client DB, so the
|
|
114
|
+
file matches `WHERE r.name = 'Agilant - Administrators'`. The subselect stays entirely inside
|
|
115
|
+
`Client_Compass`, so the file names only tables in its own database and is cluster-safe.
|
|
116
|
+
- **Wrap the idempotency guard in a derived table.** The guard reads the INSERT's own target table,
|
|
117
|
+
which trips MySQL error 1093 (and, unwrapped, can see its own new row mid-statement):
|
|
118
|
+
|
|
119
|
+
```sql
|
|
120
|
+
AND r.id NOT IN (
|
|
121
|
+
SELECT roleId FROM (
|
|
122
|
+
SELECT DISTINCT roleId FROM Users_Roles WHERE userId = 289067
|
|
123
|
+
) AS existing
|
|
124
|
+
)
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
Same dodge documented in
|
|
128
|
+
[Re-runnable additive INSERTs](../../../2.0/apps/dbchanges2/features/rerunnable-additive-inserts.md).
|
|
129
|
+
- **Pre-generate the v4 UUID as a literal** — never `UUID()`.
|
|
130
|
+
|
|
131
|
+
## Gotchas
|
|
132
|
+
|
|
133
|
+
- **⚠ Inventory (110) and Service Requests (114) are unreachable for the ENTIRE Compass tenant.** No
|
|
134
|
+
role — not even Agilant - Administrators — has an `IS_VISIBLE` override for them, and Core base is
|
|
135
|
+
`0`. Copying "the same nav as the admin user" will *not* surface them. This was found, not fixed, on
|
|
136
|
+
2026-08-27 and needs its own ticket.
|
|
137
|
+
- **Role permissions resolve as a union**, so an additive `Users_Roles` row never takes access away —
|
|
138
|
+
see [ACL Permission Chain](../../../2.0/apps/_underscore/features/acl-permission-chain.md). The flip
|
|
139
|
+
side is that it also grants everything else the role carries.
|
|
140
|
+
- **Verifying the change requires a LOGOUT, not a reload.** Surface meta is cached
|
|
141
|
+
`staleTime`/`gcTime` `Infinity` **and persisted to `localStorage`** under
|
|
142
|
+
`supply-chain-query-cache`, so a hard reload restores the stale bundle and proves nothing. There is
|
|
143
|
+
no server-side surface cache. Full detail in
|
|
144
|
+
[Surface Frontend](../../../2.0/apps/toga25-supply/features/surface-frontend.md).
|
|
145
|
+
- **`aclActionId` is NULL on all six nav elements** — resist the instinct to reach for
|
|
146
|
+
`AclActionPermissions` when a menu item is missing. It is always a `SurfaceOverrides` row.
|
|
147
|
+
|
|
148
|
+
## Change history
|
|
149
|
+
- 2026-08-27 — Initial: documented that Compass `navigation` is gated **solely** by role-scoped
|
|
150
|
+
`SurfaceOverrides IS_VISIBLE` rows (all six elements are Core-base `isVisible = 0` with
|
|
151
|
+
`aclActionId = NULL`, so the element ACL gate is a no-op); recorded the element-id → role map
|
|
152
|
+
(109/111/112/113; role 9 is the only role granting Items/Vendor Items/Bundles) and that
|
|
153
|
+
`SurfaceElements` **109–114** sit **outside** the reserved 125–146 block. Added the diff-then-
|
|
154
|
+
intersect diagnostic recipe (a four-role gap for user 289067 collapsed to role 9 alone), the
|
|
155
|
+
visible-element-count trick for attributing a pasted bundle, and the role-grant vs. SurfaceOverride
|
|
156
|
+
tradeoff. Flagged that **Inventory and Service Requests are visible to no Compass role at all**.
|
|
157
|
+
Migration `2026-08-27a - GrantAgilantAdministratorsRoleToInternalUser.sql` written on branch
|
|
158
|
+
`TRUE-81325` — not committed, not executed. (apeterson)
|
|
@@ -11,6 +11,7 @@ owners: [bala]
|
|
|
11
11
|
files:
|
|
12
12
|
- dbchanges2/Client_Compass/2026-08-26a - CompassCreativeStudioPersona.sql
|
|
13
13
|
related:
|
|
14
|
+
- ./granting-navigation-access.md
|
|
14
15
|
- ../features/persona-model-and-levy-gating.md
|
|
15
16
|
- ./persona-refactor-migration.md
|
|
16
17
|
- ./persona-population-env-comparison.md
|
package/package.json
CHANGED