toga-ai 1.0.798 → 1.0.799

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: Library
6
6
  client: shared
7
7
  type: feature
8
8
  status: active
9
- updated: 2026-09-08
9
+ updated: 2026-09-11
10
10
  owners: [jcardinal, mhammontree, bala]
11
11
  files:
12
12
  - library/app/api/toga2.php
@@ -79,6 +79,17 @@ belong to the NetSuite importer — documented in the per-client-sync doc, not h
79
79
  - Returns the decoded response object; callers read `->data->{resource}`, `->meta->nextPage`,
80
80
  `->isSuccess`, `->status`, `->messages[].code`.
81
81
 
82
+ > **The "Invalid API Response" exception now LEADS with the api2 message codes (2026-09-11).**
83
+ > `send()`'s failure exception used to `print_r` the whole response envelope, whose header prints
84
+ > first — so on a real rejection the useful api2 code (e.g. `EV-10: ...`) fell **past** the
85
+ > `Logs.Event.errorMessage` `varchar(255)` limit and was **truncated away**, making the failure look
86
+ > "quiet" (this is exactly what hid the NYCHH duplicate-`Units` EV-10 — see the
87
+ > [per-client sync doc](../../worker/features/netsuite-togasupply-per-client-sync.md)). The exception
88
+ > message now puts the `->messages[].code: text` list **first**, via a new private helper
89
+ > `describeApiMessages()` (~L726), then the `print_r` dump. The helper **caps each message's text at
90
+ > 160 chars** so an echoed customer value cannot land in full in the log table (cso point). Net: the
91
+ > real api2 cause self-reveals in `Logs.Event` on the next run instead of being cut off.
92
+
82
93
  > **`send()` ALWAYS returns something carrying `isSuccess` — so `empty($response->isSuccess)` is a
83
94
  > safe failure check on anything it returns, GETs included** (verified 2026-08-12, ~L432–450). It
84
95
  > returns early **only** when `isSuccess` is true; when `isSuccess` is false and
@@ -413,6 +424,15 @@ enable flags** and an optional `$monitorTogadeskDepartmentIds[]`:
413
424
 
414
425
  ## Change history
415
426
 
427
+ - 2026-09-11 — **Observability: `send()`'s "Invalid API Response" exception now leads with the api2
428
+ message codes.** Added a private `describeApiMessages()` helper (~L726) that renders the
429
+ `->messages[].code: text` list (each text capped at 160 chars, cso point) and prepended it to the
430
+ exception message ahead of the `print_r` dump (~L636). Reason: `Logs.Event.errorMessage` is
431
+ `varchar(255)` and the old dump printed the envelope header first, pushing the real api2 code (e.g.
432
+ `EV-10`) past 255 chars where it was truncated — so failures looked "quiet". This made the NYCHH
433
+ duplicate-`Units` EV-10 self-reveal; the underlying case-mismatch fix and the three diagnosis traps
434
+ are on the [per-client sync doc](../../worker/features/netsuite-togasupply-per-client-sync.md). Library
435
+ only, `php -l` clean; **written, not committed/deployed**. (jcardinal)
416
436
  - 2026-09-08 - Added `updateTransferOrderStageToClosed(&$nsOrder, array &$clientConfiguration): bool`
417
437
  and the constant `TRANSFER_ORDER_STATUS__CLOSED = 'closed'` (also now used by the `case 'Closed'` in
418
438
  the transfer-order status switch). It is a **stage-only** writer: it never touches line items, so it
@@ -6,7 +6,7 @@ project: Worker
6
6
  client: shared
7
7
  type: feature
8
8
  status: active
9
- updated: 2026-09-09
9
+ updated: 2026-09-11
10
10
  owners: ["dfranks", "bala", "jcardinal", "mhammontree", "snaredla", "rgirish"]
11
11
  files:
12
12
  - worker/crons/toga2/netsuite/common_sync_togasupply.php
@@ -296,6 +296,49 @@ hit **every section and every client**, not just the by-reference ones.
296
296
  GET's UTC. `strtotime()` and `App_Date::convertToSQLDate()` both use the script tz (Chicago) and are
297
297
  the trap. Any manual cursor rewind value you set by hand must also be **Eastern**.
298
298
 
