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-
|
|
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-
|
|
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
|
|
92
|
-
never *tune the window size down*. (Corollary: a window that has grown back to
|
|
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