toga-ai 1.0.699 → 1.0.700

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.
@@ -14,6 +14,7 @@
14
14
  | [Error Capture in 1.0 (App_Error_Capture → shared 2.0 Logs DB)](features/error-capture-1-0.md) | The 1.0 side of the platform error-reporting pipeline (TRUE-78188). | library/app/error/capture.php, library/app/error.php, library/app/exception/business.php, library/app/api/toga2.php, library/app/cloud.php, worker/config.worker.ini, worker/crons/toga2/compass/workflow/1_transmit_compass_sales_orders_to_mits.php |
15
15
  | [HTTP 500 Error Monitor (App_SystemMonitor_500Error) — and why its \"Error Type\" is not a diagnosis](features/http-500-error-monitor.md) | `App_SystemMonitor_500Error` (`library/app/systemmonitor/500error.php`, title **"HTTP 500 Error Alert"**) is the 1.0 system monitor that watches **`Logs.Api` fo | library/app/systemmonitor/500error.php, worker/crons/infrastructure/system_monitors.php, api2/Controller/Index.php, _underscore/Error.php |
16
16
  | [1.0 MVC Page Pattern & New-App Skeleton](features/mvc-page-pattern-and-app-skeleton.md) | This is the **reusable recipe for standing up a new 1.0 (`App_`) application** and for adding pages to one — the folder-based MVC routing, the page lifecycle, t | library/app/framework.php, library/app/frameworkindex.php, library/app/mvc.php, library/app/database.php, library/app/model.php, library/app/config.php |
17
+ | [NetSuite File Cabinet Content Retrieval via RESTlet (fetchInvoiceFile)](features/netsuite-filecabinet-restlet.md) | How 1.0 pulls **File Cabinet binary content** (invoice PDFs) out of NetSuite over REST. | library/app/api/netsuite/rest.php, worker/crons/toga2/netsuite/netsuite_restlet_filecontent.js, worker/config.worker.ini |
17
18
  | [isFulfillable from NetSuite during Item Sync (Phase 1)](features/netsuite-item-isfulfillable-sync.md) | This is the **1.0 (Phase 1)** half of the `isFulfillable` feature: reading the NetSuite `isfulfillable` flag during item sync and stamping it onto the **Agilant | library/app/netsuite.php, library/app/api/toga2.php, worker/crons/toga2/netsuite/common_sync_togasupply.php, worker/crons/toga2/netsuite/backfill_isfulfillable_jul5.php |
18
19
  | [NetSuite SuiteQL/REST API Reference](features/netsuite-suiteql-api-reference.md) | General working reference for the Agilant NetSuite integration: how to authenticate, how SuiteQL behaves, and the confirmed schema of the tables/columns/codes w | library/app/api/netsuite/rest.php, library/ssl/netsuite_ec_key.pem, test/@dave/Junk Drawer/nsq.php |
19
20
  | [NetSuite SuiteQL/REST Shim — Field Semantics](features/netsuite-suiteql-rest-shim.md) | `App_Api_Netsuite_Rest` is the REST/SuiteQL replacement for the deprecated NetSuite SOAP toolkit. | library/app/api/netsuite/rest.php, library/app/netsuite.php, worker/crons/toga2/forecast2/common_import_sales_from_netsuite.php |
@@ -0,0 +1,122 @@
1
+ ---
2
+ title: NetSuite File Cabinet Content Retrieval via RESTlet (fetchInvoiceFile)
3
+ framework: "1.0"
4
+ repo: library
5
+ project: Library
6
+ client: shared
7
+ type: feature
8
+ status: active
9
+ updated: 2026-08-29
10
+ owners: [jcardinal]
11
+ files:
12
+ - library/app/api/netsuite/rest.php
13
+ - worker/crons/toga2/netsuite/netsuite_restlet_filecontent.js
14
+ - worker/config.worker.ini
15
+ related:
16
+ - netsuite-suiteql-api-reference.md
17
+ - netsuite-suiteql-rest-shim.md
18
+ - ../../worker/features/netsuite-togasupply-per-client-sync.md
19
+ ---
20
+
21
+ ## Summary
22
+
23
+ How 1.0 pulls **File Cabinet binary content** (invoice PDFs) out of NetSuite over REST. The
24
+ short version: **there is no token-only, code-only REST path to File Cabinet binary content in
25
+ this account.** The `/record/v1/file` Record Service is not enabled, SuiteQL exposes only file
26
+ *metadata* (no content column), and the `media.nl` UI URL is not fetchable with an OAuth token.
27
+ The only REST mechanism is a **SuiteScript 2.1 RESTlet** that loads the file server-side and
28
+ returns its bytes base64-encoded. `App_Api_Netsuite_Rest::fetchInvoiceFile()` uses exactly that.
29
+
30
+ This replaced the SOAP `App_NetSuite::getInvoiceFile` as part of completing the SOAP→REST
31
+ migration of the togasupply sync path (see the
32
+ [per-client-sync doc](../../worker/features/netsuite-togasupply-per-client-sync.md)).
33
+
34
+ ## Why there is no token-only REST path (verified live, 2026-08-29)
35
+
36
+ Three routes were probed against this account and all three are dead ends for binary content:
37
+
38
+ - **REST Record Service `GET /record/v1/file/{id}` → HTTP 404 `NONEXISTENT_ID`**, *"Record type
39
+ 'file' does not exist."* The `file` record type is simply not in this account's REST Record
40
+ Service. (The earlier `fetchInvoiceFile` design assumed this endpoint worked — it never did.)
41
+ - **SuiteQL `file` table exposes ONLY metadata** — `id, name, filetype, filesize, folder, url,
42
+ createddate, lastmodifieddate` and `is*` flags. There is **no binary/content column**. Good for
43
+ *finding* the file id by name; useless for its bytes.
44
+ - **The SuiteQL `url` is a `media.nl` UI link, not an API resource.** It is a
45
+ `/core/media/media.nl?...&c=<acct>&h=<hash>` link on the `<acct>.app.netsuite.com` **UI domain**.
46
+ Fetching it — anonymous OR with an OAuth Bearer — is rejected (redirects to `page_not_found`),
47
+ because invoice PDFs are **not** flagged "Available Without Login" and the OAuth token does not
48
+ authorize the UI media servlet.
49
+
50
+ Conclusion: to get File Cabinet bytes over REST you must run server-side script. That is the
51
+ RESTlet.
52
+
53
+ ## How it works
54
+
55
+ ### NetSuite side — the RESTlet
56
+
57
+ `worker/crons/toga2/netsuite/netsuite_restlet_filecontent.js` is a **SuiteScript 2.1 RESTlet**
58
+ that does `N/file.load(id).getContents()` and returns the content **base64-encoded**. Deployed in
59
+ NetSuite as script **`customscript2829`** / deployment **`customdeploy1`** (non-secret identifiers),
60
+ with the **RESTlets** OAuth scope enabled on the **"TOGA OAuth 2.0"** integration record.
61
+
62
+ ### Client side
63
+
64
+ - **`App_Api_Netsuite_Rest::callRestlet()`** — GETs
65
+ `<acct>.restlets.api.netsuite.com/app/site/hosting/restlet.nl?script=<id>&deploy=<id>` (note the
66
+ **`restlets.api.netsuite.com`** host, distinct from the SuiteQL/REST `suitetalk` host).
67
+ - **`fetchInvoiceFile()`** — SuiteQL `file`-table lookup by name to get the file **id** →
68
+ `callRestlet()` → `base64_decode()` → raw bytes. **Fail-loud preserved:** no file row → `null`;
69
+ a file found but unretrievable, or a RESTlet error → **throws** (never silently returns empty).
70
+ - **Config:** the script/deploy ids live in `worker/config.worker.ini` `[netsuite]` as
71
+ `restletScriptId` / `restletDeployId`.
72
+
73
+ ### Validated byte-identical to SOAP
74
+
75
+ RESTlet-retrieved PDFs were confirmed **byte-identical** (matching length + md5) to what SOAP
76
+ `getInvoiceFile` returned, across 3 Compass invoices (~265 KB each). Zero `fetchInvoiceFile` /
77
+ `INVALID_LOGIN` errors after deploy. This also unblocked Endeavor Health's whole pipeline — its
78
+ INVOICES section had been throwing on the broken file fetch and collateral-freezing
79
+ IR/IF/InvAdj.
80
+
81
+ ## Gotchas / known issues
82
+
83
+ - **⚠ NetSuite issues ONE active OAuth 2.0 token per client credential — a second token with a
84
+ DIFFERENT scope INVALIDATES the first.** Fetching a separately-scoped `restlets` token caused the
85
+ very next SuiteQL call to fail with **HTTP 400 `INVALID_LOGIN`**. **Fix: request ONE
86
+ combined-scope token** — a single `OAUTH_SCOPE` of `rest_webservices restlets` — never two
87
+ separate tokens. This requires the **RESTlets scope enabled on the integration record**. The
88
+ initially-tried "isolated separate restlets token" design is what caused the `INVALID_LOGIN` and
89
+ was removed. (This is a general OAuth fact: any future added scope must be folded into the single
90
+ combined-scope request, not fetched as a second token.)
91
+ - **⚠ A RESTlet whose function returns an object requires the REQUEST to carry
92
+ `Content-Type: application/json`** — otherwise NetSuite rejects it with HTTP 400
93
+ `INVALID_RETURN_DATA_FORMAT` (*"Invalid data format. You should return text."*). Set the request
94
+ content type even though `callRestlet` is a GET with no meaningful body.
95
+ - **PHP 7.2 prod constraint** applies to `rest.php` as everywhere — lint with
96
+ `C:\xampp7\php\php.exe -l` before deploying (a single 7.4+ token parse-errors the whole class;
97
+ see the [api reference](netsuite-suiteql-api-reference.md) blast-radius gotcha).
98
+
99
+ ## Change history
100
+
101
+ - 2026-08-29 — Created. Migrated invoice-PDF retrieval off SOAP `getInvoiceFile` to a SuiteScript
102
+ 2.1 RESTlet (`customscript2829`/`customdeploy1`) after verifying live that **no token-only REST
103
+ path to File Cabinet binary exists** in this account: `/record/v1/file/{id}` 404s
104
+ (`NONEXISTENT_ID`, file record not in the REST Record Service), SuiteQL `file` is metadata-only
105
+ (no content column), and the `media.nl` UI url is not OAuth-fetchable. Added
106
+ `App_Api_Netsuite_Rest::callRestlet()` + rewrote `fetchInvoiceFile` (SuiteQL id lookup →
107
+ callRestlet → base64_decode, fail-loud). Recorded two RESTlet/OAuth gotchas: one active token per
108
+ client credential so scopes must be combined (`rest_webservices restlets` in a single request,
109
+ else the next SuiteQL call 400s `INVALID_LOGIN`), and an object-returning RESTlet needs request
110
+ `Content-Type: application/json` (else 400 `INVALID_RETURN_DATA_FORMAT`). RESTlet PDFs verified
111
+ byte-identical to SOAP across 3 Compass invoices; unblocked Endeavor Health's pipeline. (jcardinal)
112
+
113
+ ## Related docs
114
+
115
+ - [`netsuite-suiteql-api-reference.md`](netsuite-suiteql-api-reference.md) — auth (OAuth2/JWT),
116
+ SuiteQL mechanics, the `fetch*ById()` helper family that `fetchInvoiceFile` belongs to.
117
+ - [`netsuite-suiteql-rest-shim.md`](netsuite-suiteql-rest-shim.md) — REST-shim field/shape
118
+ semantics.
119
+ - [per-client-sync](../../worker/features/netsuite-togasupply-per-client-sync.md) — the togasupply
120
+ engine whose INVOICES section consumes `fetchInvoiceFile`.
121
+ </content>
122
+ </invoke>
@@ -14,6 +14,7 @@ files:
14
14
  - test/@dave/Junk Drawer/nsq.php
15
15
  related:
16
16
  - netsuite-suiteql-rest-shim.md
17
+ - netsuite-filecabinet-restlet.md
17
18
  - ../architecture.md
18
19
  - ../../worker/features/forecast2-netsuite-reconciliation.md
19
20
  - ../../../clients/nychh/features/netsuite-inventory-adjustment-fulfillment-link.md
@@ -42,6 +43,12 @@ This doc is the **broad map** (mechanics, schemas, codes, performance). For the
42
43
  a placeholder `CERTIFICATE_ID_HERE` that `initialize()` explicitly rejects.
43
44
  - `readConfig()` supports two framework contexts: 1.0 via `App_Registry::get('config')['netsuite']`,
44
45
  and 2.0 via `_Config::netsuite()`.
46
+ - **⚠ ONE active OAuth token per client credential — combine scopes, never fetch a second token.**
47
+ NetSuite invalidates the first token when a second is requested with a *different* scope: after a
48
+ separately-scoped `restlets` token was fetched, the next SuiteQL call 400'd `INVALID_LOGIN`. Request
49
+ a single combined `OAUTH_SCOPE` (currently `rest_webservices restlets`, the latter enabled on the
50
+ "TOGA OAuth 2.0" integration for `fetchInvoiceFile`). Any future scope must be folded into this one
51
+ request. See [File Cabinet content via RESTlet](netsuite-filecabinet-restlet.md).
45
52
 
46
53
  ## Bootstrapping the client from a standalone script
47
54
 
@@ -365,8 +372,13 @@ exclusively — zero `App_NetSuite::` SOAP network calls remain** in `common_syn
365
372
  - **`fetchItemFlagsByIds`** — batch item serialized/fulfillable flags; replaces
366
373
  `getItemSerializedAndFulfillable`.
367
374
  - **`fetchInventoryAdjustmentById`** — replaces the `diagnose_hh` script's SOAP `getObject`.
368
- - **`fetchInvoiceFile`** — SuiteQL `file`-table lookup + REST `GET /record/v1/file/{id}` content,
369
- `base64_decode` → raw bytes. Replaces SOAP `getInvoiceFile`.
375
+ - **`fetchInvoiceFile`** — SuiteQL `file`-table lookup for the id → **RESTlet** call
376
+ (`callRestlet()`) → `base64_decode` → raw bytes. Replaces SOAP `getInvoiceFile`. **NOT** a REST
377
+ record GET — `GET /record/v1/file/{id}` 404s (`NONEXISTENT_ID`; the `file` record is not in this
378
+ account's REST Record Service), SuiteQL `file` is metadata-only, and the `media.nl` url is not
379
+ OAuth-fetchable, so a SuiteScript RESTlet is the only REST path to File Cabinet binary. Full
380
+ mechanics + the two OAuth/RESTlet gotchas: [File Cabinet content via
381
+ RESTlet](netsuite-filecabinet-restlet.md).
370
382
 
371
383
  `listCustomers()` also gained a **`companyName`** field on its shim (the `netsuite_customers.php` cron
372
384
  moved from `listAllCustomers` → `listCustomers`). REST-shim *shape* traps that bite these helpers (an
@@ -442,6 +454,14 @@ numbers. Budget hours for multi-year runs and launch them under `nohup`/`tmux`.
442
454
 
443
455
  ## Change history
444
456
 
457
+ - 2026-08-29 — Corrected `fetchInvoiceFile` (deploy/verify session): it is **RESTlet-based**, not a
458
+ REST record GET — `GET /record/v1/file/{id}` 404s (`file` record not in this account's REST Record
459
+ Service), SuiteQL `file` is metadata-only, `media.nl` isn't OAuth-fetchable, so a SuiteScript 2.1
460
+ RESTlet is the only REST path to File Cabinet binary; new
461
+ [File Cabinet content via RESTlet](netsuite-filecabinet-restlet.md) doc holds the mechanics. Added the
462
+ **one-active-token-per-credential** auth rule to Authentication (combine scopes into a single
463
+ `rest_webservices restlets` request; a separate second token 400s the next SuiteQL call
464
+ `INVALID_LOGIN`). (jcardinal)
445
465
  - 2026-08-29 — Documented the **`fetch*ById()` single-record helper family** (`fetchItemReceiptById`,
446
466
  `fetchPurchaseOrderById`, `fetchSalesOrderByIdFull`, `fetchItemFlagsByIds`,
447
467
  `fetchInventoryAdjustmentById`, `fetchInvoiceFile`) that replaced the SOAP `App_NetSuite::get*` calls,
@@ -122,6 +122,20 @@ Forecast2 tables already store.
122
122
  skew — the field just reads as its default — so verify the exact scriptId string on both the write and
123
123
  the read side. See [NYCHH TransferOrders import](../../../clients/nychh/features/netsuite-transfer-order-import.md).
124
124
 
125
+ - **`fetchPurchaseOrderById()` omitted `entity` from its SELECT and never set `$shim->entity` — so
126
+ every PO shim had a null vendor and the fail-loud guard fired every run (2026-08-29).** Unlike its
127
+ sibling `listPurchaseOrders` (which DOES select `entity` + `BUILTIN.DF(entity)` and populates the
128
+ shim's vendor), `fetchPurchaseOrderById`'s SuiteQL header query left `entity` out and the shim never
129
+ built `$shim->entity`. The item-receipt path (`syncItemReceiptFromNetsuite` →
130
+ `syncPurchaseOrderFromNetsuite`) therefore always saw a null entity, and the fail-loud guard
131
+ `if (empty($nsPurchaseOrder->entity)) throw 'has no vendor/entity'` threw on **every** run — freezing
132
+ **Canon** and **Endeavor Health** ITEM_RECEIPTS at `1-RUNNING` (window collapsed ÷3 to 1s), watermarks
133
+ frozen ~8 and ~5 days. **Fix:** added `entity, BUILTIN.DF(entity) AS entity_name` to the header SELECT
134
+ and populated `$shim->entity` as `{internalId, name}` (matching the other shims); a genuinely
135
+ vendorless PO still throws. Same class as the `tl.location` regression below and the missing-entity
136
+ guard was correct — the SELECT was the defect. **When adding a shim, every field the guard/consumer
137
+ reads must actually be in the SELECT.**
138
+
125
139
  - **A shim must produce the SOAP object's SHAPE, not just its scalar fields — an absent nested list
126
140
  crashes 1.0 (2026-08-29).** `fetchCustomerById()`'s `addressbookList` was where the AIG lookup phase
127
141
  crashed: a REST customer with no/odd address made the shim omit the nested `addressbook` structure the
@@ -215,6 +229,14 @@ None — uniform across clients (NetSuite is a single shared account).
215
229
 
216
230
  ## Change history
217
231
 
232
+ - 2026-08-29 — Recorded `fetchPurchaseOrderById()`'s **missing `entity` SELECT** (deploy/verify
233
+ session): its header query omitted `entity`/`BUILTIN.DF(entity)` and the shim never set
234
+ `$shim->entity` (unlike `listPurchaseOrders`), so the PO vendor was always null and the
235
+ `syncPurchaseOrderFromNetsuite` fail-loud missing-entity guard threw every run — the actual cause of
236
+ the **Canon + Endeavor Health ITEM_RECEIPTS** freeze at `1-RUNNING` (watermarks ~8/~5 days). Fixed
237
+ additively (add `entity, BUILTIN.DF(entity) AS entity_name`; populate `$shim->entity` as
238
+ `{internalId,name}`); same "a field the shim consumes must be in the SELECT" class as `tl.location`.
239
+ Deployed ~12:52; both sections self-healed RUNNING→IDLE and resumed advancing. (jcardinal)
218
240
  - 2026-08-29 — Recorded two shim-shape facts from root-causing the 5 stuck togasupply integrations
219
241
  (which were **shim-shape mismatches, not dead SOAP** — see the
220
242
  [per-client-sync doc](../../worker/features/netsuite-togasupply-per-client-sync.md)): a
@@ -583,8 +583,10 @@ Parameters are stored **per client DB** but accessed **through the TOGa2 API**,
583
583
  `App_NetSuite::` (SOAP) network calls remain** anywhere in the `common_sync_togasupply.php` engine
584
584
  or the `*FromNetsuite()` methods it calls — so the whole tier-wide-freeze class above is now driven
585
585
  by REST-shim shape, not SOAP availability. New REST completeness pieces built in `library`:
586
- `fetchInvoiceFile` (SuiteQL `file` lookup + REST `GET /record/v1/file/{id}` content, `base64_decode`
587
- → raw bytes; replaces `getInvoiceFile`), `getItemSerializedAndFulfillable` → `fetchItemFlagsByIds`,
586
+ `fetchInvoiceFile` (SuiteQL `file` id lookup + **RESTlet** content fetch → `base64_decode` → raw
587
+ bytes; replaces `getInvoiceFile` — the `/record/v1/file/{id}` approach 404s, so a SuiteScript RESTlet
588
+ is the only REST path — see [File Cabinet content via RESTlet](../../library/features/netsuite-filecabinet-restlet.md)),
589
+ `getItemSerializedAndFulfillable` → `fetchItemFlagsByIds`,
588
590
  the `diagnose_hh` script's `getObject` → `fetchInventoryAdjustmentById`, and
589
591
  `worker/crons/sync/netsuite/netsuite_customers.php` `listAllCustomers` → `listCustomers` (with
590
592
  `companyName` added to the `listCustomers` shim).
@@ -601,8 +603,59 @@ Parameters are stored **per client DB** but accessed **through the TOGa2 API**,
601
603
  Note `SalesOrderItems.lineNumber` is a **`smallint`**, so the parked base must stay within smallint
602
604
  range for the client's line counts (headroom 1000 is safe at observed volumes).
603
605
 
606
+ - **⚠ Canon + Endeavor Health ITEM_RECEIPTS froze because `fetchPurchaseOrderById` never populated the
607
+ PO vendor (deployed/verified 2026-08-29).** The PO shim's `->entity` was always null (its SuiteQL
608
+ SELECT omitted `entity`, unlike `listPurchaseOrders`), so the fail-loud missing-entity guard in
609
+ `syncPurchaseOrderFromNetsuite` (reached via `syncItemReceiptFromNetsuite`) threw **every** run —
610
+ freezing Canon (clientId 12) and Endeavor Health (clientId 11) ITEM_RECEIPTS at `1-RUNNING`,
611
+ watermarks frozen ~8/~5 days. Fix is in the shim
612
+ ([rest-shim doc](../../library/features/netsuite-suiteql-rest-shim.md)): add
613
+ `entity, BUILTIN.DF(entity) AS entity_name` and build `$shim->entity`. `library`-only, additive; a
614
+ genuinely vendorless PO still throws. Deployed ~12:52 — both sections self-healed RUNNING→IDLE and
615
+ resumed advancing. Same class as the missing `tl.location` SELECT.
616
+
617
+ - **⚠ Diagnostic refinement — the stuck signal is specifically `1-RUNNING`, NOT any `*-RUNNING`.** A
618
+ section frozen because it throws every run collapses its window ÷3 all the way to **1 second**, so its
619
+ `NETSUITE_EXECUTION_MODE_*` reads exactly **`1-RUNNING`**. A **large-window** `<big>-RUNNING` (e.g.
620
+ `864000-RUNNING`) is usually just a healthy section caught **mid-execution** at the query instant —
621
+ not stuck. And a large-window value with a *years-old* watermark can be a legitimate long **backfill**
622
+ (e.g. NYCHH backfilling from 2020/2021), also not a bug. Read the window magnitude, not just the
623
+ RUNNING state. **Sweep method:** read every client's `Client_<X>.Parameters` for
624
+ `NETSUITE_EXECUTION_MODE_*` / `NETSUITE_LAST_SYNC_DATETIME_*`; a cross-schema `UNION` over many client
625
+ DBs needs `CONVERT(<col> USING utf8mb4)` on the value column or MySQL raises *"Illegal mix of
626
+ collations."* Then map each stuck client/section to an exact crash **file:line** via prod
627
+ `Logs.Event` — note **`Logs.Issue` has no `clientId` column but `Logs.Event` does**, so filter
628
+ `Logs.Event` by `clientId`.
629
+
630
+ - **OPEN, NOT FIXED (observed 2026-08-29, low priority — separate tickets):**
631
+ - **AIG (clientId 32, held account)** crashes in the **LOOKUPS** phase at
632
+ `common_sync_togasupply.php:463` with `Undefined property: stdClass::$companyName` — a
633
+ customer-shim shape gap that **survived** the earlier `fetchCustomerById` `addressbookList` fix.
634
+ (AIG remains a held/non-supply account — see the AIG gotcha above.)
635
+ - **Adyen (clientId 48)** crashes at `common_sync_togasupply.php:99` *"Invalid API Response"* from
636
+ `App_Api_Toga2::send()` at **setup** — it is **stuck-IDLE** (crashes before any section writes
637
+ RUNNING, so the `1-RUNNING` sweep misses it — same class as a LOOKUPS/setup abort). Likely an
638
+ **improperly onboarded** client (cf. AIG "listed ≠ functioning"). No knowledge-base client profile
639
+ exists for Adyen; no supply work has been done for it — confirm it's a real supply client before
640
+ onboarding effort.
641
+ Both are pre-existing and unrelated to this session's fixes.
642
+
604
643
  ## Change history
605
644
 
645
+ - 2026-08-29 — **Deploy/verify/stabilize session for the SOAP→REST conversion.** Root-caused the
646
+ **Canon + Endeavor Health ITEM_RECEIPTS** freeze: `fetchPurchaseOrderById` never populated the PO
647
+ vendor (`entity` missing from its SuiteQL SELECT), so `syncPurchaseOrderFromNetsuite`'s fail-loud
648
+ missing-entity guard threw every run — fixed in the shim (add `entity`/`BUILTIN.DF(entity)`, build
649
+ `$shim->entity`); deployed ~12:52, both sections self-healed. Migrated **invoice-PDF retrieval to a
650
+ RESTlet** (`/record/v1/file/{id}` 404s — no token-only REST path to File Cabinet binary exists), which
651
+ unblocked Endeavor's whole pipeline; new [File Cabinet content via
652
+ RESTlet](../../library/features/netsuite-filecabinet-restlet.md) doc. Recorded the **`1-RUNNING`
653
+ (window collapsed to 1s) = stuck** vs large-window `*-RUNNING` (healthy/mid-run or legit backfill)
654
+ diagnostic, the cross-schema `Parameters` sweep needing `CONVERT(... USING utf8mb4)`, and that
655
+ `Logs.Event` (not `Logs.Issue`) carries `clientId`. Logged two OPEN, unfixed items: **AIG** (clientId
656
+ 32) LOOKUPS crash at `:463` (`Undefined property $companyName`, customer-shim gap that survived the
657
+ addressbook fix) and **Adyen** (clientId 48) setup crash at `:99` (stuck-IDLE, likely improperly
658
+ onboarded). (jcardinal)
606
659
  - 2026-08-29 — **Root-caused the 5 stuck togasupply integrations as REST-shim SHAPE mismatches, not
607
660
  dead SOAP, and fixed them.** SOAP was up; the earlier SOAP→REST conversion left shims returning
608
661
  null/absent where SOAP had values, and a 1.0 unguarded read of that null → PHP warning →
@@ -4,7 +4,7 @@ _Auto-generated by `knowledge.js index`. Do not hand-edit._
4
4
 
5
5
  ## 1.0 framework
6
6
 
7
- - **library** (Library) _(framework core)_ — 21 doc(s) → [1.0/apps/library/INDEX.md](1.0/apps/library/INDEX.md)
7
+ - **library** (Library) _(framework core)_ — 22 doc(s) → [1.0/apps/library/INDEX.md](1.0/apps/library/INDEX.md)
8
8
  - **worker** (Worker) — 31 doc(s) → [1.0/apps/worker/INDEX.md](1.0/apps/worker/INDEX.md)
9
9
  - **dbchanges** (Database Changes) _(framework core)_ — 1 doc(s) → [1.0/apps/dbchanges/INDEX.md](1.0/apps/dbchanges/INDEX.md)
10
10
  - **worker1.5** (Worker 1.5) — 0 doc(s) → [1.0/apps/worker1.5/INDEX.md](1.0/apps/worker1.5/INDEX.md)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "toga-ai",
3
- "version": "1.0.699",
3
+ "version": "1.0.700",
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",