toga-ai 1.0.1096 → 1.0.1097

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.
@@ -6,7 +6,7 @@ project: TOGa Commerce
6
6
  client: shared
7
7
  type: feature
8
8
  status: active
9
- updated: 2026-10-07
9
+ updated: 2026-10-21
10
10
  owners: [tcox, bala, apeterson]
11
11
  files:
12
12
  - src/api/sessionTokens.ts
@@ -27,6 +27,7 @@ related:
27
27
  - 2.0/apps/toga2-commerce/workflows/cypress-testing.md
28
28
  - 2.0/apps/api2/architecture.md
29
29
  - 2.0/apps/_underscore/features/order-for-delegation.md
30
+ - clients/compass-usa/features/people-file-user-lifecycle.md
30
31
  ---
31
32
 
32
33
  How an admin impersonates another user and gets back: the delegated token pair, the parked admin pair in localStorage, and the return path. Open before touching auth tokens, the axios refresh, or the view-as button.
@@ -90,6 +91,7 @@ Seeds a long-lived `ADMIN_ACCESS_JWT`; stubs `/auth/delegator` → `mock-delegat
90
91
  - **Never test "View As is on" with `Object.keys(returnOriginalUser).length > 0`.** Order For parks the same user; use `useIsViewAsActive`.
91
92
  - **Clear the delegated state before any await on return.** `setUser(owner)` / `clearOriginalUser` / `endDelegation` must land before the token refresh or any refetch, or an expired parked token's refresh opens a window where the "parked pair gone while delegated" guard logs the user out (and the banner and cart-clear flicker).
92
93
  - **⚠ Admin View As is now gated by the Order For delegation list.** `canUserOrderForOtherUser` guards `/users/encryptedUser` and `/auth/delegator` for everyone, so View As returns **403 EZ-1** for any target not on the signed-in user's `Users_DelegatorUsers` rows (beta and client-sandbox). The UI shows nothing — `onError` only logs to Sentry; no toast by decision (tcox), since prod carries the full list.
94
+ - **⚠ View As on a target with no `Users_Roles` row strands the admin.** Every call as the target 403s EZ-1 (`/v2/users/encryptedUser`, `/v2/pages/meta`, `/v2/core-parameters`, bundles), the banner shows the **admin's own** name, return does not work, and clearing cookies does not help. Not a token bug. Diagnose: `Logs_<Tenant>.Api` rows with `responseCode = 403`, `responsePayload.audience.user` = target, app TOGa Commerce; then check `Client_<Tenant>.Users_Roles` for that user id. First case: Compass user 311024, created by the PEOPLE import a day earlier (TRUE-82976). Cause + fix: [PEOPLE-file lifecycle](../../../../clients/compass-usa/features/people-file-user-lifecycle.md#gotchas).
93
95
  - **A view-as test must stub `/v2/pages/meta?page=home-page&app=commerce`.** Unstubbed, the real call fails and triggers the enter-view-as rollback, so the spec sees a return-to-admin it did not ask for.
94
96
 
95
97
  ## Open items (out of ticket, not fixed)
@@ -102,6 +104,7 @@ Seeds a long-lived `ADMIN_ACCESS_JWT`; stubs `/auth/delegator` → `mock-delegat
102
104
 
103
105
  ## Change history
104
106
 
107
+ - 2026-10-21 — Gotcha: View As on a role-less target 403s everything and strands the admin; how to diagnose (TRUE-82976). (bala)
105
108
  - 2026-10-07 — Open item: `/users/encryptedUser` permission gap found 2026-09-25 (prod grants to ordinary roles); confirm the delegation gate is live in prod. (jcardinal)
106
109
  - 2026-09-30 — Order For merged onto the TRUE-81991 session layer on `_sandbox-client`: parked pair cleared via `AUTH_STORAGE_KEYS`; return and switch use `clearPersistedQueryCache`; a refresh that lost the identity race now returns the current token instead of `null` (stopped a false logout on return); View As `onError` restores the admin's language and logs out if the parked pair is gone. (tcox)
107
110
  - 2026-09-22 — Refresh identity binding upgraded to an epoch bumped on the view-as swap; `Bearer undefined` swap bug fixed. TRUE-81991 (`e546db3d`). (apeterson)
@@ -6,7 +6,7 @@ project: Worker
6
6
  client: compass-usa
7
7
  type: client-feature
8
8
  status: active
9
- updated: 2026-10-19
9
+ updated: 2026-10-21
10
10
  owners: [bala, ajean, mhammontree]
11
11
  files:
12
12
  - worker2/Worker/Client/Compass/PeopleFile.php
