toga-ai 1.0.823 → 1.0.824
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,8 +6,8 @@ project: Tools
|
|
|
6
6
|
client: shared
|
|
7
7
|
type: feature
|
|
8
8
|
status: active
|
|
9
|
-
updated: 2026-09-
|
|
10
|
-
owners: [ajean]
|
|
9
|
+
updated: 2026-09-16
|
|
10
|
+
owners: [ajean, bala]
|
|
11
11
|
files:
|
|
12
12
|
- tools/_/app/compass.php
|
|
13
13
|
- tools/mvc/compass/users/get.php
|
|
@@ -101,6 +101,10 @@ conditions of the decision below — **do not remove one without re-opening it**
|
|
|
101
101
|
(the same human on two `Users` rows).
|
|
102
102
|
5. The four `ensureCrossRegionUsersActive()` addresses are **blocked as both source and target**
|
|
103
103
|
(`isCrossRegionProtectedEmail()`), because that hardcoded worker2 list matches on email.
|
|
104
|
+
**⚠ Out of date as of 2026-09-16:** worker2 split that list into six addresses
|
|
105
|
+
(`ALWAYS_ACTIVE_EMAILS_BOTH` + `ALWAYS_ACTIVE_EMAILS_CA`), so the two new
|
|
106
|
+
`@compassdigital.io` Canada-protected addresses are **not** blocked here yet. See
|
|
107
|
+
[PEOPLE-File User Lifecycle](../../../clients/compass-usa/features/people-file-user-lifecycle.md).
|
|
104
108
|
6. The **audit row is written before the write**, on its own connection (see the transaction
|
|
105
109
|
gotcha below).
|
|
106
110
|
|
|
@@ -191,9 +195,14 @@ names, reads `Core.ClientEmailDomains` / `Core.Clients`, and inserts into `Logs_
|
|
|
191
195
|
~178,615 of 178,616 `Client_Compass` rows, so the client id cannot be derived from the user row.
|
|
192
196
|
See [Compass Canada profile](../../../clients/compass-canada/profile.md).
|
|
193
197
|
- **A cross-region user still needs a code change and a deploy.** The only durable way to hold one
|
|
194
|
-
active is the hardcoded
|
|
198
|
+
active is the hardcoded list in `ensureCrossRegionUsersActive()` in
|
|
195
199
|
`worker2/Worker/Client/Compass/PeopleFile.php`. This tool does not change that — and because that
|
|
196
|
-
list matches on **email**, those
|
|
200
|
+
list matches on **email**, those addresses are blocked from this tool's email edit entirely.
|
|
201
|
+
**As of 2026-09-16 that list is six addresses, not four**: `ALWAYS_ACTIVE_EMAILS_BOTH` (the
|
|
202
|
+
original four, held active in both tenants) plus `ALWAYS_ACTIVE_EMAILS_CA` (two
|
|
203
|
+
`@compassdigital.io` US staff held active in `Client_CompassCanada` only).
|
|
204
|
+
`isCrossRegionProtectedEmail()` here still only knows the original four, so the two new addresses
|
|
205
|
+
can be edited in this tool and silently lose their protection.
|
|
197
206
|
- **Every user search is a full table scan.** `Client_Compass.Users` has no index on `email`,
|
|
198
207
|
`c_hrEmpUsername`, `c_hrEmpPersonnelNbr` or `isActive` (176k rows; `EXPLAIN` reports `type=ALL`,
|
|
199
208
|
no possible keys). Same on `Client_CompassCanada`. Keep result sets bounded and do not add
|
|
@@ -235,12 +244,22 @@ names, reads `Core.ClientEmailDomains` / `Core.Clients`, and inserts into `Logs_
|
|
|
235
244
|
(`_underscore/Model/Compass/SalesOrder.php:287-305`): it joins
|
|
236
245
|
`INNER JOIN Contacts ON Contacts.id = Users.id`, which should be `Users.contactId`.
|
|
237
246
|
- **worker2** — scope `ensureCrossRegionUsersActive()` to a `Users.id` list instead of matching on
|
|
238
|
-
email; that would also lift this tool's block on those
|
|
247
|
+
email; that would also lift this tool's block on those addresses.
|
|
248
|
+
- **tools** — extend `isCrossRegionProtectedEmail()` to the two `ALWAYS_ACTIVE_EMAILS_CA` addresses
|
|
249
|
+
added in worker2 on 2026-09-16, or the block silently under-covers the protected set.
|
|
239
250
|
- **ops** — rotate every secret in `tools/config.production.ini`; consider a least-privilege
|
|
240
251
|
Compass account.
|
|
241
252
|
|
|
242
253
|
## Change history
|
|
243
254
|
|
|
255
|
+
- 2026-09-16 — Recorded that worker2's cross-region protected list **grew from four addresses to
|
|
256
|
+
six** (`ALWAYS_ACTIVE_EMAILS_BOTH` + `ALWAYS_ACTIVE_EMAILS_CA`), so this tool's
|
|
257
|
+
`isCrossRegionProtectedEmail()` block now under-covers it: the two new `@compassdigital.io`
|
|
258
|
+
Canada-protected addresses can still be email-edited here and would silently drop out of the
|
|
259
|
+
protected set, because the worker2 match is on email. No `tools` code changed. Detail:
|
|
260
|
+
[PEOPLE-File User Lifecycle](../../../clients/compass-usa/features/people-file-user-lifecycle.md).
|
|
261
|
+
(bala)
|
|
262
|
+
|
|
244
263
|
- 2026-09-04 — Added **email, phone and cost-center editing** (PR tools#14,
|
|
245
264
|
`feature/compass-user-field-edits`, **open / not merged / not deployed**): new `App_Compass`
|
|
246
265
|
setters plus the `normaliseEmail` / `normalisePhone` / `emailDomainIsRegistered` /
|
|
@@ -6,7 +6,7 @@ project: Worker
|
|
|
6
6
|
client: compass-usa
|
|
7
7
|
type: client-feature
|
|
8
8
|
status: active
|
|
9
|
-
updated: 2026-09-
|
|
9
|
+
updated: 2026-09-16
|
|
10
10
|
owners: [bala, ajean]
|
|
11
11
|
files:
|
|
12
12
|
- worker2/Worker/Client/Compass/PeopleFile.php
|
|
@@ -85,6 +85,39 @@ insert-refresh time the **new row already has its id** and the **old row is stil
|
|
|
85
85
|
deactivation-time hook would be worse — by then the new row's relationship to the old one has to be
|
|
86
86
|
rediscovered, and the deactivation path has no model hooks at all (see below).
|
|
87
87
|
|
|
88
|
+
### Always-active protection is per-tenant, and has two layers
|
|
89
|
+
|
|
90
|
+
A small hardcoded set of people must stay active regardless of what HR sends. Since **2026-09-16**
|
|
91
|
+
that set is split into two class constants on `_Worker_Client_Compass_PeopleFile`:
|
|
92
|
+
|
|
93
|
+
- **`ALWAYS_ACTIVE_EMAILS_BOTH`** — the original four addresses (two `@compass-usa.com`, two
|
|
94
|
+
`@compass-canada.com`). Held active in **both** `Client_Compass` and `Client_CompassCanada`.
|
|
95
|
+
- **`ALWAYS_ACTIVE_EMAILS_CA`** — two `@compassdigital.io` **Compass USA** staff (usernames
|
|
96
|
+
`shuttk01` / `racerk01` in `Client_Compass`). Held active in **`Client_CompassCanada` only**.
|
|
97
|
+
They are allowed to go inactive in the US tenant when the PEOPLE file drops them; that is the
|
|
98
|
+
requirement, not a bug.
|
|
99
|
+
|
|
100
|
+
`isEmailAlwaysActive(string $email, string $tenantKey): bool` is the single decision point — a
|
|
101
|
+
lowercased match that returns true for the BOTH list in either tenant, and for the CA list only when
|
|
102
|
+
`$tenantKey === 'CA'`. Two layers consume it:
|
|
103
|
+
|
|
104
|
+
1. **The inactivation loop** in `executeForTenant()` skips any user whose `Users.email` is
|
|
105
|
+
protected for that tenant, so the batch `UPDATE ... SET isActive = 0` never lists them.
|
|
106
|
+
2. **`ensureCrossRegionUsersActive()`**, which runs after both tenants have executed and force-sets
|
|
107
|
+
`isActive = 1` — in both databases for the BOTH list, and in `Client_CompassCanada` only for the
|
|
108
|
+
CA list.
|
|
109
|
+
|
|
110
|
+
**Today only layer 2 actually protects the CA-only pair.** In `Client_CompassCanada` both of those
|
|
111
|
+
users have `c_hrEmpUsername = NULL`, and `loadLookups()` keys its map on username — so they never
|
|
112
|
+
enter the map and can never reach the inactivation batch on the Canada side. The loop guard is the
|
|
113
|
+
defence for the day HR gives them a Canada username, not what is holding them active now.
|
|
114
|
+
(Read-only prod check, 2026-09-16.)
|
|
115
|
+
|
|
116
|
+
**The email match works across a case difference.** `Client_Compass` stores these two addresses
|
|
117
|
+
UPPERCASE while `Client_CompassCanada` stores them lowercase. `Users.email` uses
|
|
118
|
+
`utf8mb4_0900_ai_ci`, a case-insensitive collation, so `WHERE email = '...'` still hits — each
|
|
119
|
+
address resolves to exactly **one** row per database.
|
|
120
|
+
|
|
88
121
|
## Gotchas
|
|
89
122
|
|
|
90
123
|
- **⚠ Deactivation is raw bulk SQL — model hooks never fire.** User inactivation is executed as
|
|
@@ -157,16 +190,26 @@ rediscovered, and the deactivation path has no model hooks at all (see below).
|
|
|
157
190
|
makes the [Tools Compass admin
|
|
158
191
|
pages](../../../1.0/apps/tools/features/compass-user-persona-admin.md) a one-way-durable tool.
|
|
159
192
|
- **`ensureCrossRegionUsersActive()` runs `UPDATE Users SET isActive = 1 WHERE email = X` with no
|
|
160
|
-
`isActive` filter**, so it reactivates **every** row carrying
|
|
161
|
-
|
|
162
|
-
resolves to exactly **one**
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
admin](../../../1.0/apps/tools/features/compass-user-persona-admin.md) blocks
|
|
169
|
-
from being edited at all.
|
|
193
|
+
`isActive` filter**, so it reactivates **every** row carrying a protected address, dead duplicates
|
|
194
|
+
included. Verified on prod 2026-09-02 for the original four, and 2026-09-16 for the two CA-only
|
|
195
|
+
addresses: each resolves to exactly **one** row per database, so nothing is being resurrected
|
|
196
|
+
today — but the query would happily do it if a duplicate appeared. That hardcoded list is also
|
|
197
|
+
still the **only** durable way to hold a cross-region user active, and changing it needs a code
|
|
198
|
+
change and a deploy per request. **Recommended (not done): scope it to a `Users.id` list instead
|
|
199
|
+
of matching on email** — matching on email means an email edit silently moves a user in or out of
|
|
200
|
+
the protected set, which is why the [Tools Compass
|
|
201
|
+
admin](../../../1.0/apps/tools/features/compass-user-persona-admin.md) blocks the protected
|
|
202
|
+
addresses from being edited at all.
|
|
203
|
+
- **⚠ The Tools admin's edit block covers only the original four addresses, not the two CA-only
|
|
204
|
+
ones.** The protected set grew to six in worker2 on 2026-09-16; the `tools` block list was not
|
|
205
|
+
changed and was not re-checked. Until it is, someone can edit the email of a CA-protected user in
|
|
206
|
+
[Tools Compass admin](../../../1.0/apps/tools/features/compass-user-persona-admin.md) and silently
|
|
207
|
+
drop them out of the protected set — because the match is on email, not id.
|
|
208
|
+
- **⚠ Adding a new "always keep this person active" rule means touching exactly two places.**
|
|
209
|
+
`executeForTenant()`'s inactivation loop is the only `isActive = 0` writer in the whole class, and
|
|
210
|
+
`ensureCrossRegionUsersActive()` is the post-run sweep. Guard both and the requirement is covered;
|
|
211
|
+
guard only the loop and the post-run sweep still decides the final state, guard only the sweep and
|
|
212
|
+
the user flickers inactive between the two steps.
|
|
170
213
|
|
|
171
214
|
## Remediation — BUILT 2026-08-20
|
|
172
215
|
|
|
@@ -197,6 +240,24 @@ question is now contained rather than blocking.
|
|
|
197
240
|
|
|
198
241
|
## Change history
|
|
199
242
|
|
|
243
|
+
- 2026-09-16 — **Made the always-active protection tenant-aware.** Two Compass USA staff
|
|
244
|
+
(`@compassdigital.io`, usernames `shuttk01` / `racerk01`) exist in **both** tenants, but the
|
|
245
|
+
PEOPLE file lists them only as US rows (`c_erpSystemId` 1001), so they were routed to
|
|
246
|
+
`Client_Compass` alone and had no protection on the Canada side. Requirement: they may go inactive
|
|
247
|
+
in the USA per the file, but must **always** stay active in Canada. Split the previously
|
|
248
|
+
undocumented single hardcoded list into `ALWAYS_ACTIVE_EMAILS_BOTH` (the original four) and
|
|
249
|
+
`ALWAYS_ACTIVE_EMAILS_CA` (the two new ones), added
|
|
250
|
+
`isEmailAlwaysActive(string $email, string $tenantKey): bool` as the single lowercased decision
|
|
251
|
+
point, and wired it into **both** the `executeForTenant()` inactivation loop (skip protected
|
|
252
|
+
users) and `ensureCrossRegionUsersActive()` (both DBs for BOTH, `Client_CompassCanada` only for
|
|
253
|
+
CA). Prod read-only findings: in `Client_CompassCanada` both users have `c_hrEmpUsername = NULL`,
|
|
254
|
+
so they never enter `loadLookups()`'s username-keyed map and **cannot** reach the Canada
|
|
255
|
+
inactivation batch at all — the post-run sweep is what actually protects them today, and the loop
|
|
256
|
+
guard is defence for the day they get a Canada username. Also confirmed the two tenants store
|
|
257
|
+
these addresses in different case (US uppercase, CA lowercase) and the email match still works
|
|
258
|
+
because `Users.email` uses the case-insensitive `utf8mb4_0900_ai_ci` collation, one row per
|
|
259
|
+
address per database. Not committed or pushed — worker2 working tree only. (bala)
|
|
260
|
+
|
|
200
261
|
- 2026-09-04 — Prod-verified the **full index list** on `Users` in both tenants and recorded the
|
|
201
262
|
load-bearing consequence: **there is no `UNIQUE KEY` on `email`** (and no index on
|
|
202
263
|
`c_hrEmpUsername` / `c_hrEmpPersonnelNbr` / `isActive`), so the database silently accepts duplicate
|
package/package.json
CHANGED