299
+ ### ⚠ ITEM_RECEIPTS froze at RUNNING on a case-mismatched Units serial → duplicate-unit INSERT (EV-10 / 1062) (found + fixed 2026-09-11, NYCHH prod)
300
+
301
+ `syncItemReceiptFromNetsuite()` (`library/app/api/toga2.php` ~L4022) builds a PHP array of the
302
+ client's existing `Units` keyed by **serial number** to decide PUT (update) vs POST (create). The
303
+ DB `GET /units` (`Units.serialNumber IN (...)`) is **case-insensitive** and returns the **stored**
304
+ casing (e.g. `'na100'`), but the code keyed the PHP lookup by that **raw stored** value while reading
305
+ it with the **uppercased** serial (`strtoupper` at ~L3905). **PHP array keys are case-sensitive** → the
306
+ lookup missed the existing row → took the **create** path → `POST /units` collided on the
307
+ case-insensitive `Units.itemId_serialNumber` unique key → MySQL **1062** / api2 **EV-10** → the
308
+ ITEM_RECEIPTS section **threw before its cursor PUT**, freezing at RUNNING (and ITEM_FULFILLMENTS,
309
+ which runs behind it, froze with it).
310
+
311
+ - **Proven on NYCHH:** `Client_Nychh.Units` 23400-23409 store serial `'na100'..'na109'` (lowercase),
312
+ `c_netsuiteInternalInventoryAssignmentId` NULL; the sync sent `'NA100'`.
313
+ - **Fix (library only, `php -l` clean):** `strtoupper()` on **both** the array key **and** the reads,
314
+ so the PHP lookup is case-insensitive like the DB. A **second, half-fixed copy** of the same lookup
315
+ lower in the file (~L6326) already uppercased the **key** but not its two **reads** — made them
316
+ match too. php-reviewer + cso both cleared it (SAFE TO SHIP). **Written, NOT committed/deployed.**
317
+ - **Lesson:** whenever a PHP lookup mirrors a case-insensitive DB column, normalize case on **every**
318
+ side — the key write and every read. One un-normalized read reintroduces the duplicate-create path.
319
+
320
+ #### Diagnosis: three traps that hid this EV-10 (2026-09-11)
321
+
322
+ The section looked "quiet" (RUNNING, no loud crash-loop under the usual filter) because of three
323
+ things worth knowing for any 1.0-sync freeze:
324
+
325
+ - **areaPath is NOT only `crons/toga2/netsuite`.** 1.0 sync NetSuite/api2 errors also log under
326
+ `context.REFERENCE.areaPath` = `library/app/api` and `library/app/api/netsuite` — filtering on the
327
+ cron path alone **misses** them. (The "Filter on areaPath = `crons/toga2/netsuite`" note near the top
328
+ of this doc is therefore an *incomplete* filter — widen it to these two areas as well.)
329
+ - **EV-10 is api2's GENERIC "save failed" wrapper — the real DB error is NOT in base `Logs`.** The
330
+ underlying `1062 Duplicate entry ... for key ...` lives in `Logs_<Client>.Api`: filter
331
+ `responsePayload LIKE '%EV-10%'` (or join by `transactionId`) for route + `requestPayload` + the full
332
+ `responsePayload` carrying the real MySQL error. (Same `Logs_<Client>.Api` chain as the other rejection
333
+ gotchas in this doc.)
334
+ - **Some crashes never record at all.** The worker's error-log write to the logs DB can time out
335
+ (`mysqli_select_db ... errno=110 Connection timed out`, e.g. `Logs.Issue` 588/589), so a section can
336
+ freeze with **no** `Logs.Event`/`Issue` row — read `Client_<X>.Parameters` for the stuck RUNNING
337
+ section as the primary signal, not the log tables.
338
+ - Cross-link: the `send()` "Invalid API Response" exception was also changed 2026-09-11 to **lead with
339
+ the api2 message codes** so this EV-10 self-reveals in `Logs.Event` next time — see
340
+ [App_Api_Toga2](../../library/features/toga2-api-client-and-bridge.md).
341
+
299
342
  ### ⚠ Per-record isolation is SECTION-SPECIFIC — and SALES_ORDERS has NONE (corrected 2026-08-17)
300
343
 
301
344
  Whether a bad record is "stepped over" or freezes the section depends entirely on whether **that
@@ -1439,6 +1482,19 @@ library (or vice versa) crashes GroWrk and Adyen on their next sync run.
1439
1482
 
1440
1483
  ## Change history
1441
1484
 