@@ -24,6 +24,7 @@ related:
24
24
  - ../../../2.0/apps/_underscore/features/record-change-audit-log.md
25
25
  - ../../../2.0/apps/_underscore/features/user-email-as-identity-and-password-reset.md
26
26
  - ../../compass-canada/workflows/copying-a-us-user-into-the-canada-tenant.md
27
+ - ../../../2.0/apps/toga2-commerce/features/view-as-session-tokens.md
27
28
  ---
28
29
  The nightly Compass PEOPLE-file cron's Users-row lifecycle (insert/reactivate/deactivate), why it creates duplicate accounts, and its raw-SQL/index traps; open before touching PeopleFile.php or debugging stranded approvals.
29
30
 
@@ -68,6 +69,13 @@ Current order for a **new** user:
68
69
 
69
70
  A `_isNewUser` flag on each staged contact is what separates the two paths; existing users missing a contact keep the old create-then-`UPDATE` route. `insertContact()` is the shared helper both use.
70
71
 
72
+ ### Base roles for new users (same run, restored 2026-10-21)
73
+ Every user gets **Base** + **Compass Base** in `Users_Roles`. Two staging paths, both in the per-row loop:
74
+ - **Existing user** (~line 847-856): stages the missing role ids directly.
75
+ - **New user** (~line 946-956): stages `['_needsUserId' => true, 'username' => ..., 'roleId' => ...]`, same pattern as personas. After the `Users` batch INSERT, the resolver (~line 1288-1302) maps `username` to the new id through the post-insert refresh (`$lookups['users']`).
76
+
77
+ Role ids are looked up by uuid constants `ROLE_UUID_BASE` / `ROLE_UUID_COMPASS_BASE`, never hardcoded: Compass Base is id **7** in `Client_Compass` and **8** in `Client_CompassCanada`.
78
+
71
79
  ### Order of operations inside a single run
72
80
  Within one job the **inserts (and their id refresh) run before the inactivation batch**. That ordering is the hook point for anything that has to bridge an old row to a new one: at insert-refresh time the **new row already has its id** and the **old row is still findable**. A deactivation-time hook would be worse — by then the new row's relationship to the old one has to be rediscovered, and the deactivation path has no model hooks at all.
73
81
 
@@ -105,7 +113,9 @@ The insert-time hook designed 2026-08-12 was built and grew into a reusable engi
105
113
  - **Widening the lookup window is not a free fix.** Removing or extending the grace window would stop new duplicates at source, but also changes which rows the `contactId`-unlinking logic considers and would begin reactivating long-dead accounts. Deliberate deferred item, not an oversight.
106
114
  - **`PeopleFile.php` contains a literal SFTP credential in class constants.** Do not paste class constants from this file into tickets, docs, or chat transcripts.
107
115
  - **⚠ `TEST_MODE` and `DRY_RUN` do different jobs and NEITHER alone is safe — set BOTH to test.** `TEST_MODE` reads a local file instead of SFTP and skips the SFTP delete, but does **not** stop a single database write. `DRY_RUN` skips the DB writes but does **not** stop the SFTP delete — a "safe" dry run still removes the PEOPLE file from `sftp.goagilant.com` and destroys the input you were inspecting. `DRY_RUN` also skips the whole contact/user insert path (it sits inside `if (!self::DRY_RUN)`), so a dry run **cannot** test that code and reports a misleading success. `TEST_FILE_PATH`'s committed value (`//var//www//html//...`) is not where the app lives on Elastic Beanstalk (`/var/app/current/...`).
