toga-ai 1.0.913 → 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.
@@ -6,7 +6,7 @@ project: API
6
6
  client: shared
7
7
  type: feature
8
8
  status: active
9
- updated: 2026-09-17
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
@@ -6,7 +6,7 @@ project: Database Changes
6
6
  client: elite
7
7
  type: client-feature
8
8
  status: active
9
- updated: 2026-09-17
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "toga-ai",
3
- "version": "1.0.913",
3
+ "version": "1.0.914",
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",