toga-ai 1.0.1089 → 1.0.1091
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/features/api-client.md +6 -1
- package/knowledge/2.0/apps/toga-blox/features/blox-authoring-defects.md +7 -1
- package/knowledge/2.0/apps/toga2-commerce/INDEX.md +1 -0
- package/knowledge/2.0/apps/toga2-commerce/features/cart-add-item-line-merge.md +86 -0
- package/knowledge/2.0/apps/toga2-commerce/features/catalog-cache-freshness.md +5 -4
- package/knowledge/2.0/apps/toga2-commerce/workflows/local-development-hosts.md +3 -2
- package/knowledge/2.0/apps/toga2-commerce/workflows/unit-testing-vitest.md +7 -3
- package/knowledge/2.0/apps/toga25-blox/INDEX.md +1 -1
- package/knowledge/2.0/apps/toga25-blox/architecture.md +1 -1
- package/knowledge/2.0/apps/toga25-blox/features/old-blox-port.md +13 -8
- package/knowledge/2.0/apps/toga25-supply/INDEX.md +1 -1
- package/knowledge/2.0/apps/toga25-supply/features/talos-integration.md +37 -6
- package/knowledge/2.0/apps/toga25-supply/workflows/amplify-deployment.md +4 -3
- package/knowledge/2.0/standards/frontend.md +9 -4
- package/knowledge/INDEX.md +2 -2
- package/knowledge/clients/compass-canada/INDEX.md +1 -0
- package/knowledge/clients/compass-canada/features/persona-model.md +58 -0
- package/knowledge/clients/compass-canada/profile.md +5 -8
- package/knowledge/clients/compass-usa/INDEX.md +1 -1
- package/knowledge/clients/compass-usa/features/corporate-vs-field-laptop-personas.md +64 -60
- package/knowledge/clients/compass-usa/features/location-hierarchy-and-sectors.md +1 -1
- package/knowledge/clients/compass-usa/features/persona-model-and-levy-gating.md +33 -21
- package/knowledge/clients/nychh/features/talos-assistant-enablement.md +5 -2
- package/knowledge/standalone/apps/talos-backend/features/auth-and-access-control.md +4 -2
- package/knowledge/standalone/apps/talos-backend/features/client-integration-contract.md +9 -2
- package/package.json +1 -1
|
@@ -6,7 +6,7 @@ project: TOGa Blox
|
|
|
6
6
|
client: shared
|
|
7
7
|
type: feature
|
|
8
8
|
status: active
|
|
9
|
-
updated: 2026-
|
|
9
|
+
updated: 2026-10-20
|
|
10
10
|
owners: [apeterson, tcox]
|
|
11
11
|
files:
|
|
12
12
|
- toga-blox/src/reactQuery/queryHelpers.ts
|
|
@@ -63,6 +63,7 @@ blox axios/React-Query client — envelope, auth (Bearer + refresh), option seri
|
|
|
63
63
|
- `TableViewField.slug` (URL-safe) ≠ `tableViewField` (DB column path); `getDataTableData` maps slug → DB field.
|
|
64
64
|
- Only **401** auto-retries; other network errors need React Query `retry`.
|
|
65
65
|
- 401 refresh needs a `refreshToken` in localStorage, else it logs out (hard redirect to `/`).
|
|
66
|
+
- Refresh covers **only** calls through the registered axios instance. A raw `fetch` to another host (e.g. Talos/Aegra) keeps a stale token and fails after expiry — call `refreshAccessToken` yourself, as toga25-supply's `talosFetch` does ([talos-integration](../../toga25-supply/features/talos-integration.md)). `refreshAccessToken` logs the refresh bearer on failure — [defect 10](blox-authoring-defects.md).
|
|
66
67
|
- `transactionId` is injected automatically (request tracing); `depth=-1` returns hydrated nesting.
|
|
67
68
|
- **⚠ `fetchPublicToken()` (`POST /auth/public`) is ORIGIN-GATED** — api2 resolves the browser's `Origin` header against `Core.Domains` to establish `appId`/`clientId` before minting, and rejects an unregistered origin with **EN-6 "domain not permitted"**. Every **host variant needs its own `Core.Domains` row**: verified 2026-08-13 that `https://togatech.com` and `http://localhost` (any port — the port is stripped) are allowed while **`https://www.togatech.com` is rejected**. Symptom on a public marketing page is *silent* — no token, so every anonymous fetch fails and the section renders its empty state. Fix is a `Core.Domains` row, not frontend code. See [encrypted user-uuid auth handoff](../../api2/features/encrypted-user-uuid-auth-handoff.md) for the same gating on the SSO handoff route.
|
|
68
69
|
- **⚠ `assembleOptionsWhere` (`reactQuery/queryHelpers.ts`) expects every `where.and`/`where.or` entry to be a STRUCTURED OBJECT** (`{field:{op:value}}`). A **raw API-syntax string** clause — e.g. a metadata `apiWhereClause` like `(IFNULL(Items.assetTypeId,0):ne:3)` that `getDataTableData` pushes into `apiOptions.where.and` — is iterated with `for (const field in entry)`, which on a string walks **character indices**, emitting garbage `index:0:char` conditions (`0:0:(`, `1:0:I`, …). api2 v2 then rejects the request with **`Invalid operator '0'` → EO-1 500**. Symptom: opening a modal/table whose `tableViewMeta` carries an `apiWhereClause` (seen in the Compass USA Item Fulfillment modal) 500s. Fix (in place since 2026-08-17): the serializer now passes any `typeof === "string"` entry through **verbatim**, stripping one outer paren pair so the group re-wrap doesn't double-wrap. Any raw pre-formatted where clause was 100% broken before this. If you add a new raw-string clause path, route it through this string branch — do **not** feed a raw clause into an object-expecting serializer.
|
|
@@ -88,3 +89,7 @@ blox axios/React-Query client — envelope, auth (Bearer + refresh), option seri
|
|
|
88
89
|
|
|
89
90
|
- [architecture](../architecture.md) — library overview and the data-layer summary.
|
|
90
91
|
- [Table component](table.md) — consumer of the table-data fetchers.
|
|
92
|
+
|
|
93
|
+
## Change history
|
|
94
|
+
|
|
95
|
+
- 2026-10-20 — Gotcha: axios-only refresh; raw `fetch` callers must refresh themselves (TRUE-82954) (tcox).
|
|
@@ -6,7 +6,7 @@ project: TOGa Blox
|
|
|
6
6
|
client: shared
|
|
7
7
|
type: feature
|
|
8
8
|
status: active
|
|
9
|
-
updated: 2026-10-
|
|
9
|
+
updated: 2026-10-20
|
|
10
10
|
owners: [apeterson, tcox]
|
|
11
11
|
files:
|
|
12
12
|
- toga-blox/src/components/BaseButton
|
|
@@ -53,6 +53,11 @@ Nothing here is scheduled — a backlog with evidence attached, so a fix can be
|
|
|
53
53
|
- **Consumer symptom.** On any **SSO client**, `/` is precisely the route that triggers the IdP redirect — so signing out bounces the user straight back into SSO and the local `/login` becomes **unreachable**. Worse, an app cannot fully patch around it from one place: the Logout button goes through the app's own auth context while an expired token goes through blox's 401 interceptor, so a workaround must cover **both** paths. `toga25-supply` built that workaround and then reverted it, so the loop is live there today.
|
|
54
54
|
- **Fix direction.** Make the destination **caller-supplied** (a `redirectTo` option, defaulting to today's `"/"`), or have `performLogout` not navigate and let the caller's `onLogout` own it.
|
|
55
55
|
|
|
56
|
+
**10. `refreshAccessToken` — logs the whole `AxiosError`, refresh token included** (found 2026-10-20, TRUE-82954)
|
|
57
|
+
- **Mechanism.** On failure it `console.error`s the full `AxiosError`, whose `config.headers` hold `Authorization: Bearer <refreshToken>`.
|
|
58
|
+
- **Consumer symptom.** None visible. The refresh token lands in the browser console and, in apps with Sentry, can ride along in a console breadcrumb — a credential leak the app cannot stop (frontend §12: never log a token).
|
|
59
|
+
- **Fix direction.** Log only `error.response?.status` and a short message. Non-breaking.
|
|
60
|
+
|
|
56
61
|
### Styling defects
|
|
57
62
|
|
|
58
63
|
**4. `BaseButton` — `primary` and `primaryAction` are NOT interchangeable variants**
|
|
@@ -105,6 +110,7 @@ Long-standing, previously recorded only as consumer-side advice:
|
|
|
105
110
|
- **A fix here is a fleet-wide change.** blox is unbundled and consumed by ~40 call sites; anything touching auth or `localStorage` keys changes live session behavior on the next channel head a consumer picks up — and `toga2-commerce` picks up the channel head **automatically at build time**, with no PR. Sequence auth fixes deliberately.
|
|
106
111
|
|
|
107
112
|
## Change history
|
|
113
|
+
- 2026-10-20 — added auth defect 10 (`refreshAccessToken` logs the refresh-token bearer) (tcox)
|
|
108
114
|
- 2026-10-05 — added tooling defects 7–9 (Storybook addons in `dependencies`, unstyled local-only Storybook, broken Docker scripts) (tcox)
|
|
109
115
|
|
|
110
116
|
## Related
|
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
|-----|---------|
|
|
5
5
|
| [TOGa Commerce (toga2-commerce / commerce2-react) Architecture](architecture.md) | Whole-repo map of the toga2-commerce React storefront (bootstrap, routing, MVVM pages, state, API, multi-tenant resolution); open when working anywhere in this |
|
|
6
6
|
| [Bundle item visibility & selectability (the three flags, and why they are enforced nowhere but the client)](features/bundle-item-visibility-and-selectability.md) | Which kit components a shopper can see/select is decided entirely in the browser, enforced nowhere on the server; open when touching bundle rendering, kit check |
|
|
7
|
+
| [Cart addItem line merge — re-adding an item refreshes its price, except in edit-order mode](features/cart-add-item-line-merge.md) | What `useCartStoreZu.addItem` does when the item is already a cart line: a draft cart takes the new price, edit-order mode keeps the saved order price; open for |
|
|
7
8
|
| [Cart Bundle Submission & the bundleUuid Identity Contract](features/cart-bundle-submission-and-identity.md) | How cart bundles (kits) become SalesOrderItems on submit/edit, and the bundleUuid identity contract every consumer must obey; open before touching cart-bundle s |
|
|
8
9
|
| [Cart Notification Emails — auto-add/remove lifecycle + duplicate prevention](features/cart-notification-emails.md) | Cart Notifications section: CC-email duplicate prevention + auto-add/remove of order-for-user & delegate-manager emails; open when touching notification emails |
|
|
9
10
|
| [Cart Order For user — Add New User duplicate guard + approving-manager rule](features/cart-order-for-user-and-approving-manager.md) | Cart "Order For" user section: the Add New User duplicate check (username OR email), who the Order For list can find, and the config-driven rule that blocks che |
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Cart addItem line merge — re-adding an item refreshes its price, except in edit-order mode
|
|
3
|
+
framework: "2.0"
|
|
4
|
+
repo: toga2-commerce
|
|
5
|
+
project: TOGa Commerce
|
|
6
|
+
client: shared
|
|
7
|
+
type: feature
|
|
8
|
+
status: active
|
|
9
|
+
updated: 2026-10-19
|
|
10
|
+
owners: ["tcox"]
|
|
11
|
+
files:
|
|
12
|
+
- toga2-commerce/src/stores/useCartStoreZu.ts
|
|
13
|
+
- toga2-commerce/src/stores/useCartStoreZu.test.ts
|
|
14
|
+
- toga2-commerce/src/stores/useEditOrderZu.ts
|
|
15
|
+
- toga2-commerce/src/pages/ItemsView/ItemViewPage.tsx
|
|
16
|
+
- toga2-commerce/src/api/syncSalesOrderFromApiToLocalStorage.ts
|
|
17
|
+
- toga2-commerce/src/pages/Cart/view/cartTable/CartTableBundleItem.tsx
|
|
18
|
+
related:
|
|
19
|
+
- ./catalog-cache-freshness.md
|
|
20
|
+
- ./inactive-item-purchase-gating.md
|
|
21
|
+
- ./cart-bundle-submission-and-identity.md
|
|
22
|
+
- ./cart-order-total-computation.md
|
|
23
|
+
- ../workflows/unit-testing-vitest.md
|
|
24
|
+
- ../workflows/local-development-hosts.md
|
|
25
|
+
---
|
|
26
|
+
|
|
27
|
+
What `useCartStoreZu.addItem` does when the item is already a cart line: a draft cart takes the new price, edit-order mode keeps the saved order price; open for any "cart shows the old price" or "editing an order changed its price" bug.
|
|
28
|
+
|
|
29
|
+
## Summary
|
|
30
|
+
|
|
31
|
+
> **Status 2026-10-19 — branch-dependent.** The rules below are on `TRUE-82729` (`c2b756a8`, pending merge, not on `_production`). Ship it **with or after** `TRUE-82427` — see [Merge order](#merge-order).
|
|
32
|
+
|
|
33
|
+
A line is matched by `Item.uuid`. On a match:
|
|
34
|
+
|
|
35
|
+
| Mode | `price` / `lease` / `Item` | `uuid` (sales order item link) | quantity |
|
|
36
|
+
|---|---|---|---|
|
|
37
|
+
| Draft cart | taken from the new item | kept | summed |
|
|
38
|
+
| Edit-order (`useEditOrderZu.getState().inEditMode`) | **kept** (saved order values) | kept | summed |
|
|
39
|
+
|
|
40
|
+
New items added while editing use the current price. The line `price` (`ItemForZuCart.price`) is what totals (`useCartViewModel.ts` reduce) and the sales order sync (`syncSalesOrderItemsFromLocalStorageCartToApi.ts` L42) use.
|
|
41
|
+
|
|
42
|
+
## How it works
|
|
43
|
+
|
|
44
|
+
### Decision — edit-order mode never reprices a placed order
|
|
45
|
+
|
|
46
|
+
Decided 2026-10-19 (tcox). Repricing an order the customer already placed is bad business practice. Edit mode starts at `/cart?uuid=<salesOrderUuid>` (`useCartViewModel.ts` ~L87-110 calls `setInEditMode(true)`).
|
|
47
|
+
|
|
48
|
+
**Check `inEditMode`, never the line `uuid`.** A normal draft cart is a quote sales order loaded at login by `syncSalesOrderWithLocalStorageCart` (`AuthLayout`), so its lines carry sales order item uuids too.
|
|
49
|
+
|
|
50
|
+
### Who calls `addItem` with an existing line
|
|
51
|
+
|
|
52
|
+
- **Item page** (`ItemViewPage.tsx` `handleAddItemToCart`) — the main re-add path. After add it resets quantity to 1 only; price/lease/uuid stay.
|
|
53
|
+
- **Split from kit** (`CartTableBundleItem.tsx` ~L74) — sends `bundleItem.item._unitPrice`, the item's own catalog price (the kit price is `bundleItem._unitPrice`). Split is enabled only for COMPASS / COMPASSCANADA admins.
|
|
54
|
+
- **Home `ItemsSection` and Filter** — call `addItem` only when the item is not in the cart (they show +/- otherwise), so they never reach the merge branch.
|
|
55
|
+
- **Cart "+" (`incrementItem`)** — never changes price.
|
|
56
|
+
|
|
57
|
+
A currency change in MySettings clears the cart, so a merge never mixes currencies.
|
|
58
|
+
|
|
59
|
+
### Merge order
|
|
60
|
+
|
|
61
|
+
On `_production` the item page still serves a price cached up to 24h (see [catalog cache freshness](./catalog-cache-freshness.md#where-the-staleness-lives)), so a re-add would copy that old cached price onto the line. `TRUE-82427` removes that cache. The branches touch different lines; no conflict expected.
|
|
62
|
+
|
|
63
|
+
### How to test (no DB change)
|
|
64
|
+
|
|
65
|
+
1. Add an item to the cart.
|
|
66
|
+
2. In the browser console, edit `localStorage["zu-cart"]`: parse it, set `c.state.items[0].price = 1`, write it back. Reload.
|
|
67
|
+
3. Re-add the same item from the item page. The line must jump back to the real price.
|
|
68
|
+
4. Edit mode: open `/cart?uuid=<test order uuid>`, fake the price the same way, re-add. The price must stay.
|
|
69
|
+
|
|
70
|
+
Passed 2026-10-19. Unit tests: `src/stores/useCartStoreZu.test.ts` (4 tests incl. edit mode) — see [unit testing](../workflows/unit-testing-vitest.md).
|
|
71
|
+
|
|
72
|
+
## Gotchas
|
|
73
|
+
|
|
74
|
+
- **⚠ Fixing the re-add price exposed a price-0 bug on the item page.** `handleAddItemToCart` used to reset `itemForCart` to price 0 / lease 0 / uuid `""`; the sync effect does not re-run, so a second click sent price 0. The old merge hid it by keeping the line's price. Fixed on `TRUE-82729`: reset quantity only.
|
|
75
|
+
- **⚠ The old merge bug was old, not a regression** — present since at least 2025-10 (`8c3ad44c`). It surfaced in QA after `TRUE-82427` made the item page send the refetched price (CodeRabbit, PR #376). `TRUE-81382` never touches the cart store.
|
|
76
|
+
- The inactive-item guard also lives in `addItem` — see [inactive-item purchase gating](./inactive-item-purchase-gating.md#3-the-store-is-the-choke-point-not-the-buttons).
|
|
77
|
+
|
|
78
|
+
## Change history
|
|
79
|
+
- 2026-10-19 — Created. Re-add takes the new price/lease/Item (keeps line uuid, sums quantity); edit-order mode keeps the saved price (decision); item page no longer resets price to 0 after add; first cart store unit tests. TRUE-82729 (`c2b756a8`, pending merge). (tcox)
|
|
80
|
+
|
|
81
|
+
## Related
|
|
82
|
+
|
|
83
|
+
- [catalog-cache-freshness](./catalog-cache-freshness.md)
|
|
84
|
+
- [inactive-item-purchase-gating](./inactive-item-purchase-gating.md)
|
|
85
|
+
- [cart-bundle-submission-and-identity](./cart-bundle-submission-and-identity.md)
|
|
86
|
+
- [cart-order-total-computation](./cart-order-total-computation.md)
|
|
@@ -6,7 +6,7 @@ project: TOGa Commerce
|
|
|
6
6
|
client: shared
|
|
7
7
|
type: feature
|
|
8
8
|
status: active
|
|
9
|
-
updated: 2026-10-
|
|
9
|
+
updated: 2026-10-19
|
|
10
10
|
owners: ["bala", "apeterson", "tcox"]
|
|
11
11
|
files:
|
|
12
12
|
- toga2-commerce/src/lib/queryClient.ts
|
|
@@ -31,6 +31,7 @@ files:
|
|
|
31
31
|
- toga2-commerce/src/pages/BundleView/hooks/applyBundleFormatting.ts
|
|
32
32
|
- toga2-commerce/src/pages/GetSupport/GetSupportPage.tsx
|
|
33
33
|
related:
|
|
34
|
+
- ./cart-add-item-line-merge.md
|
|
34
35
|
- ./inactive-item-purchase-gating.md
|
|
35
36
|
- ./force-logout-on-deployment.md
|
|
36
37
|
- ./sso-redirect-and-session-gating.md
|
|
@@ -90,7 +91,7 @@ A change reaches the shopper on: navigating to the page (mount), refresh / fresh
|
|
|
90
91
|
|
|
91
92
|
Limits to state to the business:
|
|
92
93
|
|
|
93
|
-
1. **Prices already in the cart do not update** — the cart is Zustand + `localStorage`, not React Query.
|
|
94
|
+
1. **Prices already in the cart do not update** — the cart is Zustand + `localStorage`, not React Query. Re-adding the item refreshes its line price on `TRUE-82729` (not in edit-order mode) — see [cart addItem line merge](./cart-add-item-line-merge.md).
|
|
94
95
|
2. **Alt-tabbing to another application is not a focus event** — see [frontend standard §4](../../../standards/frontend.md).
|
|
95
96
|
3. **On a cold load the old value paints first** from `localStorage["commerce"]`, then swaps.
|
|
96
97
|
|
|
@@ -109,8 +110,7 @@ Limits to state to the business:
|
|
|
109
110
|
|
|
110
111
|
- No loop: `selectedItem` is view-model state, and React Query structural sharing keeps its reference when data is unchanged.
|
|
111
112
|
- Never add `itemToAddToCart` as a dependency — it is rebuilt every render.
|
|
112
|
-
-
|
|
113
|
-
- **Open:** `addItem` in `src/stores/useCartStoreZu.ts` keeps the existing cart line's fields when the same item uuid is added again and only increments quantity, so re-adding after a price change keeps the old price on the whole line (CodeRabbit, PR #376). Fix direction: refresh the item fields in the existing-uuid branch. Out of scope for `TRUE-82427` (cart is Zustand, a stated limit).
|
|
113
|
+
- The re-add-keeps-old-price and second-click-price-0 bugs are fixed on `TRUE-82729` (pending merge) — see [cart addItem line merge](./cart-add-item-line-merge.md).
|
|
114
114
|
|
|
115
115
|
### Opting a single query out — `staleTime: 0` + `gcTime: 0` is not enough
|
|
116
116
|
|
|
@@ -183,6 +183,7 @@ Pinned 2026-09-03 on `TRUE-81610`.
|
|
|
183
183
|
- **`AuthLayout` never remounts**, so `refetchOnMount` fires once per session for queries it owns.
|
|
184
184
|
|
|
185
185
|
## Change history
|
|
186
|
+
- 2026-10-19 — The two open item-page Add to Cart bullets closed by `TRUE-82729`; moved to [cart addItem line merge](./cart-add-item-line-merge.md). (tcox)
|
|
186
187
|
- 2026-10-13 — `TRUE-82427` (`93f12265`, pending merge): per-query `staleTime` / `refetchOnWindowFocus: false` overrides removed, focus refetch on app-wide, guard's explicit opt-in removed, Home refetch fix, item-detail Add to Cart uses refetched price (and rebuilds on currency id change), Home refetch reapplies the submitted search (CodeRabbit fixes `61e4ffd9`, `e5eca52f`), 81382 merge resolution corrected; four other refetch-ignoring pages logged as open. Countries + 5-min login queries left out of scope. 30s heavy-query shape superseded. (tcox)
|
|
187
188
|
- 2026-10-08 — Bundle queries uncached + excluded from persistence (TRUE-81382); branch map of the two persistence filters; buster live on `_production`. (tcox)
|
|
188
189
|
- 2026-10-01 — Global default already `staleTime: 0`; per-query 24h overrides inventoried; ticket scope agreed. (apeterson)
|
|
@@ -6,7 +6,7 @@ project: TOGa Commerce
|
|
|
6
6
|
client: shared
|
|
7
7
|
type: workflow
|
|
8
8
|
status: active
|
|
9
|
-
updated: 2026-10-
|
|
9
|
+
updated: 2026-10-19
|
|
10
10
|
owners: [apeterson, tcox]
|
|
11
11
|
files:
|
|
12
12
|
- toga2-commerce/vite.config.ts
|
|
@@ -32,8 +32,9 @@ The tenant is resolved from `window.location.hostname`, and api2 checks the requ
|
|
|
32
32
|
|
|
33
33
|
- **⚠ `npm run sandbox-client` dies with `getaddrinfo ENOTFOUND`** until `client.sandbox.togacommerce` has its own hosts entry.
|
|
34
34
|
- **⚠ A missing hosts entry looks like an auth bug.** The symptom is an instant redirect to `/login`, not a network error.
|
|
35
|
-
- **⚠ `npm run dev`
|
|
35
|
+
- **⚠ `npm run dev` and the tenant scripts (`npm run compass`, …) talk to beta, not sandbox-client.** `.env.development` sets `VITE_API = https://api.beta.togahub.com/v2`. A DB error seen locally (e.g. 1054 `Unknown column 'isOpen'` on `SalesOrderItems`, 2026-10-08) is beta's schema — check the request host before reading another environment's DB or `Logs_<Tenant>.Api`.
|
|
36
36
|
|
|
37
37
|
## Change history
|
|
38
|
+
- 2026-10-19 — Tenant dev scripts (`npm run compass`) also hit beta. (tcox)
|
|
38
39
|
- 2026-10-08 — Added: local dev API target is beta. (tcox)
|
|
39
40
|
- 2026-09-25 — Documented after tracing "cannot sign in locally" to the Origin/`Core.Domains` check. (apeterson)
|
|
@@ -6,16 +6,18 @@ project: TOGa Commerce
|
|
|
6
6
|
client: shared
|
|
7
7
|
type: workflow
|
|
8
8
|
status: active
|
|
9
|
-
updated: 2026-
|
|
10
|
-
owners: [apeterson]
|
|
9
|
+
updated: 2026-10-19
|
|
10
|
+
owners: [apeterson, tcox]
|
|
11
11
|
files:
|
|
12
12
|
- toga2-commerce/vitest.config.ts
|
|
13
13
|
- toga2-commerce/package.json
|
|
14
14
|
- toga2-commerce/src/lib/session.test.ts
|
|
15
15
|
- toga2-commerce/src/api/refreshAuthTokens.test.ts
|
|
16
|
+
- toga2-commerce/src/stores/useCartStoreZu.test.ts
|
|
16
17
|
related:
|
|
17
18
|
- ./cypress-testing.md
|
|
18
19
|
- ../features/sso-redirect-and-session-gating.md
|
|
20
|
+
- ../features/cart-add-item-line-merge.md
|
|
19
21
|
---
|
|
20
22
|
|
|
21
23
|
How to run and write unit tests in commerce (vitest + jsdom), and how to verify a change actually compiles without tripping the build-mode trap.
|
|
@@ -23,16 +25,18 @@ How to run and write unit tests in commerce (vitest + jsdom), and how to verify
|
|
|
23
25
|
## Steps
|
|
24
26
|
|
|
25
27
|
1. Run the suite: `npm test` (watch: `npm test:watch`). Config is `vitest.config.ts`, environment `jsdom`.
|
|
26
|
-
2. Name the file next to its subject: `src/lib/session.test.ts`, `src/api/refreshAuthTokens.test.ts
|
|
28
|
+
2. Name the file next to its subject: `src/lib/session.test.ts`, `src/api/refreshAuthTokens.test.ts`, `src/stores/useCartStoreZu.test.ts` (Zustand store). Test names are snake_case `test_…`.
|
|
27
29
|
3. **Prove the test fails on the pre-fix code first.** Reverting `hasSession` turned 5 red; removing the single-flight and identity guards turned 3 red. A regression test that was never seen red proves nothing.
|
|
28
30
|
4. Test a race with a **held-open promise you resolve by hand**, never real timing — that is what makes the refresh tests deterministic.
|
|
29
31
|
5. Verify a build compiles with `npx vite build --mode development` (cheap), then read the trap below before trusting any `DEV`-gated path.
|
|
30
32
|
|
|
31
33
|
## Gotchas
|
|
32
34
|
|
|
35
|
+
- **⚠ `npm run test` fails with `vitest: command not found` on a stale `node_modules`.** vitest/jsdom are in `devDependencies` but not installed until you run `npm install`.
|
|
33
36
|
- **⚠ `import.meta.env.DEV` comes from `NODE_ENV`, not `--mode`.** `vite build` pins `NODE_ENV=production`, so a `DEV`-gated code path is **unreachable in every deployed mode** — including `--mode development`. Never gate behaviour a deployed build needs on `DEV`.
|
|
34
37
|
- **⚠ Commerce's production build script is plain `vite build` with no `--mode`,** so `MODE` is `"production"`. Anything keyed on `MODE` (e.g. the SSO `Environments.slug` lookup) resolves to `PRODUCTION` there.
|
|
35
38
|
- Commerce had **no** unit harness before 2026-09-22 — Cypress only ([cypress-testing](./cypress-testing.md)). Put browser/route behaviour in Cypress; put pure logic and races in vitest.
|
|
36
39
|
|
|
37
40
|
## Change history
|
|
41
|
+
- 2026-10-19 — First cart store test (`useCartStoreZu.test.ts`, 4 tests); stale `node_modules` gotcha. TRUE-82729. (tcox)
|
|
38
42
|
- 2026-09-22 — Created. vitest + jsdom added to commerce with 19 tests over `hasSession` and the token refresh. TRUE-81991 (`e546db3d`). (apeterson)
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
| [TOGa Blox 2.5 — Architecture](architecture.md) | Shared React+TS SOURCE library for the new platform apps (new platform apps first; replaces toga-blox for all apps over time); read the ADR for full detail, not |
|
|
6
6
|
| [Contact primary email/phone save (updateContactChannels) — table calls only](features/contact-channels-save.md) | How a contact edit form saves the contact's primary email and phone without nested writes; use it (do not rebuild it) in any app that edits a contact's email/ph |
|
|
7
7
|
| [Money formatting (useSessionCurrency / formatMoney / useFormatMoney)](features/money-formatting.md) | The one money formatter for every platform app (session currency + session language via Intl); open before showing any money amount or adding a money column. |
|
|
8
|
-
| [Old blox (toga-blox)
|
|
8
|
+
| [Old blox (toga-blox) port into toga25-blox — rewrite, pixel-identical](features/old-blox-port.md) | Old `@agilant/toga-blox` code in toga25-blox: interim straight copy being rewritten piece by piece, same names/props/look; open before touching any old componen |
|
|
9
9
|
| [Shell GlobalSearch, NotificationBell, NotificationsInbox (@toga/blox/shell)](features/shell-search-and-notifications.md) | Shared app-shell header search + notifications for every platform app; open when wiring search or the bell into an app, or changing search timing/keys. |
|
|
10
10
|
| [GitHub Actions → AWS role via OIDC (trust policy + sub claim)](workflows/github-actions-aws-oidc-role.md) | How a GitHub Actions job assumes an AWS IAM role with OIDC, and the `sub` claim trap; open before creating or debugging any GitHub-to-AWS role ("Not authorized |
|
|
11
11
|
| [Moving an old app from @agilant/toga-blox onto toga25-blox](workflows/moving-an-old-app-onto-toga25-blox.md) | Planned steps to switch an old app off the `@agilant/toga-blox` npm package onto toga25-blox source with zero import edits; open before moving any old app. |
|
|
@@ -22,7 +22,7 @@ Shared React+TS SOURCE library for the new platform apps (new platform apps firs
|
|
|
22
22
|
**Critical rules:**
|
|
23
23
|
- **Reuse order:** an existing `toga25-blox` piece → a piece already built in another
|
|
24
24
|
new-platform app (promote it to blox) → only then build new. Never duplicate.
|
|
25
|
-
- **Moving an old app: rewrite onto toga25-blox pieces — never port old blox code wholesale.** Old blox has 3 table engines and 3 select stacks; copying them carries that over.
|
|
25
|
+
- **Moving an old app: rewrite onto toga25-blox pieces — never port old blox code wholesale.** Old blox has 3 table engines and 3 select stacks; copying them carries that over. The rewrite must be invisible to old apps: same export names, props and pixel-identical look (tcox, 2026-10-13) — see [old-blox-port](features/old-blox-port.md).
|
|
26
26
|
- **Import boundary:** apps may import only `@toga/blox/<entry>` (the public entry barrel) —
|
|
27
27
|
lint blocks deeper paths. Blox never imports app code.
|
|
28
28
|
- **Layer order (nothing imports upward):** `lib` < `core` < `tenancy` < `theme` < `surface`
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
---
|
|
2
|
-
title: Old blox (toga-blox)
|
|
2
|
+
title: Old blox (toga-blox) port into toga25-blox — rewrite, pixel-identical
|
|
3
3
|
framework: "2.0"
|
|
4
4
|
repo: toga25-blox
|
|
5
5
|
project: TOGa Blox 2.5
|
|
6
6
|
client: shared
|
|
7
7
|
type: feature
|
|
8
8
|
status: active
|
|
9
|
-
updated: 2026-10-
|
|
9
|
+
updated: 2026-10-13
|
|
10
10
|
owners: [tcox]
|
|
11
11
|
files:
|
|
12
12
|
- toga25-blox/src/index.ts
|
|
@@ -21,18 +21,23 @@ related:
|
|
|
21
21
|
- ../workflows/moving-an-old-app-onto-toga25-blox.md
|
|
22
22
|
- ../../toga-blox/architecture.md
|
|
23
23
|
---
|
|
24
|
-
Old `@agilant/toga-blox` code
|
|
24
|
+
Old `@agilant/toga-blox` code in toga25-blox: interim straight copy being rewritten piece by piece, same names/props/look; open before touching any old component, its checks/config, or merging Storybook onto it.
|
|
25
25
|
|
|
26
26
|
## Summary
|
|
27
27
|
|
|
28
|
-
|
|
28
|
+
**Corrected 2026-10-13 (tcox):** old blox is **REWRITTEN** into toga25-blox (Jeff's ADR "do not port wholesale" is intended), but the rewrite must be **invisible to old apps**. The straight copy committed on TRUE-81994 (`5901e1c`) is only the interim base and safety net; each piece is replaced by its rewrite behind the same name, and the copy is gone when the last piece is done.
|
|
29
29
|
|
|
30
30
|
**Rules:**
|
|
31
|
-
-
|
|
32
|
-
-
|
|
33
|
-
-
|
|
31
|
+
- Rewrite every ported piece (including ones no app uses yet — they must be ready for later).
|
|
32
|
+
- Keep the public surface identical: export names, prop names (incl. `*Classes` string props), defaults, signatures, return types. Old apps must compile and behave the same with zero import edits.
|
|
33
|
+
- Keep the rendered look **pixel-identical** where old apps use it — no one should notice. Prove it with screenshot comparison against old blox before swapping a piece in.
|
|
34
|
+
- No Tailwind inside rewritten pieces: CSS modules + tokens, with values matching what apps render today.
|
|
35
|
+
- Names that also exist in new blox (e.g. `Badge`, `ToasterProvider`, `MultiSelect`, `useTableData`): extend the new piece additively so old usage keeps working.
|
|
36
|
+
- Bugs found while rewriting are fixed (e.g. old `Header` two hamburgers).
|
|
37
|
+
- No `legacy/` folder. Move apps one at a time, light users first: commerce → view → toga25-supply → togatech, BDR, talos. toga2-hub is retired and does not move; toga2-supply is mostly retired (one client may still use it) and stays on npm old blox until retired; toga2-view (2.0, no 2.5 version) is still in use and moves.
|
|
38
|
+
- Target for "identical" = what each app shows today on its pinned old-blox version, proved per app. Decision record: ADR `docs/decisions/0002-old-blox-compatible-rewrite.md` in toga25-blox (needs Jeff's sign-off).
|
|
34
39
|
|
|
35
|
-
A
|
|
40
|
+
A 2026-10-07 attempt that rewrote Tooltip/Tabs/Avatar/InlineAlert with **renamed props** was thrown away — renaming is what broke the rule, not rewriting.
|
|
36
41
|
|
|
37
42
|
## How it works
|
|
38
43
|
|
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
| [SSO redirect & public-vs-user session gating (useAuthenticationFlow)](features/sso-redirect-and-session-gating.md) | How 2.5 decides on every navigation whether to bounce an anonymous visitor to their client's SSO IdP vs the local `/login` (useAuthenticationFlow); open for SSO |
|
|
20
20
|
| [Surface Frontend (DB-driven UI consumption, src/surface/)](features/surface-frontend.md) | The FE consumer of the platform-wide Surface layer (DB-driven UI config via `GET /v2/surfaces/meta`) — hooks, registries, adapter seams, migration recipe, and t |
|
|
21
21
|
| [TableView CSV export (generic, all pages)](features/tableview-csv-export.md) | Reusable export of any blox TableView to CSV (every page, not just the loaded rows); open before adding an Export button to a toga25-supply table. |
|
|
22
|
-
| [Talos Integration in TOGa Supply](features/talos-integration.md) | TOGa Supply embeds a surface-gated blox Talos panel using the current Supply user's bearer; open for assistant discovery, streaming,
|
|
22
|
+
| [Talos Integration in TOGa Supply](features/talos-integration.md) | TOGa Supply embeds a surface-gated blox Talos panel using the current Supply user's bearer (refreshed by `talosFetch`); open for assistant discovery, streaming, |
|
|
23
23
|
| [Toolbar button chrome (ToolbarButton) — and why blox BaseButton can't be resized from the theme](features/toolbar-button-chrome.md) | toga25-supply's own ToolbarButton chrome (pill/chip, both pinned 27px) for page-toolbar buttons, because blox `BaseButton` sizes are un-themeable literals; open |
|
|
24
24
|
| [Transfer Orders page (TableView → Core surfaces → React page + record modal)](features/transfer-orders-page.md) | The Transfer Orders vertical — list + record modal + Create modal, built surface-first from nothing (NYCHH-only today); the cleanest worked example of the curre |
|
|
25
25
|
| [AWS Amplify Multi-Environment Deployment](workflows/amplify-deployment.md) | How toga25-supply deploys to all its Amplify environments from one shared `amplify.yml` — branch `_<mode>` → Vite `--mode`; open when adding an environment or d |
|
|
@@ -6,10 +6,13 @@ project: TOGa 2.5 Supply
|
|
|
6
6
|
client: shared
|
|
7
7
|
type: feature
|
|
8
8
|
status: active
|
|
9
|
-
updated: 2026-10-
|
|
10
|
-
owners: [apeterson, akhokhani]
|
|
9
|
+
updated: 2026-10-20
|
|
10
|
+
owners: [apeterson, akhokhani, tcox]
|
|
11
11
|
files:
|
|
12
12
|
- toga25-supply/src/api/talos.ts
|
|
13
|
+
- toga25-supply/src/api/talosAuth.ts
|
|
14
|
+
- toga25-supply/src/api/talosConfig.ts
|
|
15
|
+
- toga25-supply/src/api/apiBaseUrl.ts
|
|
13
16
|
- toga25-supply/src/hooks/useTalosSession.ts
|
|
14
17
|
- toga25-supply/src/hooks/useTalosThreads.ts
|
|
15
18
|
- toga25-supply/src/hooks/useTalosSurface.ts
|
|
@@ -19,18 +22,21 @@ files:
|
|
|
19
22
|
- toga25-supply/.env.production
|
|
20
23
|
related:
|
|
21
24
|
- ../../toga-blox/features/talos-assistant.md
|
|
25
|
+
- ../../toga-blox/features/api-client.md
|
|
22
26
|
- ../../dbchanges2/features/surface-layer-schema.md
|
|
27
|
+
- ../../../../standalone/apps/talos-backend/features/client-integration-contract.md
|
|
28
|
+
- ../../../../standalone/apps/talos-backend/features/auth-and-access-control.md
|
|
23
29
|
- ../../../../standalone/apps/talos-backend/workflows/toga-supply-client-onboarding.md
|
|
24
30
|
- ../../../../standalone/apps/talos/features/chat-frontend.md
|
|
25
31
|
- ../../../../clients/nychh/features/talos-assistant-enablement.md
|
|
26
32
|
- ../../../../clients/compass-usa/features/talos-supply-assistant.md
|
|
27
33
|
---
|
|
28
34
|
|
|
29
|
-
TOGa Supply embeds a surface-gated blox Talos panel using the current Supply user's bearer; open for assistant discovery, streaming, frontend lifecycle gaps or tenant enablement.
|
|
35
|
+
TOGa Supply embeds a surface-gated blox Talos panel using the current Supply user's bearer (refreshed by `talosFetch`); open for assistant discovery, streaming, token refresh/401s, where Talos can be tested, frontend lifecycle gaps or tenant enablement.
|
|
30
36
|
|
|
31
37
|
## Summary
|
|
32
38
|
|
|
33
|
-
`AppLayout` hosts the launcher and controlled panel; one Supply-owned `TalosStreamProvider` wraps LangGraph SDK `useStream`. Blox is UI only. The shared `talos-assistant` Surface is seeded off and granted per tenant; a provisioned backend assistant does not establish that a customer's panel is enabled.
|
|
39
|
+
`AppLayout` hosts the launcher and controlled panel; one Supply-owned `TalosStreamProvider` wraps LangGraph SDK `useStream`. Blox is UI only. Every Talos request goes through app-local `talosFetch`, which refreshes the api2 bearer (the blox axios interceptor covers api2 calls only). The shared `talos-assistant` Surface is seeded off and granted per tenant; a provisioned backend assistant does not establish that a customer's panel is enabled.
|
|
34
40
|
|
|
35
41
|
## How it works
|
|
36
42
|
|
|
@@ -38,12 +44,24 @@ TOGa Supply embeds a surface-gated blox Talos panel using the current Supply use
|
|
|
38
44
|
|
|
39
45
|
Use the locally saved `origin/_production` source, not a stale working clone. The October 7 survey inspected Supply commit `bb35181`: React 18.3, Vite 7, LangGraph SDK `^1.10.0` and blox `1.1.4-production.196`. `.env.production` is tracked and sets `VITE_TALOS_API=https://api.togaiq.com`; non-production env files target `https://api.beta.togaiq.com`. There is a production backend.
|
|
40
46
|
|
|
41
|
-
`src/api/
|
|
47
|
+
Env reads (TRUE-82954): `src/api/talosConfig.ts` is the only `VITE_TALOS_API` read (plus `isTalosOrigin`); `src/api/apiBaseUrl.ts` is the only `VITE_API` read (`api.ts` imports it). Both sit outside the blox barrel, so non-axios code and Vitest can use them — the barrel pulls `dist/global.css` and fails Vitest with `Unknown file extension ".css"`.
|
|
48
|
+
|
|
49
|
+
Browser requests go directly to Aegra with the current Supply access token and real browser Origin; there is no token exchange or Next proxy. Aegra's TogaHub auth resolves the exact JWT client slug into the Talos control plane; `/v2/users/me` supplies persona-linked AI-model slug grants. The platform MCP receives that same user's bearer and trusted frontend routing context; api2 remains the business-record/field authorization authority.
|
|
42
50
|
|
|
43
51
|
`useTalosSession` requires `isAuthenticated`, not merely a token (logged-out public tokens exist). Assistant query keys use user UUID, never the token: React Query cache is persisted locally. Query state produces disabled/connecting/ready/error; keep it a non-Suspense query so reconnect does not unmount the composer.
|
|
44
52
|
|
|
45
53
|
`GET /assistants?limit=100&offset=0` discovers the client assistant. Prefer `metadata.customer_facing===true`, otherwise use the list, choose first, and warn on multiple candidates. Never pin an assistant ID in env. GET carries no body or unnecessary JSON Content-Type. HTTP status logs distinguish rejected identity/tenant/entitlement from absent configuration.
|
|
46
54
|
|
|
55
|
+
### Bearer and refresh (`talosFetch`)
|
|
56
|
+
|
|
57
|
+
`src/api/talosAuth.ts` (TRUE-82954, branch `TRUE-82954`, not merged to `_production` as of 2026-10-20). Used by the REST helpers in `talos.ts` (they take no token parameter) and by `useStream` through module-scope `callerOptions: { fetch: talosFetch }`. Keep that object at module scope: an inline options/`defaultHeaders` object rebuilds the SDK Client every render, and a mount-time header never refreshes.
|
|
58
|
+
|
|
59
|
+
1. Read the token from localStorage on every request, and only when the `user` key exists (otherwise it is the blox public token).
|
|
60
|
+
2. Token within 60 s of JWT `exp` → refresh first with blox `refreshAccessToken(apiBaseURL)` (deep import `@agilant/toga-blox/dist/api/auth.js`), single-flight. No refresh when `apiBaseURL` is empty.
|
|
61
|
+
3. 401 → refresh and resend **once**, only if the sent token was expiring or the body `message === "Token expired"`, and only for string/empty bodies. Safe because Aegra auth runs before any route, so a 401 created no run or message. Other 401s and every 403 are returned as is.
|
|
62
|
+
4. Refresh fails → blox `performLogout()` (same as the axios interceptor; `api.ts` passes no `onLogout`). Dead token and failed refresh → logout without sending.
|
|
63
|
+
5. Not signed in, or URL not on the Talos origin → return a synthetic 401 `Response`, never throw: the SDK AsyncCaller retries thrown errors 4x but never retries 401/403. The bearer is only sent to the Talos origin.
|
|
64
|
+
|
|
47
65
|
### Surface and provider lifecycle
|
|
48
66
|
|
|
49
67
|
`useTalosSurface` finds a `featureEnabled` element in the `talos-assistant` bundle; missing/loading/failed/no-marker means disabled. Its `suggestion` element owns the whole ordered chip row. `config.items` has label/prompt/icon; prompt falls back to label. Invalid items are skipped, while a visible empty row is permitted.
|
|
@@ -54,10 +72,16 @@ The inspected implementation shows final-answer outcomes and suppresses final-an
|
|
|
54
72
|
|
|
55
73
|
### Backend contract versus inspected gaps
|
|
56
74
|
|
|
57
|
-
The intended embedded-chat contract requires explicit server cancellation, persisted `/transcript` reload, run-ID rejoin with replay cursor, meaningful terminal-state handling, feedback and refreshed bearer retry.
|
|
75
|
+
The intended embedded-chat contract ([client integration contract](../../../../standalone/apps/talos-backend/features/client-integration-contract.md)) requires explicit server cancellation, persisted `/transcript` reload, run-ID rejoin with replay cursor, meaningful terminal-state handling, feedback and refreshed bearer retry. Bearer refresh/retry is built (above). Still open: Stop calls only `stream.stop()` — the dedicated cancellation helper is not wired; the saved Compass handoff also found missing persisted transcript reload and run-ID rejoin. Treat these as release gaps, not finished behavior.
|
|
58
76
|
|
|
59
77
|
The backend assistant is `toga-supply` on `deep_agent`, with user-bearer `toga-platform` tools and tenant-specific persona/BLP. Compass and NYCHH backend deployments existed in the dated survey; neither tenant's current production Surface grants were read from Core/Client DB during that survey. See their client pages for exact evidence.
|
|
60
78
|
|
|
79
|
+
### Where Talos can be tested (verified 2026-10-20)
|
|
80
|
+
|
|
81
|
+
- **Local dev** (`npm run nychh`, mode `development`): `.env.development` / `.env.development.local` set no `VITE_TALOS_API`, so the panel shows "unavailable".
|
|
82
|
+
- **sandbox-client** (`https://nychh.client.sandbox.togasupply.com`, Amplify branch `_sandbox-client`, `.env.sandbox-client`): `VITE_TALOS_API=https://api.beta.togaiq.com`, but tokens come from `api.client.sandbox.togahub.com`. Beta Talos validates through `TOGAHUB_API_URL=https://api.beta.togahub.com/v2/users/me` (talos-backend `.env.beta`), which rejects them: `GET /assistants` → 401 `"Token expired"` with 43 min left on the token. Panel: "Talos is unavailable right now." Beta health (200) and CORS are fine. Pre-existing env problem, not fixed.
|
|
83
|
+
- So a real browser test needs a Talos whose `TOGAHUB_API_URL` is the same api2 that issued the Supply build's tokens. Until then, Talos auth changes are verified by unit tests only (`talosAuth.test.ts`, `talos.test.ts`, `TalosStreamProvider.test.tsx`).
|
|
84
|
+
|
|
61
85
|
### Hosting evidence
|
|
62
86
|
|
|
63
87
|
As of 2026-10-07, production Supply used Amplify app `d3v9s1i7kge4ll` and its CloudFront distribution. `*.beta.togasupply.com` used a different Amplify app/repo, `toga2-supply`, not this production 2.5 source. Confirm hostname→build mapping before reproducing a version-specific failure.
|
|
@@ -66,7 +90,10 @@ As of 2026-10-07, production Supply used Amplify app `d3v9s1i7kge4ll` and its Cl
|
|
|
66
90
|
|
|
67
91
|
- Surface visibility, active `/users/me` slug grant, control-plane tenant, assistant capability assignments and MCP data ACL are independent gates. Do not repair a denied user with an admin/service token.
|
|
68
92
|
- A backend assistant with successful historical runs is not proof of a production panel launch. Read the actual Surface bundle as the intended user.
|
|
93
|
+
- Aegra `"Token expired"` does not prove expiry: it is returned for **any** TogaHub `/users/me` 401, including a token from a different api2 environment (see sandbox-client above). Check the token's `exp` and `iss` before debugging refresh. Error bodies are `{error, message, details}`, not FastAPI `{detail}` ([auth](../../../../standalone/apps/talos-backend/features/auth-and-access-control.md)).
|
|
94
|
+
- Do not call Talos with plain `fetch` or a token captured at render/mount — it goes stale after the api2 token expires and chat fails. Use `talosFetch`.
|
|
69
95
|
- SDK Stop closes browser streaming; it is not a verified server-run cancellation in the inspected implementation.
|
|
96
|
+
- The SDK retries 5xx/network errors on `POST /runs/stream` (could duplicate a run) — open follow-up, see the [client integration contract](../../../../standalone/apps/talos-backend/features/client-integration-contract.md) gotchas.
|
|
70
97
|
- The mapper hides tools/HITL. Keep this customer-facing read-only assistant within its declared boundary; richer decision UI requires deliberate contract work.
|
|
71
98
|
- Stable query keys must not include credentials. Caches and logs can persist long after a token changes.
|
|
72
99
|
- Code comments calling Aegra “TOGa IQ” are legacy naming. Talos is the backend agent platform; TOGa IQ is the separate management UI.
|
|
@@ -74,7 +101,10 @@ As of 2026-10-07, production Supply used Amplify app `d3v9s1i7kge4ll` and its Cl
|
|
|
74
101
|
## Related
|
|
75
102
|
|
|
76
103
|
- [talos assistant](../../toga-blox/features/talos-assistant.md)
|
|
104
|
+
- [blox api client](../../toga-blox/features/api-client.md)
|
|
77
105
|
- [surface layer schema](../../dbchanges2/features/surface-layer-schema.md)
|
|
106
|
+
- [client integration contract](../../../../standalone/apps/talos-backend/features/client-integration-contract.md)
|
|
107
|
+
- [auth and access control](../../../../standalone/apps/talos-backend/features/auth-and-access-control.md)
|
|
78
108
|
- [toga supply client onboarding](../../../../standalone/apps/talos-backend/workflows/toga-supply-client-onboarding.md)
|
|
79
109
|
- [chat frontend](../../../../standalone/apps/talos/features/chat-frontend.md)
|
|
80
110
|
- [talos assistant enablement](../../../../clients/nychh/features/talos-assistant-enablement.md)
|
|
@@ -82,4 +112,5 @@ As of 2026-10-07, production Supply used Amplify app `d3v9s1i7kge4ll` and its Cl
|
|
|
82
112
|
|
|
83
113
|
## Change history
|
|
84
114
|
|
|
115
|
+
- 2026-10-20 — Added `talosFetch` bearer refresh + one-time 401 resend; env reads moved to `talosConfig.ts` / `apiBaseUrl.ts`; recorded why Talos cannot be tested locally or on sandbox-client (TRUE-82954) (tcox).
|
|
85
116
|
- 2026-10-08 — Reconciled local source and dated 2026-10-07 research into the canonical KB (akhokhani).
|
|
@@ -6,11 +6,11 @@ project: TOGa 2.5 Supply
|
|
|
6
6
|
client: shared
|
|
7
7
|
type: workflow
|
|
8
8
|
status: active
|
|
9
|
-
updated: 2026-10-
|
|
9
|
+
updated: 2026-10-20
|
|
10
10
|
owners: [jcardinal, apeterson, tcox]
|
|
11
11
|
files:
|
|
12
12
|
- toga25-supply/amplify.yml
|
|
13
|
-
- toga25-supply/src/api/
|
|
13
|
+
- toga25-supply/src/api/apiBaseUrl.ts
|
|
14
14
|
- toga25-supply/src/hooks/useAuthenticationFlow.ts
|
|
15
15
|
- toga25-supply/vite.config.ts
|
|
16
16
|
- toga25-supply/package.json
|
|
@@ -79,7 +79,7 @@ this settled TRUE-82138 in one step on 2026-09-29 (bundle carried all three stri
|
|
|
79
79
|
|
|
80
80
|
**Why the exact `--mode` matters at runtime.** `import.meta.env.MODE` (the `--mode` value) is baked into the bundle and read at **runtime** by the auth flow: `useAuthenticationFlow.ts` sends it as the `environment` value to the `/domains` API endpoint. A generic `vite build` (mode `production`) therefore ships the wrong **environment identity**, not just the wrong URL. Each deployed environment must be built with its exact `--mode` string. Environments differ only by `VITE_API` base URL (per `.env.<mode>`). Per-**client** behavior is separate and resolves at **runtime by hostname** (`useHostnameStore` / `clientSlug`), not at build time — a multi-client shared build, so no client-specific branches.
|
|
81
81
|
|
|
82
|
-
**API base-URL resolution (`api.ts`).** axios `baseURL` resolves as:
|
|
82
|
+
**API base-URL resolution (`src/api/apiBaseUrl.ts`; was in `api.ts` before TRUE-82954).** The api2 base URL (axios `baseURL`, also used by `talosFetch` for token refresh) resolves as:
|
|
83
83
|
|
|
84
84
|
```
|
|
85
85
|
import.meta.env["VITE_API_" + hostname.split(".")[0].toUpperCase()] || import.meta.env.VITE_API
|
|
@@ -123,6 +123,7 @@ Worse, **`.env.development` is git-TRACKED**, so developers keep flipping it to
|
|
|
123
123
|
- **The audit that catches the blox defects — reusable for any blox consumer.** Enumerate the shared lib's `peerDependencies` from `node_modules/<pkg>/package.json`, then check each one for a **real directory** (not a symlink passthrough) in the *app's* `node_modules`. Do this whenever a repo has ever been developed against a linked local checkout.
|
|
124
124
|
|
|
125
125
|
## Change history
|
|
126
|
+
- 2026-10-20 — `VITE_API` read moved from `api.ts` to `src/api/apiBaseUrl.ts` (TRUE-82954) (tcox).
|
|
126
127
|
- 2026-10-01 — Added the "prove the change reached the environment" bundle-fetch check (curl the
|
|
127
128
|
`/assets/index-<hash>.js`, grep for a unique string, read `last-modified`), and recorded the
|
|
128
129
|
`amplify.yml` deletion recurrence on `_sandbox-client` (commit `37f7aeb`, 2026-09-29) carried in by a
|
|
@@ -5,7 +5,7 @@ project: _Underscore
|
|
|
5
5
|
client: shared
|
|
6
6
|
type: standard
|
|
7
7
|
status: active
|
|
8
|
-
updated: 2026-10-
|
|
8
|
+
updated: 2026-10-20
|
|
9
9
|
owners: [jcardinal, apeterson, tcox]
|
|
10
10
|
files: []
|
|
11
11
|
related:
|
|
@@ -424,8 +424,11 @@ memory.
|
|
|
424
424
|
`searchForWorkspaceRoot(process.cwd())`, never a hardcoded `path.resolve(__dirname)`, or you
|
|
425
425
|
silently narrow what the dev server will serve. Mechanics:
|
|
426
426
|
[local-link-into-a-consumer-app](../apps/toga-blox/workflows/local-link-into-a-consumer-app.md) §2c.
|
|
427
|
-
- **MUST: one env-read chokepoint.** Read `
|
|
428
|
-
|
|
427
|
+
- **MUST: one env-read chokepoint per variable.** Read each `VITE_*` URL in exactly **one** small
|
|
428
|
+
module that does not import the blox barrel (see §17), so non-axios code and Vitest can import it
|
|
429
|
+
(the barrel pulls `dist/global.css` → `Unknown file extension ".css"`). desk: `VITE_API` in
|
|
430
|
+
`src/api/api.ts`. supply (TRUE-82954): `VITE_API` in `src/api/apiBaseUrl.ts` (imported by `api.ts`),
|
|
431
|
+
`VITE_TALOS_API` in `src/api/talosConfig.ts`.
|
|
429
432
|
- **SHOULD: ESLint flat config**, verified identical in both apps:
|
|
430
433
|
|
|
431
434
|
```js
|
|
@@ -543,6 +546,8 @@ const instance = createAxiosInstance({ baseURL, onLogout });
|
|
|
543
546
|
setAxiosInstance(instance);
|
|
544
547
|
```
|
|
545
548
|
|
|
549
|
+
(supply, TRUE-82954: `baseURL` is imported from `./apiBaseUrl` instead of computed inline — same expression, see §11.)
|
|
550
|
+
|
|
546
551
|
blox's API helpers **throw "No axios instance registered"** if any data hook or component runs
|
|
547
552
|
first. The order is: create → set → *then* mount. (See `../apps/toga-blox/features/api-client.md`.)
|
|
548
553
|
|
|
@@ -593,7 +598,7 @@ per-environment blox channel, `--legacy-peer-deps`, Node/heap — is owned by
|
|
|
593
598
|
|
|
594
599
|
The one code-side convention worth stating: both apps read **`VITE_API`** — plus an optional
|
|
595
600
|
per-host **`VITE_API_<FIRST-HOSTNAME-SEGMENT>`** override — in **exactly one module**
|
|
596
|
-
(`src/api/api.ts`, §14).
|
|
601
|
+
(desk: `src/api/api.ts`; supply: `src/api/apiBaseUrl.ts`, imported by `api.ts` — §11, §14).
|
|
597
602
|
|
|
598
603
|
## 18. Client-side multi-tenant isolation — **MUST** *(cto-added)*
|
|
599
604
|
|
package/knowledge/INDEX.md
CHANGED
|
@@ -22,7 +22,7 @@ _Auto-generated by `knowledge.js index`. Do not hand-edit._
|
|
|
22
22
|
## 2.0 framework
|
|
23
23
|
|
|
24
24
|
- **_underscore** (_Underscore) _(framework core)_ — 111 doc(s) → [2.0/apps/_underscore/INDEX.md](2.0/apps/_underscore/INDEX.md)
|
|
25
|
-
- **worker2** (Worker) —
|
|
25
|
+
- **worker2** (Worker) — 92 doc(s) → [2.0/apps/worker2/INDEX.md](2.0/apps/worker2/INDEX.md)
|
|
26
26
|
- **api2** (API) — 27 doc(s) → [2.0/apps/api2/INDEX.md](2.0/apps/api2/INDEX.md)
|
|
27
27
|
- **dbchanges2** (Database Changes) _(framework core)_ — 22 doc(s) → [2.0/apps/dbchanges2/INDEX.md](2.0/apps/dbchanges2/INDEX.md)
|
|
28
28
|
- **toga2-supply** (TOGa Supply) — 9 doc(s) → [2.0/apps/toga2-supply/INDEX.md](2.0/apps/toga2-supply/INDEX.md)
|
|
@@ -30,7 +30,7 @@ _Auto-generated by `knowledge.js index`. Do not hand-edit._
|
|
|
30
30
|
- **apiproxy** (API Proxy) — 1 doc(s) → [2.0/apps/apiproxy/INDEX.md](2.0/apps/apiproxy/INDEX.md)
|
|
31
31
|
- **toga2-view** (TOGa View Frontend) — 12 doc(s) → [2.0/apps/toga2-view/INDEX.md](2.0/apps/toga2-view/INDEX.md)
|
|
32
32
|
- **toga2-hub** (TOGa Hub) — 2 doc(s) → [2.0/apps/toga2-hub/INDEX.md](2.0/apps/toga2-hub/INDEX.md)
|
|
33
|
-
- **toga2-commerce** (TOGa Commerce) —
|
|
33
|
+
- **toga2-commerce** (TOGa Commerce) — 33 doc(s) → [2.0/apps/toga2-commerce/INDEX.md](2.0/apps/toga2-commerce/INDEX.md)
|
|
34
34
|
- **toga25-supply** (TOGa 2.5 Supply) — 26 doc(s) → [2.0/apps/toga25-supply/INDEX.md](2.0/apps/toga25-supply/INDEX.md)
|
|
35
35
|
- **toga25-iq** (TOGa IQ) — 1 doc(s) → [2.0/apps/toga25-iq/INDEX.md](2.0/apps/toga25-iq/INDEX.md)
|
|
36
36
|
- **toga25-desk** (TOGa Desk 2.5) — 1 doc(s) → [2.0/apps/toga25-desk/INDEX.md](2.0/apps/toga25-desk/INDEX.md)
|
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
| [Compass Canada — Item-Fulfillment TableViews (dead-Core-id 500, rebuilt onto the USA shape)](features/item-fulfillment-tableviews.md) | 2.0 | Why Compass Canada's item-fulfillment TableViews 500'd on dead Core ids and how they were rebuilt onto the USA shape; open when a Canada TableView 500s on /meta |
|
|
9
9
|
| [Compass Canada: PO-to-vendor transmission monitor (Monitor/Compass/CompassCanadaPurchaseOrderTransmissionsToVendors)](features/oneuptime-po-transmissions-to-vendors-monitor.md) | 2.0 | worker2 OneUptime push monitor for Compass Canada POs the 1.0 cron has not yet sent to the vendor (Grand & Toy), plus how the Canada PO transmit cron differs fr |
|
|
10
10
|
| [Compass Canada — order fulfillment status (standard base rule, no Canada override)](features/order-fulfillment-status-per-line.md) | 2.0 | Canada-specific facts for the shared Compass order fulfillment status (standard base rule since TRUE-79669); open before touching Canada order status or Canada |
|
|
11
|
+
| [Compass Canada Persona Model (sector-granted vs hand-assigned)](features/persona-model.md) | 2.0 | Where each `Client_CompassCanada` persona comes from: PEOPLE cron (only "All"), sector `Locations_Personas`, or hand-assigned with nothing keeping it in step. |
|
|
11
12
|
| [Compass Canada](profile.md) | 2.0 | Compass Canada is the Canadian arm of the Compass account — a separate TOGA tenant, related to but distinct from Compass USA. |
|
|
12
13
|
| [Copying a Compass USA user into the Canada tenant (cross-tenant hand-add)](workflows/copying-a-us-user-into-the-canada-tenant.md) | 2.0 | Giving a Compass **USA** person a login on the Canada TechHub portal: the guarded insert chain, the uuid role/persona mapping, and the worker2 guard that stops |
|
|
13
14
|
| [Grand & Toy ASN Backfill (cXML replay + CSV-to-JSON)](workflows/grand-and-toy-asn-backfill.md) | 2.0 | How to recover Compass Canada shipments whose ASN never landed (cXML replay, CSV-to-JSON, or SQL line backfill); open when replaying a G&T outage or repairing a |
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Compass Canada Persona Model (sector-granted vs hand-assigned)
|
|
3
|
+
framework: "2.0"
|
|
4
|
+
project: _Underscore
|
|
5
|
+
client: compass-canada
|
|
6
|
+
type: client-feature
|
|
7
|
+
status: active
|
|
8
|
+
updated: 2026-10-20
|
|
9
|
+
owners: [bala]
|
|
10
|
+
files:
|
|
11
|
+
- worker2/Worker/Client/Compass/PeopleFile.php
|
|
12
|
+
- "dbchanges2/Client_CompassCanada/2025-12-19 - Personas.sql"
|
|
13
|
+
- "dbchanges2/Client_CompassCanada/2026-01-16 - insert_locations_personas.sql"
|
|
14
|
+
- worker/crons/toga2/compasscanada/Imports2.0/create_persona.php
|
|
15
|
+
related:
|
|
16
|
+
- ../profile.md
|
|
17
|
+
- ../../compass-usa/features/persona-model-and-levy-gating.md
|
|
18
|
+
- ../../../2.0/apps/_underscore/features/effective-persona-resolution.md
|
|
19
|
+
- ../workflows/copying-a-us-user-into-the-canada-tenant.md
|
|
20
|
+
---
|
|
21
|
+
Where each `Client_CompassCanada` persona comes from: PEOPLE cron (only "All"), sector `Locations_Personas`, or hand-assigned with nothing keeping it in step. Open before answering "who sees what" in Canada or adding a Canada persona.
|
|
22
|
+
|
|
23
|
+
## Summary
|
|
24
|
+
|
|
25
|
+
Prod `Client_CompassCanada` (verified 2026-10-20): **34 personas**, **5,484** active users. Canada has
|
|
26
|
+
**no** Levy, Non-Levy or Corporate/Field logic; those are Compass USA only
|
|
27
|
+
([US persona model](../../compass-usa/features/persona-model-and-levy-gating.md)). Persona ids mean
|
|
28
|
+
different things per tenant (CA 24 = "ESFM - CGC", not Levy). Personas were created by
|
|
29
|
+
`dbchanges2/Client_CompassCanada/2025-12-19 - Personas.sql` and the 1.0
|
|
30
|
+
`worker/crons/toga2/compasscanada/Imports2.0/create_persona.php`.
|
|
31
|
+
|
|
32
|
+
## How it works (three sources)
|
|
33
|
+
|
|
34
|
+
| Source | Personas | Kept in step by |
|
|
35
|
+
|---|---|---|
|
|
36
|
+
| PEOPLE cron (`PeopleFile.php`, CA branch) | **All (1)** | Nightly cron |
|
|
37
|
+
| `Locations_Personas` on sector nodes | Sector personas **2-26** and **30** | Location data. Sector nodes use two-digit `c_erpEntityId` codes (e.g. `05` Canteen, `11` Quebec, `17` / `24` Compass One) |
|
|
38
|
+
| Hand-assigned in `Users_Personas` | Managers/VIPs (33), MacBooks (32), Admin Canada (31), Provisioning (29), CDL & Compass Network (28), WasteNot (27), Executive Assistance (34) | **Nothing.** No job adds or removes them |
|
|
39
|
+
|
|
40
|
+
The effective set is the usual UNION (direct + own location + 5-hop sector), see
|
|
41
|
+
[Effective persona resolution](../../../2.0/apps/_underscore/features/effective-persona-resolution.md).
|
|
42
|
+
|
|
43
|
+
## Gotchas
|
|
44
|
+
|
|
45
|
+
- **11 sector personas grant no kits at all:** 3, 4, 5, 6, 9, 10, 11, 12, 13, 23, 26. Users under those
|
|
46
|
+
sectors see only what "All" and any hand-assigned persona give them.
|
|
47
|
+
- **Managers/VIPs (33) drifts from reality.** 984 users hold it (19 kits), but only **858 of 1,146**
|
|
48
|
+
users who manage someone have it. It is hand-assigned, so new managers never get it.
|
|
49
|
+
- **Admin Canada (31)** carries the full catalogue (33 kits); its category gap is in the
|
|
50
|
+
[profile](../profile.md).
|
|
51
|
+
- **The CA branch of `PeopleFile` only adds/removes "All"** (Levy rule kept verbatim, never fires in
|
|
52
|
+
Canada). The US-only VIP (35) and persona-30 branches are not tenant-gated; see the latent gaps in
|
|
53
|
+
the [US persona model](../../compass-usa/features/persona-model-and-levy-gating.md#persona-assignment-by-tenant).
|
|
54
|
+
- `PeopleFile.php` has a literal SFTP credential in its class constants. Never paste its constants.
|
|
55
|
+
|
|
56
|
+
## Change history
|
|
57
|
+
- 2026-10-20 — Created from a read-only prod check (TRUE-82956): 34 personas / 5,484 users, the three
|
|
58
|
+
persona sources, 11 sector personas with no kits, Managers/VIPs (33) coverage gap. (bala)
|
|
@@ -19,7 +19,7 @@ project: _Underscore
|
|
|
19
19
|
client: compass-canada
|
|
20
20
|
type: profile
|
|
21
21
|
status: active
|
|
22
|
-
updated: 2026-10-
|
|
22
|
+
updated: 2026-10-20
|
|
23
23
|
owners: [jcardinal, bala, tcox, apeterson, ajean, mhammontree, rgirish]
|
|
24
24
|
files: []
|
|
25
25
|
related:
|
|
@@ -28,6 +28,7 @@ related:
|
|
|
28
28
|
- features/grand-and-toy-asn-import.md
|
|
29
29
|
- workflows/grand-and-toy-asn-backfill.md
|
|
30
30
|
- workflows/copying-a-us-user-into-the-canada-tenant.md
|
|
31
|
+
- features/persona-model.md
|
|
31
32
|
- ../../2.0/apps/api2/features/cxml-shipnotice-gateway.md
|
|
32
33
|
- ../compass-usa/features/approval-decision-flow.md
|
|
33
34
|
- ../compass-usa/features/stranded-approval-reassignment.md
|
|
@@ -222,13 +223,9 @@ to but distinct from Compass USA. Like Compass USA it spans the **2.0** commerce
|
|
|
222
223
|
- **Storefront French localization** (labels, submenus, orders-for-others, Get Support illustration) is
|
|
223
224
|
driven by the COMPASSCANADA FIELDS JSON; the registry language key is **`fr-CA`** (not `fr`). See
|
|
224
225
|
[Client Fields](../../2.0/apps/toga2-commerce/features/client-fields.md).
|
|
225
|
-
- **
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
(Levy users lose persona 1, non-Levy users gain it; there is no Non-Levy persona). Verified
|
|
229
|
-
unreachable by the Levy path: `Client_CompassCanada` has **zero** locations with
|
|
230
|
-
`c_erpEntityId = 'K00000'` and **zero** users with either hardcoded Levy personnel number. See
|
|
231
|
-
[Persona Model & Levy-Sector Gating](../compass-usa/features/persona-model-and-levy-gating.md).
|
|
226
|
+
- **Personas: 34, from three sources** (PEOPLE cron only manages "All"; sector personas via
|
|
227
|
+
`Locations_Personas`; the rest hand-assigned with nothing keeping them in step). No Levy, Non-Levy
|
|
228
|
+
or Corporate/Field logic in Canada. See [Canada Persona Model](features/persona-model.md).
|
|
232
229
|
- **Known production defect — My Account renders a blank Persona field.** Canada is the only tenant
|
|
233
230
|
with `languageSwitcher.isEnabled = true`, and its "no saved language" login branch (taken by ~98%
|
|
234
231
|
of logins — only **69 of 4,655** active users have a stored language) was the one `setUser` call in
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
| [Compass Approval-Decision Flow (Notifications & Manager Reassignment)](features/approval-decision-flow.md) | 2.0 | Compass sales-order approval-decision / notification flow — approval+CC email lists, manager reassignment, settings-driven manager (VIP) auto-approve, EN/FR loc |
|
|
6
6
|
| [Compass ASN → ItemFulfillment Auto-Creation](features/asn-to-item-fulfillment.md) | 2.0 | Compass USA ASN posting auto-creates the ItemFulfillment on the upstream SalesOrder and propagates tracking at every granularity; open when working ASN→IF creat |
|
|
7
7
|
| [Compass Contact Email Resolution & Crossed `primaryContactEmailAddressId` Pointers](features/contact-email-resolution.md) | 2.0 | Any Compass code reading a contact's email must assert the email row belongs to that contact (~14% of pointers are crossed); open before reading Compass contact |
|
|
8
|
-
| [Corporate vs Field laptop personas
|
|
8
|
+
| [Corporate vs Field laptop personas (Levy/Compass x Corporate/Field, PEOPLE cron)](features/corporate-vs-field-laptop-personas.md) | 2.0 | LIVE since 2026-10-13 (TRUE-82667): `PeopleFile` gives every US user a Corporate or Field laptop persona (Levy and Compass halves) by cost center / sector `J000 |
|
|
9
9
|
| [Cost Centers — Unit Locations, numeric-only policy](features/cost-centers.md) | 2.0 | A Compass "cost center" is a Unit-level Location keyed by `c_erpSystemEntityId` (NOT a `CostCenters` row), numeric-only since 2026-07-06; open for cost-center d |
|
|
10
10
|
| [Compass isFulfillable — Data Quality & the Type-Derived Rule](features/isfulfillable-data-quality-and-type-rule.md) | 2.0 | Why Compass storefront Qty-Fulfilled cells stay dead — `Items.isFulfillable` NULLs are structurally unreachable, some set values are wrong, and the type-derived |
|
|
11
11
|
| [Compass Item Catalogs (Compass vs Agilant) & the Duplicate Items the NetSuite Sync Creates](features/item-catalogs-and-duplicate-items.md) | 1.0 | `Client_Compass.Items` is split across catalogs (Compass vs Agilant) and the NetSuite sync creates duplicate rows for one product; open first whenever a Compass |
|
|
@@ -1,90 +1,94 @@
|
|
|
1
1
|
---
|
|
2
|
-
title: Corporate vs Field laptop personas
|
|
2
|
+
title: Corporate vs Field laptop personas (Levy/Compass x Corporate/Field, PEOPLE cron)
|
|
3
3
|
framework: "2.0"
|
|
4
|
-
|
|
4
|
+
repo: worker2
|
|
5
|
+
project: Worker
|
|
5
6
|
client: compass-usa
|
|
6
7
|
type: client-feature
|
|
7
|
-
status:
|
|
8
|
-
updated: 2026-10-
|
|
8
|
+
status: active
|
|
9
|
+
updated: 2026-10-20
|
|
9
10
|
owners: [bala]
|
|
10
11
|
files:
|
|
11
12
|
- worker2/Worker/Client/Compass/PeopleFile.php
|
|
13
|
+
- "dbchanges2/Client_Compass/2026-10-12a - PersonaCorporateField.sql"
|
|
12
14
|
related:
|
|
13
15
|
- ./persona-model-and-levy-gating.md
|
|
14
16
|
- ./location-hierarchy-and-sectors.md
|
|
17
|
+
- ./cost-centers.md
|
|
15
18
|
- ../workflows/persona-refactor-migration.md
|
|
19
|
+
- ../workflows/granting-persona-bundle-access.md
|
|
16
20
|
- ../../../2.0/apps/_underscore/features/effective-persona-resolution.md
|
|
17
21
|
- ../profile.md
|
|
18
22
|
---
|
|
19
|
-
|
|
23
|
+
LIVE since 2026-10-13 (TRUE-82667): `PeopleFile` gives every US user a Corporate or Field laptop persona (Levy and Compass halves) by cost center / sector `J00000`. Open before touching laptop kits, the corporate cost-center lists, or the four segment personas.
|
|
20
24
|
|
|
21
25
|
## Summary
|
|
22
26
|
|
|
23
|
-
Compass
|
|
24
|
-
|
|
25
|
-
|
|
27
|
+
Compass split the standard laptop into a **Corporate** kit and a cheaper **Field** kit. Field is the
|
|
28
|
+
default; Corporate is decided by cost center (and, for Compass, by sector `J00000`). Four personas
|
|
29
|
+
carry the kits, all assigned **directly in `Users_Personas`** by the nightly PEOPLE cron
|
|
30
|
+
(`worker2/Worker/Client/Compass/PeopleFile.php`, commit `759a05c`, in `_production`). The one-time
|
|
31
|
+
backfill was `dbchanges2/Client_Compass/2026-10-12a - PersonaCorporateField.sql`.
|
|
26
32
|
|
|
27
|
-
|
|
33
|
+
The earlier design (one `Locations_Personas` row on sector location 52) was **not** built.
|
|
28
34
|
|
|
29
|
-
##
|
|
35
|
+
## The four personas (prod `Client_Compass`, verified 2026-10-20)
|
|
30
36
|
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
single location. Assign it in `worker2/Worker/Client/Compass/PeopleFile.php` as the default in
|
|
38
|
-
`Users_Personas`, skipped for anyone under `J00000` — mirroring the existing Levy branch.
|
|
39
|
-
3. **Rejected alternative:** one `Locations_Personas` row per *other* sector (79 of them). It
|
|
40
|
-
silently misses any new sector Compass adds, which is the exact failure the feed is designed to
|
|
41
|
-
avoid.
|
|
42
|
-
4. **No `isActive` filter is needed.** No inactive cost center under `J00000` has a single active
|
|
43
|
-
user, so attaching at the sector only ever reaches real people.
|
|
37
|
+
| Persona | id | number | Kit | Active users |
|
|
38
|
+
|---|---|---|---|---|
|
|
39
|
+
| Levy Corporate | 41 | 43 | 189 LEVY - HP ZBOOK 8 | 244 |
|
|
40
|
+
| Levy Field | 42 | 44 | 248 LEVY - Field Standard Laptop | 5,159 |
|
|
41
|
+
| Compass Corporate | 43 | 45 | 187 Compass Standard HP ZBOOK 8 | 5,028 |
|
|
42
|
+
| Compass Field | 44 | 46 | 247 Compass Field Standard Laptop | 65,447 |
|
|
44
43
|
|
|
45
|
-
|
|
44
|
+
- Kit **189 moved off Levy Purchaser (24)** and **187 off Non-Levy (40)**. Only those two moved.
|
|
45
|
+
- Kit **249 NON COMPASS - Field Standard Laptop** sits on persona 5 (HTT and MITS team) and 12 (MITS
|
|
46
|
+
Team). MITS Team holds every kit, so it also has 247 and 248.
|
|
47
|
+
- Ids and numbers differ (the migration header lists numbers 43-46). `PeopleFile` resolves all four
|
|
48
|
+
**by name** (`PERSONA_NAME_US_LEVY_CORPORATE` etc.). See the id/number table in
|
|
49
|
+
[Persona Model](./persona-model-and-levy-gating.md#the-persona-set-us).
|
|
46
50
|
|
|
47
|
-
|
|
48
|
-
**Base** and carries only the 9 kits Levy also holds. See
|
|
49
|
-
[Persona Refactor](../workflows/persona-refactor-migration.md) for the three-way split.
|
|
51
|
+
## How it works
|
|
50
52
|
|
|
51
|
-
|
|
52
|
-
|---|---|---|
|
|
53
|
-
| 187 | Compass Standard HP ZBOOK 8 Laptop (Preferred) | |
|
|
54
|
-
| 188 | Compass Hybrid HP Ebook 8 Flip (Travel) | `isActive = 0` |
|
|
55
|
-
| 192 | HP PRO MINI 400 G9 | |
|
|
56
|
-
| 195 | Compass Developer HP ZBOOK POWER | |
|
|
53
|
+
`findSegmentPersonaIdsForUser()` runs for every US user on each PEOPLE run (existing and new users):
|
|
57
54
|
|
|
58
|
-
**
|
|
59
|
-
|
|
55
|
+
1. **Levy half** when the user's effective persona set holds Levy Purchaser (24):
|
|
56
|
+
cost center in `LEVY_CORPORATE_COST_CENTERS` (33 values from Compass) -> Levy Corporate, else Levy Field.
|
|
57
|
+
2. **Compass half** when the user should hold Non-Levy: location under sector `J00000` (Corporate
|
|
58
|
+
Indirect OH, `isLocationUnderSector()` walk, US only) **or** cost center in
|
|
59
|
+
`COMPASS_CORPORATE_COST_CENTERS` (10 values: 4018, 22772, 26963, 28184, 31335, 45781, 47258,
|
|
60
|
+
54188, 62230, 62937) -> Compass Corporate, else Compass Field.
|
|
61
|
+
3. Cost center = `Users.c_erpEntityId` from the file. A user who moves cost center **swaps halves on
|
|
62
|
+
the next run** (insert the wanted one, delete the other three).
|
|
63
|
+
4. A Levy user who also holds Non-Levy (Levy VIPs) gets **one of each** half.
|
|
64
|
+
5. **Null-name guard:** until all four personas exist in that environment, nothing is added or
|
|
65
|
+
removed. That is what made "deploy worker2 first" safe.
|
|
60
66
|
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
**
|
|
65
|
-
laptops" that stay corporate-only. Settle that before writing the migration.
|
|
67
|
+
The effective set comes from `buildEffectivePersonaIds()`: direct `Users_Personas` + own-location +
|
|
68
|
+
5-hop sector `Locations_Personas` (`findSectorLocationId()`) + Base, using **this run's** VIP/CDL
|
|
69
|
+
flags (a user who just lost VIP drops it). Derived personas (Non-Levy and the four halves) are
|
|
70
|
+
**removed** from that set, so a wrong grant cannot justify itself.
|
|
66
71
|
|
|
67
72
|
## Gotchas
|
|
68
73
|
|
|
69
|
-
- **
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
-
|
|
80
|
-
|
|
74
|
+
- **The rule lives in two places. Change one, change both:** `PeopleFile.php` and the
|
|
75
|
+
`2026-10-12a - PersonaCorporateField.sql` migration header/phases. The cron re-applies its copy
|
|
76
|
+
nightly, so drift silently undoes a manual fix.
|
|
77
|
+
- **The cost-center lists are hardcoded and temporary.** Compass says a PEOPLE-file flag will replace
|
|
78
|
+
them. Until then a new corporate cost center needs a code change, not a DB row.
|
|
79
|
+
- **Levy Corporate is by cost center only.** Levy's home office is the **district** `K9902`, not a
|
|
80
|
+
sector, so there is no sector walk for Levy. See [Location Hierarchy](./location-hierarchy-and-sectors.md).
|
|
81
|
+
- **Never hand-add a half in `Users_Personas` or `Locations_Personas`.** The next run deletes a direct
|
|
82
|
+
row that does not match the rule. A location row still grants the half at read time, but the cron
|
|
83
|
+
never removes it, so those users end up holding both halves.
|
|
84
|
+
- **The DB does not restrict what a persona sees**; the `toga2-commerce` storefront is the gate. A
|
|
85
|
+
grant is three tables. See [granting a bundle](../workflows/granting-persona-bundle-access.md).
|
|
86
|
+
- `PeopleFile.php` has a literal SFTP credential in its class constants. Never paste its constants.
|
|
81
87
|
|
|
82
88
|
## Change history
|
|
83
|
-
- 2026-10-
|
|
84
|
-
`
|
|
85
|
-
`J00000
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
conflict with the refactor doc — it was simply taken two days before the refactor went live on
|
|
90
|
-
2026-09-17. Removed; the refactor docs are correct. Nothing built. (bala)
|
|
89
|
+
- 2026-10-20 — Rewritten from "designed, not built" to the shipped rule (TRUE-82667, worker2
|
|
90
|
+
`759a05c`, dbchanges2 `2026-10-12a`): four by-name personas assigned in `Users_Personas`, cost-center
|
|
91
|
+
lists + `J00000` walk, Field default, null-name guard, derived personas excluded from the effective
|
|
92
|
+
set. Prod counts verified 2026-10-20. The `Locations_Personas`-on-location-52 design was dropped. (bala)
|
|
93
|
+
- 2026-10-05 — Created as a deferred design (Corporate at sector `J00000` via `Locations_Personas`,
|
|
94
|
+
Field in `PeopleFile`); laptop kits verified on Non-Levy (40) and MITS Team (12). (bala)
|
|
@@ -118,7 +118,7 @@ All of them are flagged Active and all have **zero users**. Drop them before any
|
|
|
118
118
|
## Related
|
|
119
119
|
- [Cost Centers — Unit Locations, numeric-only policy](./cost-centers.md)
|
|
120
120
|
- [Persona Model & Levy-Sector Gating](./persona-model-and-levy-gating.md)
|
|
121
|
-
- [Corporate vs field laptop personas (
|
|
121
|
+
- [Corporate vs field laptop personas (live, uses the `J00000` walk)](./corporate-vs-field-laptop-personas.md)
|
|
122
122
|
- [Effective persona resolution](../../../2.0/apps/_underscore/features/effective-persona-resolution.md)
|
|
123
123
|
|
|
124
124
|
## Change history
|
|
@@ -6,7 +6,7 @@ project: Worker
|
|
|
6
6
|
client: compass-usa
|
|
7
7
|
type: client-feature
|
|
8
8
|
status: active
|
|
9
|
-
updated: 2026-10-
|
|
9
|
+
updated: 2026-10-20
|
|
10
10
|
owners: [bala, ajean, mhammontree]
|
|
11
11
|
files:
|
|
12
12
|
- worker2/Worker/Client/Compass/PeopleFile.php
|
|
@@ -43,22 +43,29 @@ below.
|
|
|
43
43
|
|
|
44
44
|
## The persona set (US)
|
|
45
45
|
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
|
53
|
-
|
|
|
54
|
-
|
|
|
55
|
-
|
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
46
|
+
Three-way refactor live since 2026-09-11 ([Persona Refactor](../workflows/persona-refactor-migration.md));
|
|
47
|
+
Corporate/Field split live since 2026-10-13. Prod `Client_Compass` 2026-10-20: **43 personas** (id 33
|
|
48
|
+
does not exist), **75,724** active users.
|
|
49
|
+
|
|
50
|
+
| Persona | id | number | Meaning |
|
|
51
|
+
|---|---|---|---|
|
|
52
|
+
| Base (was "All") | **1** | | The **9** kits Levy and non-Levy both hold (81, 82, 84, 85, 105, 115, 120, 165, 166), plus the 71 items / 11 categories Levy also holds. **Every US user holds it.** |
|
|
53
|
+
| Levy Purchaser | **24** | 27 | Levy's own **4** kits: 193, 213, 231, 242 (189 moved to Levy Corporate). |
|
|
54
|
+
| Non-Levy | **by name** — 40 | 42 | The other persona-1 kits / 123 items / 3 categories, minus 187 (moved to Compass Corporate). |
|
|
55
|
+
| Levy / Compass Corporate / Field | **by name** — 41-44 | 43-46 | One laptop kit each. See [Corporate vs Field](./corporate-vs-field-laptop-personas.md). |
|
|
56
|
+
| VIP Role | 35 | 38 | VIP add-on. Carries the manager auto-approve limit (`PersonaGlobalSettings` setting 18 = `'5000'`) — see [Approval-Decision Flow](approval-decision-flow.md). |
|
|
57
|
+
| Macbook and Surface (CDL) | 30 | 33 | `@compassdigital.io` + VIP users. |
|
|
58
|
+
| CDL Eats Technology | 32 | 35 | |
|
|
59
|
+
|
|
60
|
+
**Never hardcode a derived persona id, and never assume id = number.** Number is assigned as
|
|
61
|
+
`MAX(number)+1` under 100, so ids and numbers drift apart (number 40 is "MyDining- KDS", id 38).
|
|
62
|
+
Every environment gets different ids, which is what name-resolution protects against. `PeopleFile`
|
|
63
|
+
still uses literal id constants for 1 / 24 / 30 / 35.
|
|
64
|
+
|
|
65
|
+
**Location-granted personas can be wider than the name suggests.** Crothall Purchasing Team (**21**)
|
|
66
|
+
is attached by `Locations_Personas` to the whole Crothall sector `B00000` (location **57**), same as
|
|
67
|
+
Crothall Sector persona 6, so all **7,250** Crothall users see Virtual Manager kits **118 / 148**.
|
|
68
|
+
Open question for Compass (2026-10-20) whether that is intended.
|
|
62
69
|
|
|
63
70
|
`PeopleFile` resolves it at runtime:
|
|
64
71
|
|
|
@@ -116,10 +123,14 @@ users get it from their *own* location.
|
|
|
116
123
|
**US** (`$tenantKey === 'US'`):
|
|
117
124
|
- Always ensure **Base (1)** — never delete it. Base is printers, which every US user should see,
|
|
118
125
|
Levy included.
|
|
119
|
-
- **
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
-
|
|
126
|
+
- Build the **effective** set (`buildEffectivePersonaIds()`): direct + own-location + 5-hop sector
|
|
127
|
+
`Locations_Personas` + Base, using this run's VIP/CDL flags, with derived personas (Non-Levy and
|
|
128
|
+
the four Corporate/Field halves) removed so a wrong grant cannot justify itself.
|
|
129
|
+
- **Non-Levy**, only when `$nonLevyPersonaId !== null`: `shouldUserHaveNonLevy()` is true unless the
|
|
130
|
+
effective set is exactly Base + Levy. Insert or delete to match.
|
|
131
|
+
- **Corporate/Field half** (`findSegmentPersonaIdsForUser()`), only when all four exist — see
|
|
132
|
+
[Corporate vs Field](./corporate-vs-field-laptop-personas.md).
|
|
133
|
+
- New users get the same three steps (Base, Non-Levy, half) with an empty direct set.
|
|
123
134
|
|
|
124
135
|
> **⚠ This rule is DUPLICATED in the migration — change one, change both.** `PeopleFile.php` and
|
|
125
136
|
> Phase 6 of `dbchanges2/Client_Compass/2026-09-11 - PersonaRefactor.sql` both skip users whose whole
|
|
@@ -184,6 +195,7 @@ Both are **latent, not live, and not yet fixed** — each needs its own ticket.
|
|
|
184
195
|
constants from this file into tickets, docs, or chat transcripts.
|
|
185
196
|
|
|
186
197
|
## Change history
|
|
198
|
+
- 2026-10-20 — Persona table refreshed from prod (TRUE-82956): ids vs numbers (24/27, 30/33, 32/35, 35/38, 40/42), Levy Purchaser down to 4 kits and Non-Levy minus 187 after the Corporate/Field split (TRUE-82667, live), assignment steps now include the effective set and the Corporate/Field half, Crothall Purchasing Team (21) sector-wide grant flagged as an open question. (bala)
|
|
187
199
|
- 2026-10-09 — Persona 35 now carries the manager auto-approve limit (setting 18); PeopleFile keeps it in exact sync with `c_isVip` (prod 2269/2269), so do not decouple the two (TRUE-82608). (bala)
|
|
188
200
|
- 2026-10-05 — Linked the deferred
|
|
189
201
|
[corporate vs field laptop split](./corporate-vs-field-laptop-personas.md) (Corporate attached at
|
|
@@ -6,8 +6,8 @@ project: TOGa 2.5 Supply
|
|
|
6
6
|
client: nychh
|
|
7
7
|
type: client-feature
|
|
8
8
|
status: active
|
|
9
|
-
updated: 2026-10-
|
|
10
|
-
owners: [apeterson, akhokhani]
|
|
9
|
+
updated: 2026-10-20
|
|
10
|
+
owners: [apeterson, akhokhani, tcox]
|
|
11
11
|
files:
|
|
12
12
|
- dbchanges2/Core/2026-09-02a - TalosAssistantSurfaceSeed.sql
|
|
13
13
|
- dbchanges2/Client_Nychh/2026-09-02a - TalosAssistantEnable.sql
|
|
@@ -60,6 +60,8 @@ The older September 2 record verified only local override teardown/re-run and re
|
|
|
60
60
|
|
|
61
61
|
For launch, inspect the actual production bundle as a non-admin intended user, verify `/users/me` grants active `toga-supply`, discover the assistant and run the shared streaming/isolation suite. Verify stop cancellation, transcript reload and run rejoin separately; code source and successful historical backend calls are insufficient.
|
|
62
62
|
|
|
63
|
+
Do not use `https://nychh.client.sandbox.togasupply.com` or local dev to test Talos today: sandbox-client tokens are rejected by beta Talos (401 "Token expired") and local dev has no `VITE_TALOS_API` (verified 2026-10-20). Details: [Supply integration § Where Talos can be tested](../../../2.0/apps/toga25-supply/features/talos-integration.md).
|
|
64
|
+
|
|
63
65
|
## Gotchas
|
|
64
66
|
|
|
65
67
|
- NYCHH stored assistant `persona_prompt` and persona `system_prompt` diverged (16,362 versus 17,848 characters in the survey). Compass had newer BLP/instructions and an embedding; do not assume publishing one client updates the other.
|
|
@@ -78,4 +80,5 @@ For launch, inspect the actual production bundle as a non-admin intended user, v
|
|
|
78
80
|
|
|
79
81
|
## Change history
|
|
80
82
|
|
|
83
|
+
- 2026-10-20 — Noted Talos cannot be tested on sandbox-client or local dev (TRUE-82954) (tcox).
|
|
81
84
|
- 2026-10-08 — Reconciled local source and dated 2026-10-07 research into the canonical KB (akhokhani).
|
|
@@ -6,8 +6,8 @@ project: TOGa Talos
|
|
|
6
6
|
client: shared
|
|
7
7
|
type: feature
|
|
8
8
|
status: active
|
|
9
|
-
updated: 2026-10-
|
|
10
|
-
owners: [akhokhani]
|
|
9
|
+
updated: 2026-10-20
|
|
10
|
+
owners: [akhokhani, tcox]
|
|
11
11
|
files:
|
|
12
12
|
- talos-backend/libs/aegra-api/src/aegra_api/core/auth_deps.py
|
|
13
13
|
- talos-backend/libs/aegra-api/src/aegra_api/core/auth_middleware.py
|
|
@@ -101,6 +101,7 @@ The shared authorization Redis holds TogaHub entitlements, cancellation and limi
|
|
|
101
101
|
- Org IDs are exact and case-sensitive (`True`, `Towfoundation`, `Compass_Usa`, `Nychh`). Fix a wrong registration in the control plane; never add fuzzy matching.
|
|
102
102
|
- A user who loses a TogaHub persona grant gets 403 even on their old threads.
|
|
103
103
|
- `audience.client` for NYCHH is "NYC Health + Hospitals"; it resolves only because the control-plane `slug` matches it exactly.
|
|
104
|
+
- 401 "Token expired" means **any** TogaHub `/users/me` 401 (`togahub_auth.py`), not only a real expiry. A token issued by a different api2 environment than `TOGAHUB_API_URL` gets it too: beta Talos (`https://api.beta.togahub.com/v2/users/me`) rejects tokens from `api.client.sandbox.togahub.com` this way, with time left on the token (verified 2026-10-20). Check the JWT `iss` against `TOGAHUB_API_URL` before chasing refresh.
|
|
104
105
|
- `aud` in the JWT is not used as an audience check by Talos; the TOGa Platform MCP does enforce audience = forwarded origin ([toga-platform-mcp](toga-platform-mcp.md)).
|
|
105
106
|
- Changing `AUTH_TYPE` to `noop` anywhere reachable from the internet exposes every tenant.
|
|
106
107
|
- The tenant registry caches canonical org IDs; after control-plane edits restart or bust the cache (`tenant_registry.bust_cache`).
|
|
@@ -110,5 +111,6 @@ The shared authorization Redis holds TogaHub entitlements, cancellation and limi
|
|
|
110
111
|
|
|
111
112
|
## Change history
|
|
112
113
|
|
|
114
|
+
- 2026-10-20 — Gotcha: "Token expired" covers any TogaHub 401, incl. a token from another api2 environment (TRUE-82954) (tcox).
|
|
113
115
|
- 2026-10-08 — Corrected custom-auth production support to match startup validation (akhokhani).
|
|
114
116
|
- 2026-10-07 — created from `_beta` code: TogaHub flow (org from `audience.client` canonicalized by org_id or slug), API-key grants, scheduled principal, slug-gated ACL, permission table (akhokhani)
|
|
@@ -6,8 +6,8 @@ project: TOGa Talos
|
|
|
6
6
|
client: shared
|
|
7
7
|
type: feature
|
|
8
8
|
status: active
|
|
9
|
-
updated: 2026-10-
|
|
10
|
-
owners: [akhokhani, apeterson]
|
|
9
|
+
updated: 2026-10-20
|
|
10
|
+
owners: [akhokhani, apeterson, tcox]
|
|
11
11
|
files:
|
|
12
12
|
- talos-backend/aegra.json
|
|
13
13
|
- talos-backend/libs/aegra-api/src/aegra_api/main.py
|
|
@@ -48,6 +48,11 @@ Talos speaks the **LangGraph Agent Protocol**, so use the LangGraph SDK (`@langc
|
|
|
48
48
|
- Browser: `Authorization: Bearer <api2 JWT>` (the token the app already holds; any TogaHub client mapped to a provisioned Talos org works). The browser's real `Origin`/`Referer` are forwarded to TogaHub `/v2/users/me`; they must be absolute and agree.
|
|
49
49
|
- Service: `X-API-Key: aegra_<tenant>_<secret>` with scope `agent:run`, `allowed_model_slugs` and `allowed_graph_ids` — allowed only on `POST /threads` and `POST /threads/{id}/runs/stream`. For one-shot server calls prefer `/api/ai/agent` or `/api/ai/generate` ([ai-service-endpoints](ai-service-endpoints.md)).
|
|
50
50
|
- Details and error strings: [auth-and-access-control](auth-and-access-control.md).
|
|
51
|
+
- Browser token refresh is the client's job: read the bearer per request, refresh it near `exp`, and on a 401 `"Token expired"` refresh and resend **once**. A resend is safe — auth runs before any route, so a 401 created no thread, run or message. Reference: Supply `talosFetch` ([talos-integration](../../../../2.0/apps/toga25-supply/features/talos-integration.md)).
|
|
52
|
+
|
|
53
|
+
### Error body
|
|
54
|
+
|
|
55
|
+
Every `HTTPException` is returned as `{error, message, details}` (`main.py:agent_protocol_exception_handler`), not FastAPI's `{detail}`. Match on `message` (for example `"Token expired"`).
|
|
51
56
|
|
|
52
57
|
### CORS
|
|
53
58
|
|
|
@@ -102,10 +107,12 @@ Upload `POST /files/upload` (multipart; 50 MB in prod), download `GET /files/{id
|
|
|
102
107
|
- `@langchain/langgraph-sdk` ≥ 1.10 declares `@langchain/core` as a required peer; missing it fails Vite dependency optimization with `Could not resolve "@langchain/core/messages"`.
|
|
103
108
|
- `stream_resumable` sent by some clients is not a server field; resumability comes from `on_disconnect: "continue"` plus `Last-Event-ID` rejoin.
|
|
104
109
|
- Older notes saying CORS headers appear only on the preflight are obsolete (verified 2026-10-07).
|
|
110
|
+
- The LangGraph SDK `AsyncCaller` retries **thrown** errors, 5xx and network failures up to 4x, but never 401/403. So a custom `fetch` should return a 401 `Response` (not throw) for "not signed in". Risk, not yet handled: a retried 5xx on `POST /threads/{id}/runs/stream` can create a second run if the server already created the first — open follow-up (TRUE-82954).
|
|
105
111
|
|
|
106
112
|
## Related
|
|
107
113
|
- [aegra-api](aegra-api.md) · [auth-and-access-control](auth-and-access-control.md) · [chat-frontend](../../talos/features/chat-frontend.md) · [toga25-supply talos-integration](../../../../2.0/apps/toga25-supply/features/talos-integration.md) · [toga-blox talos-assistant](../../../../2.0/apps/toga-blox/features/talos-assistant.md)
|
|
108
114
|
|
|
109
115
|
## Change history
|
|
116
|
+
- 2026-10-20 — added error body shape `{error, message, details}`, client token-refresh/one-resend rule and SDK retry behavior (TRUE-82954) (tcox)
|
|
110
117
|
- 2026-10-08 — corrected cross-framework/client links after standalone migration and clarified partial parallel resumes versus complete per-interrupt action decisions (akhokhani)
|
|
111
118
|
- 2026-10-07 — created: single home for the caller contract (moved from aegra-api, toga25-supply and chat-frontend docs); CORS re-verified live (akhokhani)
|
package/package.json
CHANGED