toga-ai 1.0.760 → 1.0.761

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.
@@ -202,6 +202,43 @@ Prudential-EDI, S&P Global**.
202
202
  - **Lesson:** any `sync*FromNetsuite()` that takes the record `&$byReference` and refetches must have
203
203
  its fetch-by-id shim carry **every** field the caller reads afterward — `lastModifiedDate` first.
204
204
 
205
+ ### ⚠ …but the by-reference shim wrote the cursor on the WRONG CLOCK → ITEM_RECEIPTS + ITEM_FULFILLMENTS re-matched forever (found + fixed 2026-09-03, NYCHH prod)
206
+
207
+ A follow-on to the by-reference fix directly above. Once the two shims carried a `lastModifiedDate`
208
+ again, the sections still could **not advance** — the value was in the **wrong timezone**, so
209
+ `lastmodifieddate > cursor` re-matched the **same record every run** (an infinite loop, no crash).
210
+
211
+ - **Root cause — two clocks.** The prior fix set `fetchItemFulfillmentById` / `fetchItemReceiptById`'s
212
+ `->lastModifiedDate` from the **REST record GET**, which returns **UTC** (e.g. `2024-08-06T14:45:00Z`).
213
+ `common_sync_togasupply.php` then `date()`-formats it in the script timezone **America/Chicago** →
214
+ `2024-08-06 09:45:00`, and stores that as the cursor. But the LIST fetch's `WHERE`
215
+ (`App_Api_Netsuite_Rest::listItem*`) compares `lastmodifieddate` via SuiteQL
216
+ `TO_CHAR(lastmodifieddate,'YYYY-MM-DD HH24:MI:SS')`, which is the **NetSuite account timezone =
217
+ Eastern** → `2024-08-06 10:45:16`. So the cursor was written **~1 hour behind** the clock the fetch
218
+ reads → `lastmodifieddate > cursor` was always still true for the record just processed → it was
219
+ re-fetched and re-processed forever and the watermark never moved.
220
+ - **Proven on NYCHH prod** (item receipt internalId `4953915`): REST GET `lastModifiedDate`
221
+ `2024-08-06T14:45:00Z` (UTC) · SuiteQL `2024-08-06 10:45:16` (Eastern) · stored cursor
222
+ `2024-08-06 09:45:00` (Chicago). ITEM_RECEIPTS was jammed on it; because that heavy receipt (nested
223
+ PO→SO reconcile) runs **before** fulfillments and starved the run, ITEM_FULFILLMENTS' window kept
224
+ collapsing to 6s and never grew to clear the empty gap to its first real record (2025-01-06; 1,294
225
+ NYCHH fulfillments waited after 2025-01-01).
226
+ - **INVENTORY_ADJUSTMENTS was NOT stuck** — `fetchInventoryAdjustmentById` already reads
227
+ `lastmodifieddate` from SuiteQL `TO_CHAR` (the right clock), so it advanced normally. **That is the
228
+ correct pattern**; the other two shims did not follow it.
229
+ - **Fix (library only, `php -l` clean, fail-loud preserved):** in `library/app/api/netsuite/rest.php`,
230
+ `fetchItemFulfillmentById` (~L2093) and `fetchItemReceiptById` (~L2254) now set
231
+ `$shim->lastModifiedDate` from a SuiteQL
232
+ `SELECT TO_CHAR(lastmodifieddate,'YYYY-MM-DD HH24:MI:SS') AS lastmodifieddate FROM transaction WHERE id = $id`
233
+ (Eastern) — matching `fetchInventoryAdjustmentById` — **instead of** the REST header's UTC
234
+ `$header->lastModifiedDate`. No shim reads the REST/UTC `lastModifiedDate` anymore. No worker change,
235
+ no SQL. After deploy the two sections resume and backfill on their own (idempotent).
236
+ - **Lesson:** the per-record cursor value MUST be written in the **same timezone the fetch `WHERE`
237
+ compares against** — SuiteQL account time (Eastern), never the REST record GET's UTC
238
+ `lastModifiedDate`. A cursor even an hour behind the fetch clock re-matches the same record forever.
239
+ This is the same SuiteQL-vs-REST clock trap as the date-only-format bug above — see the
240
+ [SuiteQL/REST shim doc](../../library/features/netsuite-suiteql-rest-shim.md).
241
+
205
242
  ### ⚠ Per-record isolation is SECTION-SPECIFIC — and SALES_ORDERS has NONE (corrected 2026-08-17)
206
243
 
207
244
  Whether a bad record is "stepped over" or freezes the section depends entirely on whether **that
@@ -1175,6 +1212,15 @@ the code deploy).
1175
1212
 
1176
1213
  ## Change history
1177
1214
 
1215
+ - 2026-09-03 — **By-reference shim wrote the cursor on the WRONG CLOCK → ITEM_RECEIPTS + ITEM_FULFILLMENTS
1216
+ re-matched the same record forever (fixed, `library` only).** Follow-on to the same-day by-reference
1217
+ fix below: the shims set `lastModifiedDate` from the REST GET (UTC), `date()`-formatted in
1218
+ America/Chicago, but the LIST `WHERE` compares SuiteQL `TO_CHAR(lastmodifieddate)` in the NetSuite
1219
+ account tz (Eastern), so the cursor sat ~1h behind the fetch clock and `lastmodifieddate > cursor`
1220
+ always re-matched — infinite loop, no crash. `fetchItemFulfillmentById`/`fetchItemReceiptById`
1221
+ (`rest.php` ~L2093/~L2254) now read `lastmodifieddate` from SuiteQL `TO_CHAR` (Eastern), matching
1222
+ `fetchInventoryAdjustmentById` (already correct). Proven on NYCHH prod (item receipt 4953915). No
1223
+ worker/SQL change; sections self-backfill after deploy. (jcardinal)
1178
1224
  - 2026-09-03 — **By-reference refetch dropped `lastModifiedDate` → ITEM_FULFILLMENTS + INVENTORY_ADJUSTMENTS
1179
1225
  froze (fixed, `library` only).** GroWrk's ITEM_FULFILLMENTS was re-throwing every 5 min since the
1180
1226
  2026-09-02 per-record cursor deploy (`Undefined property: stdClass::$lastModifiedDate` at
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "toga-ai",
3
- "version": "1.0.760",
3
+ "version": "1.0.761",
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",