1485
+ - 2026-09-11 — **ITEM_RECEIPTS froze at RUNNING on NYCHH: a case-mismatched `Units` serial lookup
1486
+ (library, fix written not deployed).** `syncItemReceiptFromNetsuite` (`library/app/api/toga2.php`
1487
+ ~L4022) keyed a PHP existing-`Units` array on the DB's stored serial casing (`'na100'`) but read it
1488
+ with the uppercased serial (`'NA100'`, `strtoupper` ~L3905); PHP array keys are case-sensitive, so the
1489
+ lookup missed the row, took the create path, and hit the case-insensitive `Units.itemId_serialNumber`
1490
+ unique key → MySQL **1062** / api2 **EV-10** → the section threw before its cursor PUT and froze,
1491
+ dragging ITEM_FULFILLMENTS behind it. Fixed by `strtoupper()`-ing **both** the array key and the reads
1492
+ (and the half-fixed second copy ~L6326, which had the key but not its two reads). Proven on
1493
+ `Client_Nychh.Units` 23400-23409 (serial `'na100'..'na109'`). php-reviewer + cso cleared it. Also
1494
+ recorded the three diagnosis traps that hid it: sync errors also log under areaPath `library/app/api`
1495
+ and `library/app/api/netsuite` (not only `crons/toga2/netsuite`); EV-10 is a generic wrapper whose real
1496
+ 1062 lives in `Logs_<Client>.Api` (`responsePayload LIKE '%EV-10%'`); and the worker's logs-DB write can
1497
+ time out (errno 110, `Logs.Issue` 588/589) so some crashes never record. (jcardinal)
1442
1498
  - 2026-09-09 — **The resume cursor was READ on the wrong clock — a silent +1h window skip dropped
1443
1499
  records across ALL 6 sections and ALL clients (worker, deployed + verified).** `startModeIteration`
1444
1500
  (`common_sync_togasupply.php` ~L622) parsed the Eastern-stored cursor with `strtotime()` (script tz
@@ -18,7 +18,7 @@ project: _Underscore
18
18
  client: nychh
19
19
  type: profile
20
20
  status: active
21
- updated: 2026-09-10
21
+ updated: 2026-09-11
22
22
  owners: ["jcardinal", "apeterson", "bala", "akhokhani"]
23
23
  files:
24
24
  - dbchanges2/Client_Nychh/2026-09-02a - TransferOrderNetsuitePushInterceptor.sql
@@ -115,6 +115,16 @@ table views. Client-specific DB change-sets live in `dbchanges2/Client_Nychh/`.
115
115
  written. Proven on IF `6138135` (Eastern 2025-09-04 17:26:58). Fixes and the standing "cursor is
116
116
  always Eastern" rule live in the
117
117
  [per-client sync feature doc](../../1.0/apps/worker/features/netsuite-togasupply-per-client-sync.md).
118
+ - **ITEM_RECEIPTS froze on a case-mismatched serial → duplicate-`Units` EV-10/1062, root-caused + fixed
119
+ (2026-09-11, fix NOT deployed).** The receipts feed threw before its cursor PUT (freezing it and
120
+ ITEM_FULFILLMENTS behind it) because `syncItemReceiptFromNetsuite` keyed its existing-`Units` PHP
121
+ lookup on the DB's stored serial casing (`'na100'`) but read it with the uppercased serial (`'NA100'`);
122
+ the missed row took the create path and collided on the case-insensitive `Units.itemId_serialNumber`
123
+ unique key → MySQL 1062 / api2 EV-10. Proven on `Client_Nychh.Units` 23400-23409
124
+ (serials `'na100'..'na109'`, `c_netsuiteInternalInventoryAssignmentId` NULL). Library-only fix
125
+ (`strtoupper()` on both the key and the reads). This is a **different** duplicate from the open
126
+ sales-order `Items` duplicate below (658-BFZH). Mechanism + the EV-10 → `Logs_Nychh.Api` diagnosis
127
+ traps: [per-client sync doc](../../1.0/apps/worker/features/netsuite-togasupply-per-client-sync.md).
118
128
  - **NYCHH sync scope + data-gap scale + backfill approach (2026-09-09).**
119
129
  - **Scope = 22 NetSuite customers:** parent **28908** "NYC Health + Hospitals" + 21 hospital child
120
130
  customers (`24145, 29273, 29276, 31584, 31910-31916, 31918-31925, 32229, 33674`).
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "toga-ai",
3
- "version": "1.0.798",
3
+ "version": "1.0.799",
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",