toga-ai 1.0.757 → 1.0.759

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-02
9
+ updated: 2026-09-03
10
10
  owners: [dfranks, jcardinal, kyalamarthi]
11
11
  files:
12
12
  - library/app/api/netsuite/rest.php
@@ -197,6 +197,23 @@ Forecast2 tables already store.
197
197
  same underlying trap of reusing a sales-order-shaped assumption on a purchase order. Full
198
198
  engine-side impact + the tier-wide per-client backfill live on the
199
199
  [per-client-sync doc](../../worker/features/netsuite-togasupply-per-client-sync.md).
200
+ - **⚠ The three detail-fetch shims were inconsistent about `->lastModifiedDate` — two omitted it and
201
+ froze their sections once a per-record cursor started reading it (fixed 2026-09-03).**
202
+ `fetchItemReceiptById` set `$shim->lastModifiedDate` from its REST header, but **`fetchItemFulfillmentById`
203
+ and `fetchInventoryAdjustmentById` did not** (and InventoryAdjustment's SuiteQL header didn't even SELECT
204
+ `lastmodifieddate`). This was harmless until the 2026-09-02 per-record resume cursor: because
205
+ `syncItemFulfillmentFromNetsuite(&$rec)` / `syncInventoryAdjustmentFromNetsuite(&$rec)` **reassign** their
206
+ by-reference record to the fetch-by-id shim, the engine's cursor PUT then read `$rec->lastModifiedDate` on
207
+ the **replaced** object → *Undefined property: stdClass::$lastModifiedDate* → `App_Error` `exit()` → the
208
+ ITEM_FULFILLMENTS / INVENTORY_ADJUSTMENTS section froze at `1-RUNNING`, window ÷3 (GroWrk's fulfillments
209
+ re-threw every 5 min; InvAdj was latent only because no client had processed one since the rename). **Fix
210
+ (`library` only, `php -l` clean):** `$shim->lastModifiedDate = (string)(...)` in **both** shims —
211
+ fulfillment from the REST header, inventory-adjustment after adding
212
+ `TO_CHAR(lastmodifieddate,'YYYY-MM-DD HH24:MI:SS') AS lastmodifieddate` to its header SELECT. **Lesson:
213
+ a fetch-by-id shim consumed as the replacement for a `&$byReference` record must carry every field the
214
+ caller reads after the call — `lastModifiedDate` first.** Sales orders / POs / invoices don't reassign, so
215
+ they were unaffected. Engine-side detail on the
216
+ [per-client-sync doc](../../worker/features/netsuite-togasupply-per-client-sync.md).
200
217
  - **A shim must produce the SOAP object's SHAPE, not just its scalar fields — an absent nested list
201
218
  crashes 1.0 (2026-08-29).** `fetchCustomerById()`'s `addressbookList` was where the AIG lookup phase
202
219
  crashed: a REST customer with no/odd address made the shim omit the nested `addressbook` structure the
@@ -290,6 +307,14 @@ None — uniform across clients (NetSuite is a single shared account).
290
307
 
291
308
  ## Change history
292
309
 
