toga-ai 1.0.843 → 1.0.844
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.
|
@@ -16,6 +16,8 @@ related:
|
|
|
16
16
|
- sales-order-status-filter-surface.md
|
|
17
17
|
- tableview-joins.md
|
|
18
18
|
- ../../toga-blox/features/table.md
|
|
19
|
+
- ../../api2/features/environment-variable-drives-underscore-branch.md
|
|
20
|
+
- ../../toga25-supply/features/persisted-query-cache.md
|
|
19
21
|
- ../../api2/features/tableview-field-metadata.md
|
|
20
22
|
---
|
|
21
23
|
|
|
@@ -72,23 +74,44 @@ It is a **no-op for every existing column**: nothing else declares a `<field>Fil
|
|
|
72
74
|
- Verified locally: 13 options for `Client_Compass`, 5 for `Client_CompassCanada`. Compass Canada
|
|
73
75
|
also has a role `Agilant - Administrators`, correctly excluded by the exact `name = 'Admin'` match.
|
|
74
76
|
|
|
75
|
-
**
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
**
|
|
77
|
+
**Two exclusion rules, both applied (2026-09-18).** Admin-role holders were showing up as
|
|
78
|
+
assignable to Compass users when they should not be:
|
|
79
|
+
|
|
80
|
+
1. **Internal TOGA staff, by domain** — `const INTERNAL_EMAIL_DOMAIN = 'togatech.com'`.
|
|
81
|
+
2. **Named individuals on a CLIENT domain** — `const EXCLUDED_ADMIN_EMAILS = ['vanessa.burks@compass-usa.com'];`
|
|
82
|
+
built into a `NOT IN (...)`. The domain rule alone could never catch her — her email is a
|
|
83
|
+
`@compass-usa.com` address. Assume any hide list will eventually need both.
|
|
84
|
+
|
|
85
|
+
Both conditions are **NULL-safe on purpose**:
|
|
79
86
|
|
|
80
87
|
```sql
|
|
81
88
|
(Users.email IS NULL OR Users.email NOT LIKE '%@togatech.com')
|
|
89
|
+
(Users.email IS NULL OR Users.email NOT IN ('...'))
|
|
82
90
|
```
|
|
83
91
|
|
|
84
|
-
A bare `NOT LIKE` yields NULL for a NULL email and would silently drop those admins.
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
+
A bare `NOT LIKE` / `NOT IN` yields NULL for a NULL email and would silently drop those admins.
|
|
93
|
+
Each list value is escaped with `_Database::escape()`, and the `NOT IN` is **skipped entirely when
|
|
94
|
+
the array is empty** — an empty list builds `NOT IN ()`, a MySQL syntax error.
|
|
95
|
+
|
|
96
|
+
**Why a constant and not a `c_isHiddenFromAdminFilter` column on `Client_Compass.Users`** (proposed
|
|
97
|
+
twice, rejected twice — do not re-propose): these same people **must still appear in other admin
|
|
98
|
+
lists** in the product. A `c_` flag reads as a property of the person ("hidden from admin lists"),
|
|
99
|
+
so the next developer reuses it for a different list and hides them there too. A constant inside
|
|
100
|
+
`_assignedAdminFilterOptions()` cannot leak — the exclusion stays scoped to this one dropdown.
|
|
101
|
+
Accepted trade: adding a name needs a deploy. Rationale is repeated in the method docblock.
|
|
102
|
+
The other rejected option: stripping the `Admin` role in `Users_Roles` — the team will not strip
|
|
103
|
+
admin roles from real accounts.
|
|
104
|
+
|
|
105
|
+
**ACL cannot do this**, for two independent reasons. Row-level ACL does exist in 2.0
|
|
106
|
+
(`Core.AclRecordExpressions.sqlExpression`, wired to a role via `AclLogicGroups` /
|
|
107
|
+
`AclLogicGroupExpressions`), so the instinct is reasonable, but:
|
|
108
|
+
1. `_assignedAdminFilterOptions()` runs a raw `new _Query(...)` straight against `DB_CLIENT` and
|
|
109
|
+
never passes through the `_Model` / V2 ACL layer, so no ACL row changes its result;
|
|
110
|
+
2. even if it did, an `AclRecordExpression` filters **every** read of that record for that role —
|
|
111
|
+
hiding the person from user lists and lookups everywhere, far wider than one dropdown.
|
|
112
|
+
|
|
113
|
+
**General rule: a filter-options static that builds its own `_Query` is outside ACL by
|
|
114
|
+
construction.** Whatever it must exclude, it excludes in its own SQL.
|
|
92
115
|
|
|
93
116
|
### Why a model static and not a config column
|
|
94
117
|
|
|
@@ -119,6 +142,15 @@ generic version at roughly **4–5** such columns, driven by real examples.
|
|
|
119
142
|
- **The dropdown is backend-driven — do not look in toga25-supply.** The front end only renders
|
|
120
143
|
what `_assignedAdminFilterOptions()` returns. Any change to which admins appear goes in
|
|
121
144
|
`_underscore/Model/Compass/SalesOrder.php`.
|
|
145
|
+
- **⚠ "My change had no effect" is usually stale cache or an undeployed branch, not this code.**
|
|
146
|
+
Work the ladder cheapest-first: clear the `supply-chain-query-cache` localStorage key (stale table
|
|
147
|
+
meta is served with **nothing in the network tab**), then read the meta request host in DevTools to
|
|
148
|
+
learn which api2 tier answered, then check branch/deploy state — see
|
|
149
|
+
[ENVIRONMENT drives the _underscore branch](../../api2/features/environment-variable-drives-underscore-branch.md).
|
|
150
|
+
The client-model class chain is **not** a likely cause: `_Model_Client_TableView::meta()` swaps
|
|
151
|
+
`_Model_Client_` → `_Model_<clientIdentifier>_` (`TableView.php` ~L266), and
|
|
152
|
+
`_Model_Compass_Usa_SalesOrder` is an empty subclass of `_Model_Compass_SalesOrder`, so the
|
|
153
|
+
override is inherited. Rule it out in one minute.
|
|
122
154
|
- **Key on the FIELD name, not the column slug.** The slug is `assigned-admin` (hyphen); the model
|
|
123
155
|
field is `_assignedAdmin`. The seam uses the field name.
|
|
124
156
|
- **Type `STRING` (not STATUS/SELECT) is what lets the branch fire** — the existing `switch` in
|
|
@@ -6,7 +6,7 @@ project: API
|
|
|
6
6
|
client: shared
|
|
7
7
|
type: feature
|
|
8
8
|
status: active
|
|
9
|
-
updated: 2026-09-
|
|
9
|
+
updated: 2026-09-18
|
|
10
10
|
owners: ["bala", "mhammontree", "apeterson", "jcardinal", "tcox"]
|
|
11
11
|
files:
|
|
12
12
|
- api2/.ebextensions/git.php
|
|
@@ -90,6 +90,12 @@ So on `api.beta.togahub.com` and `api.dev.sandbox.togahub.com` the code fataled
|
|
|
90
90
|
|
|
91
91
|
## Gotchas
|
|
92
92
|
|
|
93
|
+
- **⚠ Merged locally ≠ live. A `_underscore` change reaches a tier only when the branch is PUSHED *and* api2 has been REDEPLOYED since.** The clone happens in the api2 prebuild hook, so an unpushed (or un-redeployed) branch leaves the tier running the old framework while your local checkout and `git log` both look correct. Seen 2026-09-18: a model change was committed and merged locally but never pushed; `_sandbox-client` served the old code.
|
|
94
|
+
- Diagnosis ladder, cheapest first, when "my `_underscore` change did nothing":
|
|
95
|
+
1. Delete the `supply-chain-query-cache` localStorage key. toga25-supply persists the React Query cache, so stale table meta is served with **nothing in the network tab** — which reads as a backend fault. See [persisted query cache](../../toga25-supply/features/persisted-query-cache.md).
|
|
96
|
+
2. Read the meta request **host** in DevTools to learn which tier actually answered (`api.client.sandbox.togahub.com` = the `_sandbox-client` tier).
|
|
97
|
+
3. Only then suspect branch/deploy state — `git ls-remote origin <branch>` (is it pushed?) then the pipeline source action.
|
|
98
|
+
|
|
93
99
|
- **⚠ Cross-repo change ordering: land `_underscore` BEFORE deploying api2.** Because api2's EB deploy **clones `_underscore` (branch `_<ENVIRONMENT>`, e.g. `_production`) at BUILD time** (`.platform/hooks/prebuild/git.sh`), a change spanning both repos must have its `_underscore` side merged to `_<ENVIRONMENT>` **first** — otherwise the freshly-built api2 calls a framework method/class not on the box and fatals. Landing `_underscore` first is **safe on its own**: the old api2 controller doesn't use the new framework code yet. Order: merge `_underscore` → (verify it's on the branch) → deploy api2.
|
|
94
100
|
- **🚨 Worked instance — `client-sandbox` was 100% DOWN on this exact violation (2026-08-31, since fixed).** Every request returned 500 `Call to undefined method _Model_Core_AclFieldPermission::resolveFieldPermissions()`. `api2@_sandbox-client` (`Component/Api/V2/V2.php:6198`, commit `0cc37fe` "Delegate ACL field perms to shared resolver", jcardinal 2026-08-14) delegates to a resolver whose `_underscore` half (`a5e79448` "Surface field binding and ACL field resolver", same author/date) exists **only on `_qa-alpha` and `toga25-desk`** — never merged to **`_underscore@_sandbox-client`**. Resolved the same day (branches matched + redeployed). **Why a half-shipped ACL refactor is a TOTAL outage:** field-ACL resolution runs on essentially every authenticated GET, so there is no degraded mode — the whole tier 500s and every unrelated feature under test looks broken. When a whole sandbox goes dark, check the two-repo pairing before debugging your own feature.
|
|
95
101
|
- **⚠ A "Could not find required file for `_Model_...`" fatal on prod can be a STALE-BUILD artifact, not a missing file.** `Logs.Issue #476` ("Could not find required file for '_Model_Client_ItemFulfillments_InventoryAdjustment'") fired because that model file **was** committed to `_production` but `api-production-1` was running an **older build**; a **redeploy** (re-pulls `_production`) resolved it. Before assuming a model file is absent, check the **running build's `_underscore` against `_production`** (file presence on the box; `git merge-base --is-ancestor`).
|
package/package.json
CHANGED