108
- - **⚠ Read newly inserted user ids with `MAX(id)`, on the transaction connection.** A returning employee usually keeps the **same username** as their dead row, so a plain username lookup after the insert can return the **dead** id. And the new rows are invisible to any other connection until the tenant transaction commits, so the read has to happen on the transaction connection.
116
+ - **⚠ Read newly inserted user ids with `MAX(id)`, on the transaction connection.** A returning employee usually keeps the **same username** as their dead row, so a plain username lookup after the insert can return the **dead** id. And the new rows are invisible to any other connection until the tenant transaction commits, so the read has to happen on the transaction connection. **Open (not fixed, TRUE-82976):** the post-insert refresh SELECT (~line 1150) that fills `$lookups['users']` has **no `ORDER BY`**, and the last row per username wins. New-user roles and personas resolve their `userId` through that map, so they can land on the old inactive row. Fix candidate: `ORDER BY id` so the newest row wins.
117
+ - **⚠ A new user with no `Users_Roles` row 403s on every api2 call** (EZ-1 "You are not authorized to perform this action"). From commit `51cfcb9` (TRUE-76423, 2026-01-29) to 2026-10-21 the new-user role staging was missing, so new users got roles only on the **next** daily run (cron `Client/Compass/PeopleFile/ProcessPeopleFiles`, ~09:15 CT). Users created 2026-10-21 were backfilled by `dbchanges2/Client_Compass/2026-10-21a - NewUsersBaseRoles.sql` (76 users) and `Client_CompassCanada/2026-10-21a - NewUsersBaseRoles.sql` (12 users). Any backfill must be date-scoped: Canada dev account `Jugveer.Randhawa` (id 2) lacks Compass Base **on purpose**. Commerce symptom: [View As](../../../2.0/apps/toga2-commerce/features/view-as-session-tokens.md#gotchas).
118
+ - **The `_needsUserId` resolver drops a staged row silently** when its username is not in the refreshed map. No error, no count. A missing role or persona after a "successful" run can be this.
109
119
  - **`_Model_Client_Logs_Record::addNote()` is a no-op in this cron.** Staged notes are only flushed by api2, so a worker must write its `Record` rows itself. See [Record Change Audit Log](../../../2.0/apps/_underscore/features/record-change-audit-log.md).
110
120
  - **`Client_Compass.Users` is ~176,000 rows with no index on `c_hrEmpPersonnelNbr`, `email`, `c_hrEmpUsername`, or `isActive`.** Any per-row lookup against those columns is a full scan — `EXPLAIN` returns `type=ALL`, 176,018 rows, no possible keys (re-confirmed prod 2026-09-02, same on `Client_CompassCanada`). An index migration is recommended and **still not done**. **The blank-client template is not a safe proxy for this schema**: `dbchanges2/Client/2026-06-03- BLANK_CLIENT_DATABASE.sql` declares `UNIQUE KEY email (email)`, and Compass has diverged from it. Check the live tenant, never the template.
111
121
  - **⚠ There is NO UNIQUE KEY on `Users.email` in either tenant — the database will silently accept a duplicate email.** Prod-verified 2026-09-04. Complete index list on `Users` in **both** `Client_Compass` and `Client_CompassCanada`: `PRIMARY(id)`, `uuid`, `Users_UNIQUE(clientId, homeClientUserId)`, `contactId`, `Users_Users_FK(supervisorUserId)`, `Users_Addresses_FK(addressId)`, `Users_Images_FK(thumbnailImageId)`, `dtCreated`, `dtUpdated`, `Users_c_supportedByUserId`, `fk_users_defaultCostCenterId`. Also the proof of the missing-index gotcha above. **Consequence: any code that writes an email must do its own cross-row duplicate check** — and because email is a login/reset identity in 2.0, a duplicate is a security problem, not just messy data. See [Users.email is a login identity](../../../2.0/apps/_underscore/features/user-email-as-identity-and-password-reset.md).
@@ -133,6 +143,7 @@ The insert-time hook designed 2026-08-12 was built and grew into a reusable engi
133
143
  - [Users.email as login identity](../../../2.0/apps/_underscore/features/user-email-as-identity-and-password-reset.md)
134
144
 
135
145
  ## Change history
146
+ - 2026-10-21 — TRUE-82976: restored new-user Base + Compass Base role staging (`_needsUserId`), lost in `51cfcb9`; prod backfill 152 US / 24 CA `Users_Roles` rows; logged the open no-`ORDER BY` refresh risk. (bala)
136
147
  - 2026-10-19 — TRUE-82932: files for Oct 17/18/19 missed while the shared SFTP server banned worker2; no email = import never ran; linked the shared-server doc. No code changed. (bala)
137
148
  - 2026-10-06 — Noted cart Add New User as a second source of duplicate `Users` rows (now checks email too, TRUE-82423). (bala)
138
149
  - 2026-09-23 — Recorded that the import is **not** all-or-nothing (post-commit email + SFTP delete run outside the tenant transaction; job 1076884 failed yet kept 460 US / 26 CA users). TRUE-82230 confirmed in prod: 543 `Client_Compass` users created, **zero** null-or-0 `contactId`, two manual ACTION runs ~18s each — needed because the 09:15 cron row (job 1784367) was stranded. (mhammontree)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "toga-ai",
3
- "version": "1.0.1096",
3
+ "version": "1.0.1097",
4
4
  "description": "TOGA Technology Team Claude Knowledge System — shared AI coding harness with skills, knowledge base CLI, and project installer for Claude Code.",
5
5
  "keywords": [
6
6
  "claude",