toga-ai 1.0.1111 → 1.0.1112

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: Tools
6
6
  client: shared
7
7
  type: feature
8
8
  status: active
9
- updated: 2026-09-16
9
+ updated: 2026-10-22
10
10
  owners: [ajean, bala]
11
11
  files:
12
12
  - tools/_/app/compass.php
@@ -28,14 +28,16 @@ related:
28
28
  - ../../../clients/compass-usa/features/persona-model-and-levy-gating.md
29
29
  - ../../../clients/compass-usa/features/cost-centers.md
30
30
  - ../../../clients/compass-usa/workflows/granting-persona-bundle-access.md
31
+ - ../../../clients/compass-usa/workflows/granting-api-access.md
32
+ - ../../../2.0/apps/_underscore/features/acl-permission-chain.md
31
33
  ---
32
34
 
33
35
  ## Summary
34
36
 
35
37
  Two pages in the Tools `developers` nav folder that let staff do, in a browser, the Compass
36
38
  requests that used to mean "email a developer, who writes a `dbchanges2` SQL file": **activate /
37
- deactivate a Compass user**, **grant a persona**, and (as of 2026-09-04) **fix a user's email,
38
- phone number and cost center**. Both tenants are covered by one tenant switcher — **US**
39
+ deactivate a Compass user**, **grant a persona**, (as of 2026-09-04) **fix a user's email,
40
+ phone number and cost center**, and (2026-10-22, TRUE-82890) **grant a 2.0 ACL role**. Both tenants are covered by one tenant switcher — **US**
39
41
  (`Client_Compass`) and **Canada** (`Client_CompassCanada`).
40
42
 
41
43
  **This is the first thing in `tools` that WRITES to a client tenant database.** Until now the app
@@ -48,9 +50,9 @@ field-editing work (email / phone / cost center) is PR **tools#14**, branch
48
50
  ## Key files / entry points
49
51
 
50
52
  - **`tools/_/app/compass.php`** — `App_Compass`, all data access for both pages (search, persona
51
- resolution, activate/deactivate, grant, and the three field setters).
53
+ resolution, activate/deactivate, grant, the three field setters, and the role grant).
52
54
  - **`tools/mvc/compass/users/{get,post}.php`** — user search + activate/deactivate + grant persona
53
- + edit email / phone / cost center.
55
+ + edit email / phone / cost center + Roles card / `addRole` action.
54
56
  - **`tools/mvc/compass/personas/{get,post}.php`** — inspect one persona and who reaches it.
55
57
  - **`tools/_/app/nav.php`** — the two nav entries, inside the existing `developers` folder.
56
58
  - **`tools/config.production.ini`** — `[database_compass]` / `[database_compasscanada]`, plus the
@@ -67,6 +69,8 @@ field-editing work (email / phone / cost center) is PR **tools#14**, branch
67
69
  4. **Inspect a persona** and list who reaches it, from all three sources.
68
70
  5. **Grant a persona** — one `INSERT` into `Users_Personas`.
