toga-ai 1.0.591 → 1.0.592
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/1.0/apps/library/features/error-capture-1-0.md +16 -1
- package/knowledge/1.0/apps/library/features/toga2-api-client-and-bridge.md +18 -1
- package/knowledge/1.0/apps/worker/features/netsuite-togasupply-per-client-sync.md +60 -8
- package/knowledge/2.0/apps/api2/features/api-payload-interceptors.md +9 -2
- package/package.json +1 -1
|
@@ -6,7 +6,7 @@ project: Library
|
|
|
6
6
|
client: shared
|
|
7
7
|
type: feature
|
|
8
8
|
status: active
|
|
9
|
-
updated: 2026-08-
|
|
9
|
+
updated: 2026-08-17
|
|
10
10
|
owners: ["jcardinal", "mhammontree"]
|
|
11
11
|
files:
|
|
12
12
|
- library/app/error/capture.php
|
|
@@ -177,6 +177,15 @@ exists in `api2`/`worker2`. So 1.0 reads the AL1 container config at
|
|
|
177
177
|
|
|
178
178
|
## Gotchas / known issues
|
|
179
179
|
|
|
180
|
+
- **⚠ A grouped `Logs.Issue` row can show a STALE error id — read the live api2 transaction log for
|
|
181
|
+
the current error.** Two facts combine to mislead: (1) `Event.errorMessage` is **`varchar(255)`**,
|
|
182
|
+
so a long API response is **truncated** — often before the trailing error id; and (2)
|
|
183
|
+
`Logs.Issue.subject` / `Issue.errorMessage` hold the **first-occurrence** text, so a long-lived
|
|
184
|
+
issue keeps showing the error id from whenever it was first seen even after the per-call error has
|
|
185
|
+
changed. Observed 2026-08-17: issue `3H` still displayed `EO-1` / `3G-6` (the original
|
|
186
|
+
undefined-method fatal) while the current per-call failure was actually `EV-11` (an FK 1451 on a
|
|
187
|
+
DELETE). **To see what is failing *now*, read the live api2 transaction log
|
|
188
|
+
(`Logs_<client>.Api.responsePayload`) for the specific call — not the grouped `Issue` row.**
|
|
180
189
|
- **Any PHP warning inside capture code would kill the request.** 1.0's
|
|
181
190
|
`App_Error::handleError()` routes **every** PHP warning into `handleException()`, which renders
|
|
182
191
|
the red box and calls `exit()` — so a single warning raised while capturing (an unreachable IMDS
|
|
@@ -220,6 +229,12 @@ exists in `api2`/`worker2`. So 1.0 reads the AL1 container config at
|
|
|
220
229
|
|
|
221
230
|
## Change history
|
|
222
231
|
|
|
232
|
+
- 2026-08-17 — Recorded a diagnostic trap: a grouped `Logs.Issue` row can show a **stale error id**
|
|
233
|
+
because `Event.errorMessage` is `varchar(255)` (truncates a long API response, often before the
|
|
234
|
+
error id) and `Issue.subject`/`errorMessage` keep the **first-occurrence** text — observed issue
|
|
235
|
+
`3H` still showing `EO-1`/`3G-6` while the live per-call error was `EV-11` (FK 1451). Read the live
|
|
236
|
+
`Logs_<client>.Api.responsePayload` for the actual current error. Surfaced diagnosing the Compass
|
|
237
|
+
USA NetSuite SO sync freeze. (jcardinal)
|
|
223
238
|
- 2026-08-05 — Recorded the legacy `DELETE_DOA_PRE-SENTRY_error.php` handler leaking the full
|
|
224
239
|
config array (prod DB / SES / NetSuite / Payeezy / SSL Store credentials + ticket PII) on a
|
|
225
240
|
live towfoundation.togaview.com error page, meaning that host evaluates as dev mode in
|
|
@@ -6,7 +6,7 @@ project: Library
|
|
|
6
6
|
client: shared
|
|
7
7
|
type: feature
|
|
8
8
|
status: active
|
|
9
|
-
updated: 2026-08-
|
|
9
|
+
updated: 2026-08-17
|
|
10
10
|
owners: [jcardinal, mhammontree, bala]
|
|
11
11
|
files:
|
|
12
12
|
- library/app/api/toga2.php
|
|
@@ -328,6 +328,16 @@ enable flags** and an optional `$monitorTogadeskDepartmentIds[]`:
|
|
|
328
328
|
**and** the consuming code. (The general 2.0-side metadata behavior — reverse relations resolved via
|
|
329
329
|
the child model's `FOREIGNKEY_MODEL` and named as the camelCase plural of the child model — is a
|
|
330
330
|
framework-level fact captured separately.)
|
|
331
|
+
- **⚠ Depth alone does NOT return every reverse-hasMany collection — `GET /sales-orders` at depth 4
|
|
332
|
+
omits `itemFulfillmentItems` and `invoiceItems`.** Verified 2026-08-17 against prod
|
|
333
|
+
`Logs_Compass.Api` responses (no fields whitelist): a depth-4 sales-order GET returns
|
|
334
|
+
`salesOrderItems` and the **PO-link bridge** collections
|
|
335
|
+
(`salesOrderItemPurchaseOrderItems`, `purchaseOrderItemSalesOrderItems`) but **not**
|
|
336
|
+
`itemFulfillmentItems` or `invoiceItems`. So any importer code that guards on
|
|
337
|
+
`$salesOrderItem->itemFulfillmentItems` / `->invoiceItems` from that GET is a **silent no-op** —
|
|
338
|
+
same reverse-hasMany trap as above, now with a concrete consequence: the NetSuite SO renumber fix
|
|
339
|
+
keys off a recorded "could-not-delete" set instead of trusting those collections (see
|
|
340
|
+
[NetSuite → TOGa Supply per-client sync](../../worker/features/netsuite-togasupply-per-client-sync.md)).
|
|
331
341
|
- **`TOGA_AIG.Customers.emailAddress` is `varchar(255)`** (V1 legacy, latin1_swedish_ci) — wide
|
|
332
342
|
enough to hold several `'; '`-joined emails, so TRUE-79401 needed **no** schema/dbchanges migration
|
|
333
343
|
(the earlier fear that it was ~VARCHAR(55) was wrong) — but **it is a real ceiling: ~6–7
|
|
@@ -386,6 +396,13 @@ enable flags** and an optional `$monitorTogadeskDepartmentIds[]`:
|
|
|
386
396
|
|
|
387
397
|
## Change history
|
|
388
398
|
|
|
399
|
+
- 2026-08-17 — Recorded that **depth-4 `GET /sales-orders` omits the `itemFulfillmentItems` and
|
|
400
|
+
`invoiceItems` reverse-hasMany collections** (it does return `salesOrderItems` and the PO-link
|
|
401
|
+
bridges), verified against prod `Logs_Compass.Api` — so code guarding on those collections from
|
|
402
|
+
that GET is a silent no-op, reinforcing the reverse-hasMany-fields trap. Surfaced fixing the
|
|
403
|
+
NetSuite SO renumber freeze (Compass USA), which now keys off a recorded "could-not-delete" set;
|
|
404
|
+
see [NetSuite → TOGa Supply per-client sync](../../worker/features/netsuite-togasupply-per-client-sync.md).
|
|
405
|
+
(jcardinal)
|
|
389
406
|
- 2026-08-12 — Recorded the **`send()` return contract**: it always returns a response carrying
|
|
390
407
|
`isSuccess` (early-returns on true, falls through to `return $response` on false, throws when the
|
|
391
408
|
property is absent), so `empty($response->isSuccess)` is a safe failure check on anything it
|
|
@@ -6,7 +6,7 @@ project: Worker
|
|
|
6
6
|
client: shared
|
|
7
7
|
type: feature
|
|
8
8
|
status: active
|
|
9
|
-
updated: 2026-08-
|
|
9
|
+
updated: 2026-08-17
|
|
10
10
|
owners: ["dfranks", "bala", "jcardinal", "mhammontree", "snaredla"]
|
|
11
11
|
files:
|
|
12
12
|
- worker/crons/toga2/netsuite/common_sync_togasupply.php
|
|
@@ -87,13 +87,28 @@ its IDLE write. **There is no timeout, memory cap, or resource limit anywhere in
|
|
|
87
87
|
never *tune the window size down*. (Corollary: a window that has grown back to the cap is positive
|
|
88
88
|
evidence the section is completing.)
|
|
89
89
|
|
|
90
|
-
### ⚠
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
90
|
+
### ⚠ Per-record isolation is SECTION-SPECIFIC — and SALES_ORDERS has NONE (corrected 2026-08-17)
|
|
91
|
+
|
|
92
|
+
Whether a bad record is "stepped over" or freezes the section depends entirely on whether **that
|
|
93
|
+
section's** record loop wraps the `sync*FromNetsuite()` call in a per-record `try/catch`. It is not
|
|
94
|
+
uniform across the six sections, and the earlier blanket claim here ("a per-record failure does not
|
|
95
|
+
stop the cursor — the record is skipped forever") is **false for the SALES_ORDERS section**.
|
|
96
|
+
|
|
97
|
+
- **Where a section DOES isolate per record:** the cursor write (`LAST_SYNC_DATETIME_*`) and
|
|
98
|
+
`finishModeIteration()` run after the loop, so a caught record is skipped and the section still
|
|
99
|
+
reports IDLE and advances. There, **"section IDLE + cursor advancing" is not proof of a complete
|
|
100
|
+
import** — it only proves the run didn't die (recovery = rewind the cursor; re-import is
|
|
101
|
+
idempotent).
|
|
102
|
+
- **⚠ SALES_ORDERS has NO per-record `try/catch`.** The `SALES_ORDERS` foreach in
|
|
103
|
+
`common_sync_togasupply.php` (~L637-673) calls `App_Api_Toga2::syncSalesOrderFromNetsuite()`
|
|
104
|
+
**bare**. `send()` throws on any non-success by default, so **one poison order throws, propagates,
|
|
105
|
+
and aborts the whole section BEFORE** the cursor PUT + `finishModeIteration()`. The mode stays
|
|
106
|
+
`1-RUNNING`, the watermark freezes, the window shrinks ÷3 each run, and the **same** order is
|
|
107
|
+
re-tried and re-throws every 5 minutes — a permanent freeze, not a skip. So a stuck
|
|
108
|
+
`SALES_ORDERS` at `1-RUNNING` with a days-frozen watermark means **it is throwing on a specific
|
|
109
|
+
order** (go read the live api2 transaction log for that client — see the poison-order gotcha
|
|
110
|
+
below). The "shrinking window is a bug signal" rule still holds; the "record is stepped over"
|
|
111
|
+
corollary does **not** for this section.
|
|
97
112
|
|
|
98
113
|
Proven concretely on Elite: sales order **169441** (NetSuite internal id 4624062, trandate
|
|
99
114
|
2024-04-15, customer PO `BEGGS/04152024/…`) was simply absent from `Client_Elite.SalesOrders`
|
|
@@ -361,9 +376,46 @@ Parameters are stored **per client DB** but accessed **through the TOGa2 API**,
|
|
|
361
376
|
a new line is actually created. Full root cause, the prod cleanup migration, and the
|
|
362
377
|
deploy-fix-before-cleanup sequencing live in the
|
|
363
378
|
[Compass ODP Duplicate PO-Line Cleanup](../../../clients/compass-usa/workflows/odp-duplicate-po-line-cleanup.md).
|
|
379
|
+
- **⚠ A single un-deletable renumbered line could freeze the entire SALES_ORDERS section (fixed
|
|
380
|
+
2026-08-17).** When NetSuite renumbers a line, `syncSalesOrderFromNetsuite` renumbers by
|
|
381
|
+
**DELETE `/sales-order-items/{uuid}` then re-create in the per-item loop**. Its "pinned row" guard
|
|
382
|
+
only skipped rows carrying `itemFulfillmentItems` / `salesOrderItemPurchaseOrderItems` /
|
|
383
|
+
`purchaseOrderItemSalesOrderItems`; an **INVOICED** line
|
|
384
|
+
(`InvoiceItems.salesOrderItemId`, `ON DELETE RESTRICT`) was **not** guarded, so the DELETE failed
|
|
385
|
+
with MySQL **1451** → api2 **EV-11** 400, and — because the SALES_ORDERS loop has no per-record
|
|
386
|
+
`try/catch` (see the corrected gotcha above) — the throw aborted the section, freezing Compass USA
|
|
387
|
+
for ~5 days (`NETSUITE_EXECUTION_MODE_SALES_ORDERS='1-RUNNING'`, watermark stuck at
|
|
388
|
+
`2026-08-12 06:13:26`) while it re-tried the same poison order every run (SO **7447** /
|
|
389
|
+
`SalesOrderItem` 47828, invoiced by `InvoiceItem` 25515). **Fix (library-only, PHP 7.2-safe, no
|
|
390
|
+
signature changes)** in `syncSalesOrderFromNetsuite`: (1) the renumber-DELETE now passes
|
|
391
|
+
`$throwExceptionOnApiError=false`, checks `empty($resp->isSuccess)`, and records any row it cannot
|
|
392
|
+
delete in a new `$pinnedRenumberLineUniqueKeys` set (keyed by `c_netsuiteLineUniqueKey`); (2) the
|
|
393
|
+
per-item PUT loop treats a row in that set as **pinned** — it keeps its current `lineNumber` (no
|
|
394
|
+
`UNIQUE(salesOrderId,lineNumber)` collision) and its PUT is made non-throwing + `error_log` **for
|
|
395
|
+
pinned rows only** (normal rows keep throw-on-error so real failures still surface); (3) the
|
|
396
|
+
trailing "remove dropped items" DELETE now skips `invoiceItems` and checks `isSuccess`. Net: **no
|
|
397
|
+
single order can freeze the section again regardless of which `RESTRICT` child blocks a delete**;
|
|
398
|
+
the invoiced line simply stays at its existing `lineNumber` (a documented unresolvable
|
|
399
|
+
NetSuite-vs-invoiced case). Deploy `library` only — no SQL, no other repos — then the section
|
|
400
|
+
completes, mode flips IDLE, and the backlog backfills idempotently. Note the collection guard added
|
|
401
|
+
for `invoiceItems` is **best-effort only**: depth-4 `GET /sales-orders` does **not** return the
|
|
402
|
+
`itemFulfillmentItems` or `invoiceItems` reverse-hasMany collections (only the PO-link bridges), so
|
|
403
|
+
the fix keys off the recorded "could-not-delete" set rather than trusting those collections — see
|
|
404
|
+
[App_Api_Toga2 client & bridge](../../library/features/toga2-api-client-and-bridge.md).
|
|
364
405
|
|
|
365
406
|
## Change history
|
|
366
407
|
|
|
408
|
+
- 2026-08-17 — Fixed a Compass USA ~5-day SALES_ORDERS freeze: an **invoiced** line that NetSuite
|
|
409
|
+
renumbered could not be DELETEd (FK `InvoiceItems.salesOrderItemId` `RESTRICT` → MySQL 1451 →
|
|
410
|
+
api2 EV-11), and because the SALES_ORDERS loop has **no per-record `try/catch`** the throw aborted
|
|
411
|
+
the whole section (mode stuck `1-RUNNING`, watermark frozen, poison order SO 7447 re-tried every
|
|
412
|
+
run). `syncSalesOrderFromNetsuite` now makes the renumber-DELETE non-throwing, records
|
|
413
|
+
un-deletable rows in a `$pinnedRenumberLineUniqueKeys` set, and keeps a pinned row at its existing
|
|
414
|
+
`lineNumber` with a non-throwing PUT (normal rows still throw on error); the trailing dropped-item
|
|
415
|
+
DELETE now skips `invoiceItems` and checks `isSuccess`. **Corrected** the prior gotcha that claimed
|
|
416
|
+
a per-record failure never stops the cursor — that is **section-specific and false for
|
|
417
|
+
SALES_ORDERS**, which has no per-record isolation. Library-only deploy; no SQL/other repos.
|
|
418
|
+
Diagnosis touched worker/api2/_underscore (read-only). (jcardinal)
|
|
367
419
|
- 2026-08-12 — TRUE-80499 (Elite onboarding, shared-engine work). **Fixed** the sparse-client
|
|
368
420
|
`TypeError`: the two per-client item lookups are now initialised at
|
|
369
421
|
`common_sync_togasupply.php:74-75` instead of only inside the items loop, so a client whose items
|
|
@@ -6,8 +6,8 @@ project: API
|
|
|
6
6
|
client: shared
|
|
7
7
|
type: feature
|
|
8
8
|
status: active
|
|
9
|
-
updated: 2026-08-
|
|
10
|
-
owners: ["mhammontree", "dfranks", "bala", "snaredla"]
|
|
9
|
+
updated: 2026-08-17
|
|
10
|
+
owners: ["mhammontree", "dfranks", "bala", "snaredla", "jcardinal"]
|
|
11
11
|
files:
|
|
12
12
|
- api2/Component/Api/V2/V2.php
|
|
13
13
|
- api2/Controller/Index.php
|
|
@@ -448,6 +448,13 @@ and the failing environment**. It is a small table, and the drift is usually exa
|
|
|
448
448
|
|
|
449
449
|
## Change history
|
|
450
450
|
|
|
451
|
+
- 2026-08-17 — Corroborated that the unguarded-dispatch fatal is **systemic, not client-specific**:
|
|
452
|
+
the prod "undefined method" issue bucket (`Logs.Issue` ref 3G / id 59) grouped **both**
|
|
453
|
+
`_Model_Compass_Usa_SalesOrderItem::postPost()` **and** `_Model_Elite_SalesOrder::postPut()` — two
|
|
454
|
+
different tenants, one shared fragility. (This was the original Compass USA failure on 2026-08-12,
|
|
455
|
+
before it was mitigated on the 2.0 side and the client's NetSuite sync progressed to an unrelated
|
|
456
|
+
FK failure downstream.) Reinforces the standing fix direction: guard all four `V2.php` call sites
|
|
457
|
+
with `method_exists()`. Investigation only, no code change. (jcardinal)
|
|
451
458
|
- 2026-08-14 — TRUE-80489, from building the Compass inactive-standalone-item order guard.
|
|
452
459
|
(1) **A PRE interceptor on a PARENT record fires on the nested child POST, with the CHILD as the
|
|
453
460
|
payload.** `POST /sales-orders/{uuid}/sales-order-items` resolves `SalesOrders` as the
|
package/package.json
CHANGED