310
+ - 2026-09-03 — Recorded that **`fetchItemFulfillmentById` and `fetchInventoryAdjustmentById` omitted
311
+ `->lastModifiedDate`** (unlike `fetchItemReceiptById`), which froze ITEM_FULFILLMENTS + INVENTORY_ADJUSTMENTS
312
+ once the 2026-09-02 per-record cursor read that field off the by-reference record these shims replace. GroWrk
313
+ was the reported alert; shared code so all four active togasupply clients (GroWrk, NYCHH, Prudential-EDI,
314
+ S&P Global) were affected. Fixed by setting `$shim->lastModifiedDate` in both shims (adding `lastmodifieddate`
315
+ to InventoryAdjustment's SuiteQL SELECT). `library`-only, `php -l` clean, no data lost, self-heals after
316
+ deploy. Lesson: a fetch-by-id shim used to replace a `&$byReference` record must carry every field the
317
+ caller reads afterward. (jcardinal)
293
318
  - 2026-09-02 — Recorded that **SuiteQL returns date columns in the account DISPLAY format (date-only
294
319
  `M/D/YYYY` here) unless wrapped in `TO_CHAR`** — a bare `lastmodifieddate` silently drops the time and
295
320
  is locale-dependent. This defeated the per-client sync's per-record resume cursor (stored `9/2/2026|id`,
@@ -6,7 +6,7 @@ project: Worker
6
6
  client: shared
7
7
  type: feature
8
8
  status: active
9
- updated: 2026-09-02
9
+ updated: 2026-09-03
10
10
  owners: ["dfranks", "bala", "jcardinal", "mhammontree", "snaredla"]
11
11
  files:
12
12
  - worker/crons/toga2/netsuite/common_sync_togasupply.php
@@ -88,9 +88,27 @@ The single most expensive misreading of this engine. `startModeIteration()` writ
88
88
  `<seconds>-RUNNING` at the **start** of every section and `finishModeIteration()` writes `-IDLE` at
89
89
  the end; the ÷3 `REDUCE_INTERVAL_FACTOR` shrink fires only because the *previous* run never reached
90
90
  its IDLE write. **There is no timeout, memory cap, or resource limit anywhere in the script** — so
91
- "didn't finish" can only mean **it threw**. A collapsing window therefore means *go read Sentry*,
92
- never *tune the window size down*. (Corollary: a window that has grown back to the cap is positive
93
- evidence the section is completing.)
91
+ "didn't finish" can only mean **it threw**. A collapsing window therefore means *go read the error
92
+ in prod* (see below), never *tune the window size down*. (Corollary: a window that has grown back to
93
+ the cap is positive evidence the section is completing.)
94
+
95
+ **Where the error is: prod `Logs.Issue` / `Logs.Event`, NOT Sentry.** Read worker errors from the
96
+ production log DB by default — you do not need a developer to point you there. The base `Logs`
97
+ schema (prod-logs cluster) holds `Issue` (deduped, with `errorMessage` + `trace`) and `Event` (each
98
+ occurrence, with `clientId`, `errorMessage`, `trace`, and a `context` JSON carrying `errorFile` +
99
+ `errorLine`). Find a sync crash like this (NYCHH `clientId = 3`):
100
+
101
+ ```sql
102
+ SELECT e.dtCreated, e.issueId, e.errorMessage, e.context
103
+ FROM Event e
104
+ WHERE e.clientId = 3 AND e.dtCreated > NOW() - INTERVAL 2 DAY
105
+ ORDER BY e.dtCreated DESC;
106
+ ```
107
+
108
+ Filter on the `context.REFERENCE.areaPath` = `crons/toga2/netsuite`. **The aggregated `Issue.trace`
109
+ can show an OLD line; trust the newest `Event.context.errorLine` for the real spot.** `context` also
110
+ gives `memoryPeakMb` — a small peak (e.g. 20 MB) rules out "giant record / OOM" and points at a
111
+ plain thrown warning instead.
94
112
 
95
113
  ### Per-record resume cursor — checkpoint after EACH record, not window-end (LIVE 2026-09-02)
96
114
 
@@ -151,6 +169,39 @@ back in the account **display** format (here `M/D/YYYY`, date-only). Proven: `SE
151
169
  but the same trap if either ever becomes one.
152
170
  - Deployed 2026-09-02 (worker + library re-deploy; both files had changed).
153
171
 
172
+ ### ⚠ The by-reference refetch dropped `lastModifiedDate` → ITEM_FULFILLMENTS + INVENTORY_ADJUSTMENTS froze (found 2026-09-03, NYCHH prod)
173
+
174
+ The per-record cursor introduced a trap specific to the two sections whose `sync*FromNetsuite()`
175
+ **refetches the record by reference**. `syncItemFulfillmentFromNetsuite(&$nsItemFulfillment, …)`
176
+ (`library/app/api/toga2.php:4535`) and `syncInventoryAdjustmentFromNetsuite(&$nsInventoryAdjustment,
177
+ …)` (`:5655`) each **replace the caller's object** with a fetch-by-id shim
178
+ (`fetchItemFulfillmentById` / `fetchInventoryAdjustmentById`). Those two fetch shims **did not set
179
+ `->lastModifiedDate`**. So the per-record cursor PUT right after the call
180
+ (`common_sync_togasupply.php:1235` fulfillments, `:1323` inventory adjustments) read
181
+ `$record->lastModifiedDate` on the **replaced** object → *Undefined property: stdClass::$lastModifiedDate*
182
+ → 1.0 `App_Error::handleError` → `exit` → the section died **before** its cursor PUT and
183
+ `finishModeIteration()`, freezing it at RUNNING with the watermark stuck years back. Confirmed on
184
+ NYCHH prod (`Logs.Issue` 649, `clientId 3`): fulfillments frozen ~2023-01-23, inventory adjustments
185
+ ~2022-12-29, both windows shrinking. `memoryPeakMb` was 20 — a fast thrown warning, **not** a
186
+ giant-record/OOM. **GroWrk was the reported alert** (ITEM_FULFILLMENTS re-throwing every 5 min
187
+ since the 2026-09-02 per-record cursor deploy). Because the fix is in **shared library code**, all
188
+ four active togasupply clients that hit these sections were affected: **GroWrk, NYCHH,
189
+ Prudential-EDI, S&P Global**.
190
+
191
+ - **ITEM_RECEIPTS was never affected**: `fetchItemReceiptById` already set `lastModifiedDate`, so its
192
+ cursor PUT succeeded (a real `…|<internalId>` value) while the other two carried the migration's
193
+ `…|0` and never moved.
194
+ - **The `|0` is NOT the bug.** It is the intended empty-window marker (see the per-record cursor
195
+ section). The frozen sections showed `|0` only because they never completed a record since the
196
+ 2026-09-02 rename.
197
+ - **Fix (library only, `php -l` clean):** set `$shim->lastModifiedDate` in **both**
198
+ `fetchItemFulfillmentById` (from the REST header's `lastModifiedDate`) and
199
+ `fetchInventoryAdjustmentById` (add `lastmodifieddate` to its SuiteQL SELECT, then set the shim).
200
+ No worker change, no SQL, fail-loud preserved. After deploy, the two sections resume and backfill
201
+ the gap on their own (idempotent) — rewinding the cursor is optional to speed catch-up.
202
+ - **Lesson:** any `sync*FromNetsuite()` that takes the record `&$byReference` and refetches must have
203
+ its fetch-by-id shim carry **every** field the caller reads afterward — `lastModifiedDate` first.
204
+
154
205
  ### ⚠ Per-record isolation is SECTION-SPECIFIC — and SALES_ORDERS has NONE (corrected 2026-08-17)
155
206
 
156
207
  Whether a bad record is "stepped over" or freezes the section depends entirely on whether **that
@@ -1124,6 +1175,22 @@ the code deploy).
1124
1175
 
1125
1176
  ## Change history
1126
1177
 
1178
+ - 2026-09-03 — **By-reference refetch dropped `lastModifiedDate` → ITEM_FULFILLMENTS + INVENTORY_ADJUSTMENTS
1179
+ froze (fixed, `library` only).** GroWrk's ITEM_FULFILLMENTS was re-throwing every 5 min since the
1180
+ 2026-09-02 per-record cursor deploy (`Undefined property: stdClass::$lastModifiedDate` at
1181
+ `common_sync_togasupply.php:1235`). Root cause: `syncItemFulfillmentFromNetsuite(&$rec)` and
1182
+ `syncInventoryAdjustmentFromNetsuite(&$rec)` **replace** the by-ref record with a fetch-by-id shim
1183
+ (`fetchItemFulfillmentById` / `fetchInventoryAdjustmentById` in `rest.php`), and neither shim set
1184
+ `->lastModifiedDate` — so the per-record cursor PUT that reads it after the call threw → `App_Error`
1185
+ `exit()` → section died before `finishModeIteration()` → frozen RUNNING, window ÷3. `fetchItemReceiptById`
1186
+ already set it, so ITEM_RECEIPTS was never hit; INVENTORY_ADJUSTMENTS was latent (no client had processed
1187
+ one since the rename). Shared code → all four active togasupply clients affected (GroWrk, NYCHH,
1188
+ Prudential-EDI, S&P Global); no data lost (the record write commits each run), self-heals after deploy.
1189
+ Fix: set `$shim->lastModifiedDate` in both shims (add `lastmodifieddate` to InventoryAdjustment's SuiteQL
1190
+ SELECT). `php -l` clean; no worker/SQL change. Lesson: any `sync*FromNetsuite()` that refetches its
1191
+ `&$byReference` record must have its fetch-by-id shim carry every field the caller reads afterward. Full
1192
+ detail in *How it works* above; shim-side entry on the
1193
+ [SuiteQL/REST shim doc](../../library/features/netsuite-suiteql-rest-shim.md). (jcardinal)
1127
1194
  - 2026-09-02 — **Per-record cursor was storing a date-ONLY, US-format value — FIXED + DEPLOYED (shared:
1128
1195
  `worker` + `library`).** The datetime half was `M/D/YYYY` with no time (e.g. `9/2/2026|6302323`), so the
1129
1196
  cursor sat at midnight and the same-second id tiebreak never matched — every run re-fetched the whole day.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "toga-ai",
3
- "version": "1.0.757",
3
+ "version": "1.0.759",
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",