toga-ai 1.0.1090 → 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.
@@ -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) — 91 doc(s) → [2.0/apps/worker2/INDEX.md](2.0/apps/worker2/INDEX.md)
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)
@@ -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-18
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
- - **Canada has no Levy sector — and its PEOPLE-cron persona logic is deliberately unchanged.** The
226
- 2026-08-06 Compass USA persona refactor split the US logic in
227
- `worker2/Worker/Client/Compass/PeopleFile.php` by tenant; the **CA branch is preserved verbatim**
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 — the sector-52 split (designed, NOT built)](features/corporate-vs-field-laptop-personas.md) | 2.0 | Agreed design for splitting the Compass catalogue into a Corporate laptop and a field "laptop kit": attach Corporate at sector `J00000` via `Locations_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 — the sector-52 split (designed, NOT built)
2
+ title: Corporate vs Field laptop personas (Levy/Compass x Corporate/Field, PEOPLE cron)
3
3
  framework: "2.0"
4
- project: _Underscore
4
+ repo: worker2
5
+ project: Worker
5
6
  client: compass-usa
6
7
  type: client-feature
7
- status: deferred
8
- updated: 2026-10-05
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
- Agreed design for splitting the Compass catalogue into a Corporate laptop and a field "laptop kit": attach Corporate at sector `J00000` via `Locations_Personas`, assign Field in `PeopleFile`. Designed 2026-10-05, no code written. Open before building the field kit.
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 confirmed the new **field laptop kit REPLACES the standard laptop** for non-corporate users;
24
- only admins see both. So the split is by **corporate vs not**, and corporate is a hierarchy question,
25
- not a flag — see [Location Hierarchy & Sectors](./location-hierarchy-and-sectors.md).
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
- Nothing is built. This doc records the agreed shape so the next session does not re-derive it.
33
+ The earlier design (one `Locations_Personas` row on sector location 52) was **not** built.
28
34
 
29
- ## How it works (the agreed design)
35
+ ## The four personas (prod `Client_Compass`, verified 2026-10-20)
30
36
 
31
- 1. **Corporate persona.** Move the standard laptop bundle(s) out of **Non-Levy (persona 40)** into a
32
- new **Corporate** persona, and attach it with a **single `Locations_Personas` row on sector
33
- `J00000` (`Locations.id = 52`)**. Effective-persona resolution unions `Users_Personas`, the user's own
34
- `Locations_Personas`, and their **sector's** (the 5-level walk), so every cost center under
35
- `J00000` inherits it — including ones Compass adds on a later daily import.
36
- 2. **Field persona.** It **cannot** use `Locations_Personas`: "everyone except sector 52" is not a
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
- ## Where the laptop bundles live (prod `Client_Compass`, verified 2026-10-05)
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
- The standard laptop bundles sit in **persona 40 "Non-Levy"** (number 42) — not in persona 1, which is
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
- | Bundle | Name | Note |
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
- **All four are attached to persona 12 "MITS Team" as well**, so the split must move/handle **both**
59
- personas — doing it on Non-Levy alone leaves MITS Team seeing the old laptop.
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
- There is still **no `Locations_Personas` row for location 52**, so nothing inherits at that sector
62
- today. The table is already used this way for other sectors (e.g. Levy sector 55 → persona 24).
63
-
64
- **Open question — unanswered by the client:** which of **187 / 192 / 195** count as "the standard
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
- - **Never hardcode a persona id.** Resolve by name, exactly as `PeopleFile` does for `Non-Levy` —
70
- ids differ per environment. See [Persona Model & Levy-Sector Gating](./persona-model-and-levy-gating.md).
71
- - **A persona grant is three tables** (`Personas_Bundles`, `Personas_Items`, `Personas_Assortments`),
72
- not just bundles — see [granting a bundle](../workflows/granting-persona-bundle-access.md).
73
- - **The DB does not restrict what a persona sees** — the storefront front end is the only gate. A new
74
- persona alone changes nothing until `toga2-commerce` filters on it.
75
-
76
- ## Related
77
- - [Persona Model & Levy-Sector Gating](./persona-model-and-levy-gating.md)
78
- - [Location Hierarchy & Sectors](./location-hierarchy-and-sectors.md)
79
- - [Persona Refactor migration](../workflows/persona-refactor-migration.md)
80
- - [Effective persona resolution](../../../2.0/apps/_underscore/features/effective-persona-resolution.md)
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-05 — Created. Recorded the agreed corporate/field split (Corporate attached at sector
84
- `J00000` via one `Locations_Personas` row; Field assigned in `PeopleFile` and skipped under
85
- `J00000`; the 79-row per-sector alternative rejected) and the prod bundle locations verified the
86
- same day: laptops 187 / 188 / 192 / 195 live in **Non-Levy (40)** and are also on **MITS Team
87
- (12)**, and location 52 still has no `Locations_Personas` row. An earlier draft of this doc quoted
88
- a **2026-09-15** persona read (persona 1 = "All", 23 bundles) as if it were current and called it a
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 (deferred)](./corporate-vs-field-laptop-personas.md)
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-09
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
- **The refactor is LIVE in prod since 2026-09-11** (three-way split, verified 2026-09-17 — see
47
- [Persona Refactor](../workflows/persona-refactor-migration.md)). Persona 1 is now named **Base**.
48
-
49
- | Persona | id | Meaning |
50
- |---|---|---|
51
- | 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.** |
52
- | Levy Purchaser | **24** | Levy's own **5** kits: 189, 193, 213, 231, 242. |
53
- | Non-Levy | **resolved by name** — **id 40, number 42 in prod** | The other 14 persona-1 kits / 123 items / 3 categories. Granted to non-Levy users only. |
54
- | VIP | 35 | VIP add-on. Carries the manager auto-approve limit (`PersonaGlobalSettings` setting 18 = `'5000'`) — see [Approval-Decision Flow](approval-decision-flow.md). |
55
- | CDL (MacBook / Surface) | 30 | `@compassdigital.io` + VIP users. |
56
-
57
- **Never hardcode the Non-Levy id, and never assume it matches the number.** In prod it is **id 40**
58
- with **number 42** — the number is derived (`MAX(number)+1` under 100) because number 40 already
59
- belongs to "MyDining- KDS" (id 38). Earlier drafts variously claimed id 38 and id/number 40; both are
60
- wrong for prod. Every environment gets a different id, which is exactly what name-resolution protects
61
- against.
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
- - **Non-Levy**, only when `$nonLevyPersonaId !== null`:
120
- - `isLevy && hasNonLevy` → stage delete
121
- - `!isLevy && !hasNonLevy` → stage insert
122
- - New users: always insert Base; insert Non-Levy only when `!isLevy` and the persona exists.
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
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "toga-ai",
3
- "version": "1.0.1090",
3
+ "version": "1.0.1091",
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",