69
71
  6. **Edit email, phone number and cost center** (2026-09-04, PR tools#14).
72
+ 7. **Grant a role** — Roles card + "Grant role" form; one `Users_Roles` insert (2026-10-22,
73
+ TRUE-82890). See below.
70
74
 
71
75
  The "why" column is the point of the tool. Answering "who has persona N" from `Users_Personas`
72
76
  alone badly under-reports on Compass, because most users hold their catalogue through their
@@ -113,6 +117,37 @@ rows — see [Cost Centers](../../../clients/compass-usa/features/cost-centers.m
113
117
  **before/after persona diff** to be shown and confirmed, because a location change moves the user's
114
118
  sector and therefore their catalogue.
115
119
 
120
+ ### Role grant (TRUE-82890)
121
+
122
+ `App_Compass::roles()`, `role()`, `rolesForUser()`, `hasRole()`, `addUserToRole()`, plus
123
+ `isSensitiveRole()` / `isBlockedRole()` / `roleNameIn()` over the consts `SENSITIVE_ROLE_NAMES` /
124
+ `BLOCKED_ROLE_NAMES`. Post action `addRole` in `users/post.php`.
125
+
126
+ 1. **Additive only.** Inserts one `Users_Roles` row; never replaces Base / Compass Base (roles
127
+ resolve as a union — removing one takes access away). **No remove action** — undo is hand SQL.
128
+ 2. **Blocked: `API`, `Refresh API`, `Public`.** These are API-key / anonymous roles bound through
129
+ `Apis_Roles`, not roles for people. Hidden from the dropdown AND refused in both `post.php` and
130
+ `addUserToRole()`.
131
+ 3. **High-access roles need a confirm checkbox:** `Admin`, `SuperUser`, `Developer`,
132
+ `Agilant - Administrators`, or any role with `coreRoleId > 1`. Name match is trimmed and
133
+ case-blind. The audit note records `[high-access confirmed]`.
134
+ 4. **Resolve the posted `roleId` against that tenant's `Roles` table** — ids differ per tenant
135
+ (see gotcha). Never hardcode.
136
+ 5. **Duplicate-safe insert:** `INSERT ... SELECT ... WHERE NOT EXISTS` + `affectedRows()`.
137
+ `Users_Roles` has **no unique key on `(userId, roleId)`**, so a double-click would otherwise
138
+ add a second row.
139
+ 6. **Audit row first** (`logChange()`, before the insert); `logFailureRow` on failure.
140
+
141
+ **Survives the night.** The worker2 PEOPLE cron (`Worker/Client/Compass/PeopleFile.php`) only
142
+ INSERTS `Users_Roles` rows (Base + Compass Base for everyone, Manager for anyone who is some
143
+ user's `supervisorUserId`) and never deletes one — unlike email / phone / cost center /
144
+ activation, which it overwrites.
145
+
146
+ **Takes effect at the user's next login.** api2 puts the user's roles into the login JWT; a
147
+ signed-in user keeps the old set until they log in again. No ACL cache to clear.
148
+
149
+ Not browser-tested at write time (no local tools server on the developer's Mac).
150
+
116
151
  ### Access gating is deliberately tighter than the folder
117
152
 
118
153
  Both pages `requireAuth(['Development Team'])` — narrower than the `developers` folder's own
@@ -147,8 +182,8 @@ reuse the **existing `[database_true]` account** — same client cluster, only `
147
182
  ### Schema footprint: none
148
183
 
149
184
  The tool creates and alters **nothing**. It reads/writes only `Users`, `Contacts` (+ its primary
150
- email/phone children), `Users_Personas`, `Locations_Personas`, `Locations`, reads `Personas` for
151
- names, reads `Core.ClientEmailDomains` / `Core.Clients`, and inserts into `Logs_<Tenant>.Record`.
185
+ email/phone children), `Users_Personas`, `Users_Roles` (insert only), `Locations_Personas`,
186
+ `Locations`, reads `Personas` / `Roles` for names, reads `Core.ClientEmailDomains` / `Core.Clients`, and inserts into `Logs_<Tenant>.Record`.
152
187
 
153
188
  ## Decisions
154
189
 
@@ -172,9 +207,19 @@ names, reads `Core.ClientEmailDomains` / `Core.Clients`, and inserts into `Logs_
172
207
  `c_erpSystemEntityId` is also copied into `c_hrEmpDirectoryKey` and treated as unique per
173
208
  employee. Same one-way-durability shape as the activation gotcha. Detail:
174
209
  [Cost Centers](../../../clients/compass-usa/features/cost-centers.md).
210
+ - **2026-10-22 — Role grant: every tenant role except `API`, `Refresh API`, `Public` (ajean,
211
+ TRUE-82890).** Developer first chose "all roles", then blocked those three after the CTO review
212
+ flagged them. Additive only, no remove action.
175
213
 
176
214
  ## Gotchas
177
215
 
216
+ - **⚠ `Roles` ids differ per Compass tenant.** Prod `Client_Compass`: Base 1, Developer 2, API 3,
217
+ Admin 4, SuperUser 5, Contractor 6, Compass Base 7, Manager 8, Agilant - Administrators 9,
218
+ Public 10, ODP Vendor Items Manager 11, Refresh Orders 12, View Orders 14, Fulfil and Ship 15,
219
+ View Vendor Items 16, Refresh API 17. Prod `Client_CompassCanada`: Base 1, Developer 2, API 3,
220
+ Public 4, Admin 5, SuperUser 6, Contractor 7, Compass Base 8, Manager 9,
221
+ Agilant - Administrators 10, Refresh Orders 11. Match by name or resolve per tenant.
222
+
178
223
  - **⚠ Activating a Compass user is TEMPORARY; deactivating is durable.** Because the
179
224
  `c_isKeepActive` flag was dropped from scope, the nightly PEOPLE importer will deactivate anyone
180
225
  absent from that day's file — so an activation only sticks if the person is actually in the file.
@@ -19,7 +19,7 @@ project: _Underscore
19
19
  client: compass-canada
20
20
  type: profile
21
21
  status: active
22
- updated: 2026-10-20
22
+ updated: 2026-10-22
23
23
  owners: [jcardinal, bala, tcox, apeterson, ajean, mhammontree, rgirish]
24
24
  files: []
25
25
  related:
@@ -29,6 +29,7 @@ related:
29
29
  - workflows/grand-and-toy-asn-backfill.md
30
30
  - workflows/copying-a-us-user-into-the-canada-tenant.md
31
31
  - features/persona-model.md
32
+ - ../../1.0/apps/tools/features/compass-user-persona-admin.md
32
33
  - ../../2.0/apps/api2/features/cxml-shipnotice-gateway.md
33
34
  - ../compass-usa/features/approval-decision-flow.md
34
35
  - ../compass-usa/features/stranded-approval-reassignment.md
@@ -266,7 +267,9 @@ to but distinct from Compass USA. Like Compass USA it spans the **2.0** commerce
266
267
  Canada: 1 Base, 7 **Contractor**, 8 **Compass Base**, 9 Manager. USA: 1 Base, 7 **Compass Base**,
267
268
  8 Manager. USA grants the fulfillment records to roles 3 + 7; Canada grants most of them to role 1
268
269
  only. Nearly every logged-in Canada user carries role 1, so this is usually invisible — but ~10
269
- users hold Compass Base without Base and still hit the gap.
270
+ users hold Compass Base without Base and still hit the gap. To grant one Canada user a role, use
271
+ tools `/compass/users` (Canada tenant) — it resolves the id per tenant; see
272
+ [Compass user/persona admin](../../1.0/apps/tools/features/compass-user-persona-admin.md).
270
273
  - **Canada was NOT damaged by the 2026-08-31 cross-client item merge, and is structurally
271
274
  immune.** It was **not** in the `duplicate_items_merge_ALL_CLIENTS.sql` client list; it has a
272
275
  **single catalog** ("compass"), 0 part numbers in multiple catalogs, and 0 duplicate part
@@ -23,7 +23,7 @@ project: _Underscore
23
23
  client: compass-usa
24
24
  type: profile
25
25
  status: active
26
- updated: 2026-10-21
26
+ updated: 2026-10-22
27
27
  owners: [jcardinal, bala, tcox, apeterson, dfranks, akhokhani, ajean, rgirish]
28
28
  files: []
29
29
  related:
@@ -219,7 +219,9 @@ separate, related client (see its own profile).
219
219
  not `id`, and **`Users` has no `username` column** — it is `c_hrEmpUsername`, stored lowercase.
220
220
  Super user = `Roles.id 5` added **additively** via `Users_Roles`, never replacing Base /
221
221
  Compass Base. **Kit add-on bundles (`BundleItems.childBundleId`) need their own persona grant**
222
- and stay `isActive = 0` by design (TRUE-82981, 2026-10-21).
222
+ and stay `isActive = 0` by design (TRUE-82981, 2026-10-21). A single role grant (USA or Canada)
223
+ is now self-serve in tools `/compass/users` (Roles card; `API` / `Refresh API` / `Public`
224
+ blocked) — see [Compass user/persona admin](../../1.0/apps/tools/features/compass-user-persona-admin.md).
223
225
  - [Prod ↔ dev-sandbox persona comparison](workflows/persona-population-env-comparison.md) — the
224
226
  full-population diff technique, the **join-on-email** rule (`Users.id` means different people per
225
227
  environment), and the drift bundles (188/191/242) that mimic a catalogue grant.
@@ -6,7 +6,7 @@ project: Database Changes
6
6
  client: compass-usa
7
7
  type: workflow
8
8
  status: active
9
- updated: 2026-10-21
9
+ updated: 2026-10-22
10
10
  owners: [bala, ajean]
11
11
  files:
12
12
  - dbchanges2/Client_Compass/2026-08-26a - CompassCreativeStudioPersona.sql
@@ -72,7 +72,7 @@ The `personaId <> @personaId` exclusion matters: by the time this phase runs, th
72
72
  **`Client_Compass.Users` has no `username` column** — the natural first query fails with `Unknown column 'username'`. The Compass login / personnel username lives in **`c_hrEmpUsername`**, stored **lowercase**: people quote it uppercase ("KINGK01") but the row says `kingk01`. Look users up by `email` or `c_hrEmpUsername` (lowercased), which is also how the nightly PEOPLE cron matches them — see [PEOPLE-File User Lifecycle](../features/people-file-user-lifecycle.md).
73
73
 
74
74
  ### Making someone a super user
75
- There is **no `isSuperUser` flag on `Users`** — it is a role row. `Client_Compass.Roles.id` **5**, name **`SuperUser`** (113 holders as of 2026-08-26), granted **additively** through `Users_Roles (userId, roleId)`, which is unique on the pair. **SuperUser is never a replacement for the base roles.** The established prod pattern for a Compass super user is **Base (1) + SuperUser (5) + Compass Base (7)**, optionally **+ Manager (8)**. Role permissions resolve as a **union** (see [ACL Permission Chain](../../../2.0/apps/_underscore/features/acl-permission-chain.md)), so adding role 5 while removing 1 or 7 takes access *away*.
75
+ There is **no `isSuperUser` flag on `Users`** — it is a role row. `Client_Compass.Roles.id` **5**, name **`SuperUser`** (113 holders as of 2026-08-26), granted **additively** through `Users_Roles (userId, roleId)`, which has **no** unique key on the pair (guard the insert with `WHERE NOT EXISTS`). A single role grant needs no SQL: use the tools `/compass/users` Roles card — [Compass user/persona admin](../../../1.0/apps/tools/features/compass-user-persona-admin.md). **SuperUser is never a replacement for the base roles.** The established prod pattern for a Compass super user is **Base (1) + SuperUser (5) + Compass Base (7)**, optionally **+ Manager (8)**. Role permissions resolve as a **union** (see [ACL Permission Chain](../../../2.0/apps/_underscore/features/acl-permission-chain.md)), so adding role 5 while removing 1 or 7 takes access *away*.
76
76
 
77
77
  ### Verify the way the ACL actually resolves personas
78
78
  A user's **effective** persona set is three UNIONed sources, not just `Users_Personas`:
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "toga-ai",
3
- "version": "1.0.1111",
3
+ "version": "1.0.1112",
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",