toga-ai 1.0.912 → 1.0.914
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.
- package/knowledge/2.0/apps/_underscore/INDEX.md +2 -0
- package/knowledge/2.0/apps/_underscore/features/persona-nav-visibility-gate.md +51 -0
- package/knowledge/2.0/apps/_underscore/features/surface-overrides-uuid-rekey.md +37 -0
- package/knowledge/2.0/apps/_underscore/features/surface-resolver.md +2 -0
- package/knowledge/2.0/apps/api2/features/health-check-endpoint.md +9 -1
- package/knowledge/2.0/apps/api2/features/tableview-apiwhereclause-row-filtering.md +34 -1
- package/knowledge/INDEX.md +1 -1
- package/knowledge/clients/elite/features/supply2-tableview-config-drift.md +21 -1
- package/package.json +1 -1
|
@@ -44,6 +44,7 @@
|
|
|
44
44
|
| [Legacy page meta (Page::meta) & context-scoped ClientRecordFieldSettings](features/page-meta-context-field-settings.md) | Legacy `Page::meta()` page-meta resolver + context-scoped `ClientRecordFieldSettings`; open when a grid column header is wrong/camelCase, or a `/pages/meta` pag |
|
|
45
45
|
| [Per-Client Database Connections & the Local Logs Trap](features/per-client-database-connections.md) | `_underscore` opens three per-client DB connections (data/logs/archive) per request; open when a local or prod request 500s with `Unknown database`, when resolv |
|
|
46
46
|
| [Persona Name Translation (PersonaTranslations sidecar)](features/persona-name-translation.md) | Serves Persona names in multiple languages via a `PersonaTranslations` sidecar (copy of the assortment-translation pattern); open when adding/running persona tr |
|
|
47
|
+
| [Persona nav-visibility gate + user nav pins (keyed by Core.SurfaceElements uuid)](features/persona-nav-visibility-gate.md) | Sidebar-nav persona gate in `_Model_Core_Surface` (`Client.PersonaNavVisibility`) and user nav pins (`Client.UserNavPreferences`), both keyed by Core `SurfaceEl |
|
|
47
48
|
| [Record Change Audit Log (Logs_<Client>.Record / RecordField) — reading a field's history](features/record-change-audit-log.md) | Field-level change audit trail per tenant; open to answer "who changed this field, to what, when" or to tell a direct SQL edit from an in-app change. |
|
|
48
49
|
| [Recursive Item Fulfillments (upstream mirroring)](features/recursive-item-fulfillments.md) | Engine that mirrors a downstream ItemFulfillment up the SO→PO→SO chain onto every customer-facing SO; open when touching IF creation, bundle scaling, or upstrea |
|
|
49
50
|
| [Sales-order denial reason (`ApprovalDecisions.note`) — capture, the denial-details card, and the read-only workflow mode](features/sales-order-denial-reason.md) | Map of the SO approval denial reason (`ApprovalDecisions.note`) — where it is written, read, and displayed; open when building any denial-reason display, banner |
|
|
@@ -53,6 +54,7 @@
|
|
|
53
54
|
| [Shipping Carriers & Carrier Account Numbers (records, matching, duplication, deletion)](features/shipping-carriers-and-accounts.md) | Per-client `ShippingCarriers` / `ShippingCarrierAccountNumbers` model, how the syncs match/create carrier rows, and the duplication + blank-carrier + pagination |
|
|
54
55
|
| [_String helpers — ASCII-safe HTML entity encoding (and the parseBetween trap)](features/string-html-entity-helpers.md) | `_String` static string utilities: use `encodeToAsciiHtmlEntities` for any dynamic text going into a 2.0 email; open when escaping email text or parsing substri |
|
|
55
56
|
| [Surface FIELD ↔ RecordField binding (maxLength / validationType / isWritable fold-in)](features/surface-field-recordfield-binding.md) | Resolver capability that folds `Core.RecordFields` input-contract metadata (maxLength/validationType/isWritable) onto a surface FIELD element; open when a 2.5 s |
|
|
57
|
+
| [DEFERRED — re-key Client.SurfaceOverrides from numeric Core ids to uuids](features/surface-overrides-uuid-rekey.md) | Deferred: `Client.SurfaceOverrides.surfaceId`/`surfaceElementId` still point at Core rows by numeric id; open before a client's first override rows on an env wh |
|
|
56
58
|
| [Surface Resolver (_Model_Core_Surface::resolve — replaces Page::meta)](features/surface-resolver.md) | Runtime for the platform Surface UI layer (`_Model_Core_Surface::resolve`/`metaGroup`, replaces `Page::meta`); open for surface resolution, the override cascade |
|
|
57
59
|
| [Table-View Hyperlink Columns (meta → ACL → computed URL → render)](features/tableview-hyperlink-columns.md) | How a 2.0 table-view column renders its value as a clickable link (`hyperlinkRecordFieldId` → ACL field-read → computed URL → render); open when a configured hy |
|
|
58
60
|
| [TableView joins (TableViewJoins → SQL) — aliasing, chained multi-hop joins, ACL](features/tableview-joins.md) | How `TableViewJoins` rows become a view's join SQL — aliasing, chained multi-hop joins, ACL gating, and the three ways a view breaks; open before adding/rebasin |
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: "Persona nav-visibility gate + user nav pins (keyed by Core.SurfaceElements uuid)"
|
|
3
|
+
framework: "2.0"
|
|
4
|
+
repo: _underscore
|
|
5
|
+
project: _Underscore
|
|
6
|
+
client: shared
|
|
7
|
+
type: feature
|
|
8
|
+
status: active
|
|
9
|
+
updated: 2026-09-29
|
|
10
|
+
owners: [jcardinal]
|
|
11
|
+
files:
|
|
12
|
+
- _underscore/Model/Core/Surface.php
|
|
13
|
+
- _underscore/Model/Client/PersonaNavVisibility.php
|
|
14
|
+
- _underscore/Model/Client/UserNavPreference.php
|
|
15
|
+
- dbchanges2/Client/2026-09-29a - NavItemSurfaceElementUuidColumns.sql
|
|
16
|
+
- dbchanges2/Client/2026-09-29b - DropNavItemSurfaceElementIdColumns.sql
|
|
17
|
+
- dbchanges2/Core/2026-09-29a - UserNavPreferencesNavItemUuidRecordField.sql
|
|
18
|
+
related:
|
|
19
|
+
- surface-resolver.md
|
|
20
|
+
- surface-overrides-uuid-rekey.md
|
|
21
|
+
- ../../dbchanges2/features/surface-layer-schema.md
|
|
22
|
+
---
|
|
23
|
+
|
|
24
|
+
Sidebar-nav persona gate in `_Model_Core_Surface` (`Client.PersonaNavVisibility`) and user nav pins (`Client.UserNavPreferences`), both keyed by Core `SurfaceElements.uuid`; open before touching nav visibility, pins, or any Client row that points at a Core Surface element.
|
|
25
|
+
|
|
26
|
+
## Summary
|
|
27
|
+
|
|
28
|
+
- Gate runs only when the surface `config.renderAs === 'nav'`. Default-deny: a nav element survives only if one of the caller's personas has a `PersonaNavVisibility` grant for it in the request app.
|
|
29
|
+
- Grants and pins store `navItemSurfaceElementUuid` (CHAR(36), lowercase) = `Core.SurfaceElements.uuid`. The numeric `navItemSurfaceElementId` column was dropped 2026-09-29.
|
|
30
|
+
- Rule: never key a Client row to a Core Surface/SurfaceElement by numeric id. Core ids shift when Core is rebuilt or copied to another env; seeded uuids are the same everywhere.
|
|
31
|
+
|
|
32
|
+
## How it works
|
|
33
|
+
|
|
34
|
+
1. `_buildBundle` step 5b: `$isNavGated` = `config.renderAs === 'nav'`. False → nothing runs, no element dropped (user menu uses `renderAs='menu'`). Do not widen this check.
|
|
35
|
+
2. App id = `$api->app->id`, else `Surfaces.appId`.
|
|
36
|
+
3. `_grantedNavItemSurfaceElementUuids($personaIds, $appId)`: one query on the client DB (`personaId IN (...) AND appId = ... AND navItemSurfaceElementUuid IS NOT NULL`) → set keyed by lowercase uuid. No personas or no app → empty set → all nav elements hidden.
|
|
37
|
+
4. Per element: drop if `strtolower($element->uuid)` is not in the set.
|
|
38
|
+
5. Cluster-safe: grant uuids are read on the client connection and matched in PHP against the Core elements. No cross-database SQL.
|
|
39
|
+
|
|
40
|
+
**Pins (`Client.UserNavPreferences`, Core.Records 349):** RecordField 2645 = `navItemSurfaceElementUuid` (2487 = old id field, retired). `prePost` interceptor stamps `userId` from the JWT and `appId` (`$api->app->id`, fallback 34), checks uuid format, lowercases it.
|
|
41
|
+
|
|
42
|
+
## Gotchas
|
|
43
|
+
|
|
44
|
+
- Model field is `FIELD_CHAR`, not `FIELD_CHAR_UUID`. `FIELD_CHAR_UUID` auto-generates a value, which would overwrite the FK.
|
|
45
|
+
- Symptom of id drift: sidebar-nav returns only the theme (all elements dropped). Seen on qa-alpha 2026-09-29: Core rebuilt from prod, grants pointed at ids 114-134, local elements were 241-261 (+127).
|
|
46
|
+
- `Client/2026-09-29b` deletes old id-only grants only for a persona+app that already has uuid grants, then sets the uuid NOT NULL. A client never re-keyed fails loud (`Invalid use of NULL`) instead of losing its whole sidebar. Fix: write that client's uuid grant seed (pattern: `Client_True/2026-09-29a`), then re-run.
|
|
47
|
+
- Run order: `Client/2026-09-29a` + client grant seeds → code live → `Client/2026-09-29b` → `Core/2026-09-29b` (removes RecordField 2487).
|
|
48
|
+
- `Client.SurfaceOverrides` still uses numeric Core ids — same risk, deferred: [surface-overrides-uuid-rekey](surface-overrides-uuid-rekey.md).
|
|
49
|
+
|
|
50
|
+
## Change history
|
|
51
|
+
- 2026-09-29 — Grants + pins re-keyed from numeric SurfaceElements id to uuid; `_grantedNavItemSurfaceElementIds` renamed `_grantedNavItemSurfaceElementUuids` (_underscore `8e299b1b`, dbchanges2 `66ba056`) (jcardinal)
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: "DEFERRED — re-key Client.SurfaceOverrides from numeric Core ids to uuids"
|
|
3
|
+
framework: "2.0"
|
|
4
|
+
repo: _underscore
|
|
5
|
+
project: _Underscore
|
|
6
|
+
client: shared
|
|
7
|
+
type: feature
|
|
8
|
+
status: deferred
|
|
9
|
+
updated: 2026-09-29
|
|
10
|
+
owners: [jcardinal]
|
|
11
|
+
files:
|
|
12
|
+
- _underscore/Model/Core/Surface.php
|
|
13
|
+
- _underscore/Model/Client/SurfaceOverride.php
|
|
14
|
+
related:
|
|
15
|
+
- persona-nav-visibility-gate.md
|
|
16
|
+
- surface-resolver.md
|
|
17
|
+
---
|
|
18
|
+
|
|
19
|
+
Deferred: `Client.SurfaceOverrides.surfaceId`/`surfaceElementId` still point at Core rows by numeric id; open before a client's first override rows on an env whose Core can be rebuilt.
|
|
20
|
+
|
|
21
|
+
## Summary
|
|
22
|
+
|
|
23
|
+
Not built. `_Model_Core_Surface::_loadOverrides` matches `SurfaceOverrides.surfaceId` / `surfaceElementId` to Core numeric ids. Same failure as the nav gate before 2026-09-29 ([persona-nav-visibility-gate](persona-nav-visibility-gate.md)): once a client has override rows and Core is rebuilt/copied with different AUTO_INCREMENT ids, overrides silently miss or land on the wrong element.
|
|
24
|
+
|
|
25
|
+
## How it works
|
|
26
|
+
|
|
27
|
+
Planned fix (do before first client use on such an env):
|
|
28
|
+
1. Add `surfaceUuid` / `surfaceElementUuid` CHAR(36) to `Client.SurfaceOverrides` (`FIELD_CHAR`, not `FIELD_CHAR_UUID`).
|
|
29
|
+
2. `_loadOverrides`: match by uuid on the client connection, join to Core elements in PHP (no cross-database SQL).
|
|
30
|
+
3. Forward-only re-key seeds per client, then drop the id columns with a fail-loud NOT NULL step (same shape as `Client/2026-09-29b`).
|
|
31
|
+
|
|
32
|
+
## Gotchas
|
|
33
|
+
|
|
34
|
+
- Elements seeded with reserved id literals are stable across envs; the risk is elements that took AUTO_INCREMENT ids (e.g. the `navigation` surface's elements). The framework-rules "Bounded exemption — the Surface tables" stays as-is until this is built.
|
|
35
|
+
|
|
36
|
+
## Change history
|
|
37
|
+
- 2026-09-29 — Recorded as deferred after the nav-gate uuid fix exposed the same numeric-id risk (jcardinal)
|
|
@@ -36,6 +36,8 @@ related:
|
|
|
36
36
|
- sales-order-status-filter-surface.md
|
|
37
37
|
- surface-field-recordfield-binding.md
|
|
38
38
|
- ../../toga25-supply/features/side-navigation-and-default-route.md
|
|
39
|
+
- persona-nav-visibility-gate.md
|
|
40
|
+
- surface-overrides-uuid-rekey.md
|
|
39
41
|
---
|
|
40
42
|
|
|
41
43
|
Runtime for the platform Surface UI layer (`_Model_Core_Surface::resolve`/`metaGroup`, replaces `Page::meta`); open for surface resolution, the override cascade, vocab/theme/message resolution, client seeding, caching, and 403/empty-bundle debugging.
|
|
@@ -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-29
|
|
10
10
|
owners: [jcardinal]
|
|
11
11
|
files:
|
|
12
12
|
- api2/health
|
|
@@ -32,6 +32,7 @@ The EB/ALB liveness probe (`/health`) is served two ways, and the **load-bearing
|
|
|
32
32
|
- **`api2/health`** — a one-line static file (`OK`). Apache serves it directly.
|
|
33
33
|
- **`api2/.platform/httpd/conf.d/health_probe.conf`** — comment-only documentation of why the probe is static (no directives; explains the decision at the Apache layer).
|
|
34
34
|
- **Why it works without a rewrite fight:** `api2/.htaccess` only rewrites to `index.php` when the request target is **not a real file** (`RewriteCond %{REQUEST_FILENAME} !-f`). Because `health` is a real file, the rewrite is skipped and Apache serves it straight.
|
|
35
|
+
- **`api2/.ebextensions/health-check.config`** (2026-09-29) — pins the EB default process health check: `aws:elasticbeanstalk:environment:process:default` → `HealthCheckPath: /health`, `MatcherHTTPCode: 200`. Without it EB checks `/`, which returns 405 → env Red/Severe, `Target.ResponseCodeMismatch`. Applies on the next deploy of each env.
|
|
35
36
|
- **Why `enforce_https` doesn't redirect it:** EB health checks arrive on **HTTP:80 with no `X-Forwarded-Proto`**, and `enforce_https.conf` only redirects requests carrying the forwarded-proto marker — so the probe is not bounced to HTTPS.
|
|
36
37
|
|
|
37
38
|
**PHP short-circuit (for `/v2/health` and direct hits).** At the top of `Index.php::api()`, before host dispatch and before the Core/Logs DB bootstrap. Matches against a class constant:
|
|
@@ -56,7 +57,14 @@ So **all** these probe variants return 200 and never touch routing or the DB: `/
|
|
|
56
57
|
- **Do not** move `/health` back into a PHP-served route or rename/remove `api2/health` — that re-couples EB liveness to PHP-FPM worker availability, the exact failure this fixed (see [api2 architecture — EB "Degraded" health](../architecture.md)).
|
|
57
58
|
- Health probes must degrade to a 200 short-circuit, not enter routing. Add new probe paths to `HEALTH_CHECK_ROUTES` in normalized form, never as a new exact-string branch.
|
|
58
59
|
|
|
60
|
+
- Env Red/Severe with `Target.ResponseCodeMismatch` → check the target group health-check path is `/health`, not `/`. The `.ebextensions` file sets it; an env not yet redeployed still has the old path.
|
|
61
|
+
- Git Bash path-converts AWS CLI args (`/health` → `C:/Program Files/Git/health`). Prefix the command with `MSYS_NO_PATHCONV=1`.
|
|
62
|
+
|
|
59
63
|
## Related
|
|
60
64
|
|
|
61
65
|
- [api2 architecture — EB "Degraded" health](../architecture.md)
|
|
62
66
|
- [V2 API error codes](v2-api-error-codes.md)
|
|
67
|
+
|
|
68
|
+
## Change history
|
|
69
|
+
- 2026-09-29 — Added `.ebextensions/health-check.config` (HealthCheckPath `/health`, matcher 200); api-qa-alpha was Red on `/` 405 (api2 `885d114`) (jcardinal)
|
|
70
|
+
- 2026-08-28 — `/health` moved to a static Apache file, off PHP-FPM (jcardinal)
|
|
@@ -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-29
|
|
10
10
|
owners: ["bala", "rgirish"]
|
|
11
11
|
files:
|
|
12
12
|
- api2/Component/Api/V2/V2.php
|
|
@@ -17,6 +17,7 @@ related:
|
|
|
17
17
|
- ../../../clients/compass-usa/features/item-catalogs-and-duplicate-items.md
|
|
18
18
|
- ../../../clients/elite/features/supply2-tableview-config-drift.md
|
|
19
19
|
- tableview-field-metadata.md
|
|
20
|
+
- ../../_underscore/features/calculated-sql-fields.md
|
|
20
21
|
---
|
|
21
22
|
|
|
22
23
|
## Summary
|
|
@@ -167,8 +168,40 @@ None — engine behavior. A specific client's view may carry its own `apiWhereCl
|
|
|
167
168
|
- **Operator allowlist.** `parseOptionsWhere` whitelists the operators above and rejects unknown
|
|
168
169
|
ones (e.g. `regexp` returns a 500) — see the Compass cost-centers doc. Numeric/regex filtering
|
|
169
170
|
that the grammar can't express must be done client-side or by adding an operator to api2.
|
|
171
|
+
- **⚠ The FIELD name is never validated — an unknown one is a bare 500 with an EMPTY body.**
|
|
172
|
+
`parseOptionsWhere` maps only the operator token; the field slot passes straight into the SQL.
|
|
173
|
+
A name no model declares reaches MySQL as `Unknown column '<x>' in 'where clause'`, nothing
|
|
174
|
+
catches it, and the request dies as a 500 with **no message at all** (the 1.0 monitor then reports
|
|
175
|
+
its fallback literal *"No message found in response payload"* — see
|
|
176
|
+
[v2-api-error-codes](v2-api-error-codes.md) note 13). Live case: a `_isServiceClass` condition
|
|
177
|
+
stored on Elite's `inventory_items` clause 500'd `GET /v2/items` **91 times in 5 minutes across
|
|
178
|
+
7 prod api2 instances** (2026-09-17). **Open gap:** api2 has no guard that rejects an unknown
|
|
179
|
+
`_`-prefixed field with a 400 before it reaches MySQL.
|
|
180
|
+
- **⚠ Deploy order: the `_underscore` model field ships BEFORE the migration that references it.**
|
|
181
|
+
An `_`-prefixed name in a clause only resolves if the model declares it `FIELD_SQL` with a
|
|
182
|
+
matching same-name static (e.g. `_qtyOnHand` on `_Model_Client_Item`) — see
|
|
183
|
+
[calculated SQL fields](../../_underscore/features/calculated-sql-fields.md). A clause is **data**,
|
|
184
|
+
so it goes live the instant the `dbchanges2` file runs, with no deploy gate; applying it first
|
|
185
|
+
takes the listing endpoint down for that tenant. Check the client's own model too — a
|
|
186
|
+
`_Model_<Client>_Item` inherits from `_Model_Client_Item` and may declare neither.
|
|
187
|
+
- **An unexplained field in a `where=` lives in the DB, not the frontend.** Filters reaching api2
|
|
188
|
+
are stored in `Client_<Tenant>.TableViews.apiWhereClause`, not written in app code. When a field
|
|
189
|
+
name appears in **no** repo and **no** git history, query `TableViews` across the client DBs —
|
|
190
|
+
that is where it is.
|
|
170
191
|
|
|
171
192
|
## Change history
|
|
193
|
+
- 2026-09-29 — **Recorded that an unvalidated field name in a stored clause is a production
|
|
194
|
+
outage class, not a typo.** `parseOptionsWhere` maps only the operator and passes the field slot
|
|
195
|
+
straight to MySQL, so an undeclared `_`-prefixed name becomes `Unknown column` → an uncaught
|
|
196
|
+
500 with an **empty body**. Live case: a draft `_isServiceClass` condition on Elite's
|
|
197
|
+
`inventory_items` view 500'd `GET /v2/items` 91 times in 5 minutes across 7 prod api2 instances
|
|
198
|
+
(2026-09-17); `_isServiceClass` was declared on no model and existed in no repo or git history.
|
|
199
|
+
Added the deploy-ordering rule (the `_underscore` `FIELD_SQL` field must be deployed **before**
|
|
200
|
+
the `dbchanges2` file that references it, because a clause is data and needs no deploy), the
|
|
201
|
+
debugging path (an unexplained `where=` field lives in `TableViews.apiWhereClause`, not the
|
|
202
|
+
frontend repo), and the open gap that api2 rejects unknown operators but not unknown fields.
|
|
203
|
+
Diagnosis only, no code changed. Prod re-verified 2026-09-29: zero `TableViews` rows across all
|
|
204
|
+
37 `Client_*` databases reference the field. (bala)
|
|
172
205
|
|
|
173
206
|
- 2026-09-17 — **Recorded that subqueries ARE possible in a stored `apiWhereClause`**: the parser
|
|
174
207
|
splits on literal spaces, so writing `/**/` in place of every space (MySQL reads it as
|
package/knowledge/INDEX.md
CHANGED
|
@@ -19,7 +19,7 @@ _Auto-generated by `knowledge.js index`. Do not hand-edit._
|
|
|
19
19
|
|
|
20
20
|
## 2.0 framework
|
|
21
21
|
|
|
22
|
-
- **_underscore** (_Underscore) _(framework core)_ —
|
|
22
|
+
- **_underscore** (_Underscore) _(framework core)_ — 92 doc(s) → [2.0/apps/_underscore/INDEX.md](2.0/apps/_underscore/INDEX.md)
|
|
23
23
|
- **worker2** (Worker) — 80 doc(s) → [2.0/apps/worker2/INDEX.md](2.0/apps/worker2/INDEX.md)
|
|
24
24
|
- **api2** (API) — 26 doc(s) → [2.0/apps/api2/INDEX.md](2.0/apps/api2/INDEX.md)
|
|
25
25
|
- **dbchanges2** (Database Changes) _(framework core)_ — 22 doc(s) → [2.0/apps/dbchanges2/INDEX.md](2.0/apps/dbchanges2/INDEX.md)
|
|
@@ -6,7 +6,7 @@ project: Database Changes
|
|
|
6
6
|
client: elite
|
|
7
7
|
type: client-feature
|
|
8
8
|
status: active
|
|
9
|
-
updated: 2026-09-
|
|
9
|
+
updated: 2026-09-29
|
|
10
10
|
owners: [tcox, bala, rgirish]
|
|
11
11
|
files:
|
|
12
12
|
- dbchanges2/Client_Elite/
|
|
@@ -297,6 +297,17 @@ for the technique and the parser mechanics.
|
|
|
297
297
|
required a `_isServiceClass` model field on `_Model_Elite_Item`; **that field does not exist and is
|
|
298
298
|
not needed** — do not add one.
|
|
299
299
|
|
|
300
|
+
**⚠ That earlier draft reached PRODUCTION and took `/v2/items` down.** Before the rewrite, the
|
|
301
|
+
stored clause was `(Items.partNumber:excludes:SVC-,_isServiceClass:eq:0)`. `_isServiceClass` is
|
|
302
|
+
declared on neither `_Model_Client_Item` nor `_Model_Elite_Item`, so the raw name went into the SQL
|
|
303
|
+
and MySQL threw `Unknown column '_isServiceClass' in 'where clause'` — **91 empty-body 500s on
|
|
304
|
+
`GET /v2/items` in 5 minutes across 7 prod api2 instances (2026-09-17)**. A stored clause needs no
|
|
305
|
+
deploy, so it went live the moment the migration ran. Closed: `2026-09-15a` reverted the clause and
|
|
306
|
+
the current one uses only `_qtyOnHand` / `_qtyAvailable`, which **are** declared `FIELD_SQL`.
|
|
307
|
+
Re-verified 2026-09-29 — zero `TableViews` rows in any of the 37 prod `Client_*` databases mention
|
|
308
|
+
`ServiceClass`. The general rule and the debugging path are in
|
|
309
|
+
[apiWhereClause row filtering](../../../2.0/apps/api2/features/tableview-apiwhereclause-row-filtering.md#gotchas--known-issues).
|
|
310
|
+
|
|
300
311
|
### Why the class check replaced the `SVC-` prefix
|
|
301
312
|
|
|
302
313
|
The `SVC-` prefix was an unreliable signal. Verified on **prod `Client_Elite`** (2026-09-15):
|
|
@@ -388,6 +399,15 @@ the old `SVC-` prefix check back into the clause.
|
|
|
388
399
|
[error-reporting-issue-event](../../../2.0/apps/_underscore/features/error-reporting-issue-event.md).
|
|
389
400
|
|
|
390
401
|
## Change history
|
|
402
|
+
- 2026-09-29 — **Recorded that the abandoned `_isServiceClass` draft clause actually shipped to
|
|
403
|
+
production and caused an outage**, which this doc previously reported only as "does not exist and
|
|
404
|
+
is not needed". The stored clause `(Items.partNumber:excludes:SVC-,_isServiceClass:eq:0)` put an
|
|
405
|
+
undeclared field into the SQL, giving `Unknown column '_isServiceClass' in 'where clause'` and 91
|
|
406
|
+
empty-body 500s on `GET /v2/items` in 5 minutes across 7 prod api2 instances (2026-09-17).
|
|
407
|
+
Incident is closed — `2026-09-15a` reverted the clause, the live one uses only the declared
|
|
408
|
+
`FIELD_SQL` fields `_qtyOnHand` / `_qtyAvailable`, and a 2026-09-29 sweep of all 37 prod
|
|
409
|
+
`Client_*` databases found no `TableViews` row mentioning `ServiceClass`. Diagnosis only, no code
|
|
410
|
+
changed. (bala)
|
|
391
411
|
- 2026-09-17 — **Replaced the `SVC-` partNumber prefix filter with an ItemClass-hierarchy check**
|
|
392
412
|
on the `inventory_items` view (`2026-09-15a - InventoryHideServiceClassItems.sql`): the clause
|
|
393
413
|
now walks the `ItemClasses` parent chain and excludes items whose ROOT class id is 1, 22 or 40,
|
package/package.json
CHANGED