toga-ai 1.0.757 → 1.0.758
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: 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