toga-ai 1.0.700 → 1.0.701

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.
@@ -135,6 +135,26 @@ Forecast2 tables already store.
135
135
  vendorless PO still throws. Same class as the `tl.location` regression below and the missing-entity
136
136
  guard was correct — the SELECT was the defect. **When adding a shim, every field the guard/consumer
137
137
  reads must actually be in the SELECT.**
138
+ - **The entity fix was only the first of a chain — the real fix completed the shim to MATCH
139
+ `listPurchaseOrders`' FULL shape (deploy/verify session, 2026-08-29).** `syncPurchaseOrderFromNetsuite`
140
+ (reached via the item-receipt path) reads a whole set of PO properties **unguarded**, so each one the
141
+ `fetchPurchaseOrderById` shim omitted threw a 1.0 warning → `App_Error` `exit()` → froze the section —
142
+ an "entity → createdFrom → shippingAddress → customForm → tranId → tranDate → line/description/rate"
143
+ whack-a-mole, one missing field surfacing the next. Fixed by making the per-id shim reproduce the
144
+ sibling **list** shape exactly: added `entity` + `trandate` to the header SuiteQL, `tl.rate` + `tl.memo`
145
+ to the line query, and populated `createdFrom` / `shippingAddress` / `customForm` from the per-id REST
146
+ GET. **Lesson (sharper than "a field the consumer reads must be in the SELECT"): a REST shim consumed
147
+ by a SHARED sync method must reproduce the FULL sibling-`list*` shape — every header field, every line
148
+ field, every nested object — not just the fields the one path you are testing happens to read.** This
149
+ cleared Canon(12)/Endeavor(11) ITEM_RECEIPTS to IDLE.
150
+
151
+ - **`buildSalesOrderShims()` / `fetchSalesOrderByIdFull()` SO-item shims now carry `createdPo` + `poRate`
152
+ (2026-08-29).** `syncPurchaseOrderFromNetsuite`'s SO-reconciliation loop reads
153
+ `$nsSalesOrderItem->createdPo` ({internalId}|null) and `$nsSalesOrderItem->poRate` (float|null)
154
+ **unguarded** — an `Undefined property $createdPo` → `exit()`. This loop was **newly reached** by the
155
+ `createdFrom` completion above, which is exactly how the next-missing-field surfaced. Both are now read
156
+ defensively from the per-id REST `salesOrder` sublist already fetched, so the SO-item shim matches what
157
+ the loop consumes. Same "reproduce the full sibling shape" class as the PO-shim completion.
138
158
 
139
159
  - **A shim must produce the SOAP object's SHAPE, not just its scalar fields — an absent nested list
140
160
  crashes 1.0 (2026-08-29).** `fetchCustomerById()`'s `addressbookList` was where the AIG lookup phase
@@ -229,6 +249,17 @@ None — uniform across clients (NetSuite is a single shared account).
229
249
 
230
250
  ## Change history
231
251
 
252
+ - 2026-08-29 — **Completed `fetchPurchaseOrderById` to the full `listPurchaseOrders` shape** and added
253
+ the SO-item `createdPo`/`poRate` shim fields (same deploy/verify session, after the entity fix). The
254
+ entity SELECT was only the first missing field: `syncPurchaseOrderFromNetsuite` reads `createdFrom`,
255
+ `shippingAddress`, `customForm`, `tranId`, `tranDate`, and line `rate`/`description` unguarded, so each
256
+ omission threw in turn (whack-a-mole). Fixed by matching the sibling **list** shape exactly (header:
257
+ `entity`+`trandate`; line: `tl.rate`+`tl.memo`; populated `createdFrom`/`shippingAddress`/`customForm`
258
+ from the per-id REST GET). Completing `createdFrom` newly reached the SO-reconciliation loop, exposing
259
+ the next unguarded reads `$nsSalesOrderItem->createdPo`/`poRate` — now carried on the
260
+ `buildSalesOrderShims`/`fetchSalesOrderByIdFull` SO-item shims (read from the per-id REST `salesOrder`
261
+ sublist). Lesson: a shim consumed by a shared sync method must reproduce the FULL sibling-list shape,
262
+ not the fields the tested path happens to read. (jcardinal)
232
263
  - 2026-08-29 — Recorded `fetchPurchaseOrderById()`'s **missing `entity` SELECT** (deploy/verify
233
264
  session): its header query omitted `entity`/`BUILTIN.DF(entity)` and the shim never set
234
265
  `$shim->entity` (unlike `listPurchaseOrders`), so the PO vendor was always null and the
@@ -640,8 +640,54 @@ Parameters are stored **per client DB** but accessed **through the TOGa2 API**,
640
640
  onboarding effort.
641
641
  Both are pre-existing and unrelated to this session's fixes.
642
642
 
643
+ - **⚠ A serialized unit's OWN location can differ from its receipt line's — auto-create it before the
644
+ `/units` write or api2 400s EV-12 (fixed 2026-08-29, `library/app/api/toga2.php`
645
+ `syncItemReceiptFromNetsuite`).** The unit carries its own `custitemnumber_location` (referenced on the
646
+ `/units` write by `c_netsuiteInternalLocationId`); when that location differs from the receipt line's
647
+ and isn't provisioned in the client DB, api2 rejects the unit write **EV-12**. The fix now **POSTs
648
+ `/locations` for the unit's own location before the write**, mirroring the existing item-line location
649
+ create one level up. Shared/general fix (any client running serialized item receipts), surfaced by
650
+ **GroWrk** location **1569**. Deployed.
651
+
652
+ - **⚠ The exact api2 rejection detail lives in `Logs_<Client>.Api`, not `Logs.Event` (which TRUNCATES
653
+ it).** When a section throws on an api2 non-success, `Logs.Event` records the error envelope but
654
+ **truncates** the useful detail (the `EV-9`/`EV-12` field/`searchCriteria` specifics). The full
655
+ request/response is in the **per-client** `Logs_<Client>.Api` table — **join on the `transactionId`
656
+ carried in the error envelope**; it has `method`, `route`, `responseCode`, `requestPayload`, and
657
+ `responsePayload`. So the diagnosis chain is: `Client_<X>.Parameters` (localize the stuck section) →
658
+ `Logs.Event` filtered by `clientId` (crash file:line + `transactionId`) → `Logs_<Client>.Api` on that
659
+ `transactionId` (the untruncated api2 rejection). (Distinct from the fatal-before-logging case where the
660
+ api2 request dies before the middleware and writes **no** `Logs_<Client>.Api` row — then read
661
+ `Logs.Issue` instead; see the 2026-08-18 poison-order diagnostic trap above.)
662
+
663
+ - **⚠ The shared `syncTransferOrderFromNetsuite` item-sync is NON-IDEMPOTENT — it creates DUPLICATE
664
+ `TransferOrderItems` every run (found 2026-08-29, NOT fixed; affects stocking-SO TOs too).** The shared
665
+ item loop does not reconcile against existing rows, so every sync appends the TO's items again: an
666
+ attempted real-TrnfrOrd import reached **22 duplicate items all `lineNumber` 1** on TO #15, and a
667
+ **pre-existing stocking-SO** transfer order (`Client_Nychh.TransferOrders` id **1** / number **62541**)
668
+ already carries **13 duplicates** — proving the bug is in the **shared** loop, not the real-TO path.
669
+ Because of this (plus an api2 `transferOrderItem`-reference gap and a receipt-line↔TO-line mapping
670
+ mismatch), the **real NetSuite Transfer Order (TrnfrOrd) item-receipt feature is INTENTIONALLY SKIPPED**
671
+ — a documented skip in `common_sync_togasupply.php`, exactly like the existing Return-Authorization
672
+ (`RtnAuth`) receipt skip — pending a proper rebuild. Full state, the inactive scaffolding left staged,
673
+ and the rebuild spec are on the
674
+ [NYCHH NetSuite → TransferOrders import](../../../clients/nychh/features/netsuite-transfer-order-import.md)
675
+ doc. **Do not re-enable real-TO receipts until the shared item loop is made idempotent (which also
676
+ fixes the stocking-SO dups and cleans the pre-existing TO 1).**
677
+
643
678
  ## Change history
644
679
 
680
+ - 2026-08-29 — **Stabilize session, additional durable findings.** (a) `syncItemReceiptFromNetsuite`
681
+ now **auto-creates a serialized unit's OWN location** (`custitemnumber_location` → POST `/locations`)
682
+ before the `/units` write, mirroring the item-line location create — a general fix surfaced by GroWrk
683
+ location 1569 (unit location ≠ receipt-line location → EV-12). (b) Recorded the diagnostic that
684
+ `Logs.Event` **truncates** the api2 rejection detail — the untruncated `method`/`route`/`responseCode`/
685
+ `requestPayload`/`responsePayload` is in the per-client `Logs_<Client>.Api` table, joined on the
686
+ envelope's `transactionId`. (c) Recorded that the shared `syncTransferOrderFromNetsuite` item-sync is
687
+ **non-idempotent** (duplicate `TransferOrderItems` every run — TO #15 reached 22 dups, pre-existing
688
+ stocking-SO TO 1/62541 has 13), so it affects stocking-SO TOs too; the **real-TrnfrOrd item-receipt
689
+ feature was intentionally SKIPPED** in `common_sync_togasupply.php` (like the RtnAuth skip) pending a
690
+ rebuild — rebuild spec on the NYCHH TransferOrders-import doc. (jcardinal)
645
691
  - 2026-08-29 — **Deploy/verify/stabilize session for the SOAP→REST conversion.** Root-caused the
646
692
  **Canon + Endeavor Health ITEM_RECEIPTS** freeze: `fetchPurchaseOrderById` never populated the PO
647
693
  vendor (`entity` missing from its SuiteQL SELECT), so `syncPurchaseOrderFromNetsuite`'s fail-loud
@@ -4,7 +4,7 @@
4
4
  |-----|---------|-------|
5
5
  | [Proposed — git-sourced base+overlay JSON authoring for the Surface layer](architecture/surface-authoring-proposal.md) | A **proposal / handoff recommendation** (not implemented) that the Surface layer's *authoring* model move off hand-authored SQL against the `SurfaceOverrides` E | _underscore/Model/Core/Surface.php, _underscore/Model/Client/SurfaceOverride.php |
6
6
  | [_underscore Framework Architecture](architecture.md) | `_underscore` is the shared PHP backend framework for **all 2.0 applications**. | _underscore/_underscore.php, _underscore/Loader.php, _underscore/Framework.php, _underscore/Model.php, _underscore/Database.php, _underscore/Query.php, _underscore/Route.php, _underscore/Component.php |
7
- | [ACL Permission Chain (Record & Field Authorization)](features/acl-permission-chain.md) | Authorization in the 2.0 API is **metadata-driven**: whether a role may Create/Read/Update/Delete a record is decided by rows across **four linked tables**, not | api2/Component/Api/V2/V2.php, dbchanges2/Core/2026-08-27a - TransferOrderUuidSearchableIdentifier.sql, _underscore/Model/Core/Page.php, _underscore/Model/Core/Surface.php, _underscore/Model/Client/TrackingNumber.php, dbchanges2/Client/2026-06-03- BLANK_CLIENT_DATABASE.sql, dbchanges2/Client/2026-06-23b - ItemTranslationsAcl.sql, dbchanges2/Client/2026-07-02c - TrackingNumberNeedsReturnLabelFieldPermission.sql, dbchanges2/Client/2026-07-22c - TrackingNumberMeasureIdsFieldPermission.sql, dbchanges2/Client_Quad/2026-08-24 - Quad Multi Currency Item Pricing.sql |
7
+ | [ACL Permission Chain (Record & Field Authorization)](features/acl-permission-chain.md) | Authorization in the 2.0 API is **metadata-driven**: whether a role may Create/Read/Update/Delete a record is decided by rows across **four linked tables**, not | api2/Component/Api/V2/V2.php, dbchanges2/Core/2026-08-27a - TransferOrderUuidSearchableIdentifier.sql, _underscore/Model/Core/Page.php, _underscore/Model/Core/Surface.php, _underscore/Model/Client/TrackingNumber.php, dbchanges2/Client/2026-06-03- BLANK_CLIENT_DATABASE.sql, dbchanges2/Client/2026-06-23b - ItemTranslationsAcl.sql, dbchanges2/Client/2026-07-02c - TrackingNumberNeedsReturnLabelFieldPermission.sql, dbchanges2/Client/2026-07-22c - TrackingNumberMeasureIdsFieldPermission.sql, dbchanges2/Client_Quad/2026-08-24 - Quad Multi Currency Item Pricing.sql, dbchanges2/Client/2026-08-29 - ItemReceiptTransferOrderIdFieldPermission.sql, dbchanges2/Client/2026-08-29b - ItemReceiptItemTransferOrderItemIdFieldPermission.sql |
8
8
  | [Address Uniqueness Normalization (unit identifier + 5-digit ZIP comparison)](features/address-uniqueness-normalization.md) | When a business rule says *"only one X per physical address"*, comparing address rows field-for-field does **not** work: the same dwelling is spelled many diffe | _underscore/Model/Rate/Entitlement.php |
9
9
  | [Address Validation (carrier waterfall + validateAddress scripted endpoint)](features/address-validation.md) | `_Model_Client_Address::validateAddress` verifies a US address against a **carrier waterfall (USPS → FedEx → UPS)** and returns a single canonical, carrier-norm | _underscore/Model/Client/Address.php, _underscore/Component/Library/Carriers/Usps/Usps.php |
10
10
  | [_ApiRequest — JSON encode/decode & api-logging behavior](features/apirequest-json-content-type.md) | `_ApiRequest` is the 2.0 outbound HTTP client. | _underscore/ApiRequest.php |
@@ -19,6 +19,8 @@ files:
19
19
  - dbchanges2/Client/2026-07-02c - TrackingNumberNeedsReturnLabelFieldPermission.sql
20
20
  - dbchanges2/Client/2026-07-22c - TrackingNumberMeasureIdsFieldPermission.sql
21
21
  - dbchanges2/Client_Quad/2026-08-24 - Quad Multi Currency Item Pricing.sql
22
+ - dbchanges2/Client/2026-08-29 - ItemReceiptTransferOrderIdFieldPermission.sql
23
+ - dbchanges2/Client/2026-08-29b - ItemReceiptItemTransferOrderItemIdFieldPermission.sql
22
24
  ---
23
25
 
24
26
  ## Summary
@@ -490,6 +492,15 @@ hardcoded `Core.RecordFields` id literals instead of a subselect.
490
492
  and every repo is on the **same branch** so the generated model matches the DB.
491
493
 
492
494
  ## Change history
495
+ - **2026-08-29** — Two more worked examples of the **standard-field write grant via hardcoded
496
+ `Core.RecordFields` id literal** (the cross-cluster caveat — a `Client_*`→`Core` JOIN is refused on the
497
+ prod client cluster), each cloning a working sibling: `ItemReceipts.transferOrderId` (Core RF **2153**,
498
+ clone `purchaseOrderId` **85**) and `ItemReceiptItems.transferOrderItemId` (Core RF **2152**, clone
499
+ `purchaseOrderItemId` **471**). Correct + deployed but **dormant** — they only take effect once NYCHH's
500
+ real-`TrnfrOrd` item-receipt path is re-enabled (deferred; see
501
+ [NYCHH TransferOrders import](../../../clients/nychh/features/netsuite-transfer-order-import.md)). Note
502
+ for the team: this recipe was in this doc all along, yet the field-ACL model was reverse-engineered from
503
+ DB/code first this session before the doc was consulted — search the KB first. (jcardinal)
493
504
  - **2026-08-28** — Scoped the **third gate**: `Core.AclActions` + `AclActionPermissions` is
494
505
  `_Model_Core_Page::meta()` behavior only, and is **dead config in the surface-driven `toga25-supply`
495
506
  app** — `_Model_Core_Surface::resolve()` drops an element only when the element itself declares an
@@ -3,7 +3,7 @@
3
3
  | Doc | Framework | Summary | Files |
4
4
  |-----|-----------|---------|-------|
5
5
  | [NYCHH inventory-adjustment → item-fulfillment linking (direct FK columns, REST)](features/netsuite-inventory-adjustment-fulfillment-link.md) | 1.0 | For NYCHH stock adjustments, the NetSuite **inventory adjustment** carries a link to the **Item Fulfillment** it corrects, on the multi-select body custom field | library/app/api/toga2.php, library/app/api/netsuite/rest.php, worker/crons/toga2/netsuite/common_sync_togasupply.php, worker/crons/toga2/netsuite/sync_togasupply_hh.php, worker/crons/toga2/netsuite/diagnose_hh_import_inventory_adjustment.php |
6
- | [NYCHH NetSuite → TransferOrders import (stocking-flag routing, PO bridges, origin location)](features/netsuite-transfer-order-import.md) | 1.0 | NYCHH's NetSuite **transfer orders are represented as NetSuite sales orders** (same as the GroWrk pattern), so the shared NetSuite → TOGa Supply importer (`App_ | library/app/api/toga2.php, library/app/api/netsuite/rest.php, library/app/netsuite.php, worker/crons/toga2/netsuite/sync_togasupply_hh.php, worker/crons/toga2/netsuite/common_sync_togasupply.php, worker/crons/toga2/netsuite/diagnose_hh_stuck_transfer_order.php, dbchanges2/Core/2026-08-27a - TransferOrderUuidSearchableIdentifier.sql, dbchanges2/Client/2026-08-28a - TransferOrderItemsTimestampColumns.sql, dbchanges2/Client/2026-08-28b - TransferOrderItemsTimestampColumnsReposition.sql |
6
+ | [NYCHH NetSuite → TransferOrders import (stocking-flag routing, PO bridges, origin location)](features/netsuite-transfer-order-import.md) | 1.0 | NYCHH's NetSuite **transfer orders are represented as NetSuite sales orders** (same as the GroWrk pattern), so the shared NetSuite → TOGa Supply importer (`App_ | library/app/api/toga2.php, library/app/api/netsuite/rest.php, library/app/netsuite.php, worker/crons/toga2/netsuite/sync_togasupply_hh.php, worker/crons/toga2/netsuite/common_sync_togasupply.php, worker/crons/toga2/netsuite/diagnose_hh_stuck_transfer_order.php, dbchanges2/Core/2026-08-27a - TransferOrderUuidSearchableIdentifier.sql, dbchanges2/Client/2026-08-28a - TransferOrderItemsTimestampColumns.sql, dbchanges2/Client/2026-08-28b - TransferOrderItemsTimestampColumnsReposition.sql, dbchanges2/Client/2026-08-29 - ItemReceiptTransferOrderIdFieldPermission.sql, dbchanges2/Client/2026-08-29b - ItemReceiptItemTransferOrderItemIdFieldPermission.sql |
7
7
  | [NYCHH PO links are UPSTREAM — the downstream SO→PO route returns empty](features/po-number-upstream-direction.md) | 2.0 | NYCHH's sales orders are created **from the customer's purchase order**, so their SO↔PO links live in the **upstream** table `PurchaseOrders_SalesOrders` (route | _underscore/Model/Client/SalesOrder.php, _underscore/Trait/Netsuite/SalesOrder.php, toga25-supply/src/pages/SalesOrders/view/SalesOrderRecordModalLayout/hooks/usePurchaseOrderDetails.ts, dbchanges2/Client/2026-08-11b - SalesOrderPurchaseOrdersField.sql, dbchanges2/Client/2026-08-25 - SalesOrderPurchaseOrdersFieldAllClients.sql |
8
8
  | [NYCHH transfer-order 2.0 model — inventory quantities + V2 field enablement](features/transfer-order-inventory-quantities.md) | 2.0 | The 2.0 (`_underscore`) side of NYCHH transfer-order support: a **two-branch inventory quantity model** on the NYCHH `Item` override, plus the **client-override | _underscore/Model/Nychh/Item.php, _underscore/Model/Nychh/TransferOrder.php, _underscore/Model/Nychh/TransferOrderStage.php, _underscore/Model/Client/TransferOrder.php, _underscore/Model/Client/TransferOrderItem.php, dbchanges2/Client_Nychh/2026-08-27a - TransferOrderCustomFieldWritePermission.sql, dbchanges2/Client_Nychh/2026-08-27b - TransferOrderStageStatusIdentifier.sql, dbchanges2/Client_Nychh/2026-08-28c - TransferOrderRecordAcl.sql, dbchanges2/Client/2026-08-28b - TransferOrderItemsTimestamps.sql |
9
9
  | [NYC Health & Hospitals](profile.md) | 2.0 | NYC Health & Hospitals (NYCHH) is a TOGA 2.0 client on the `_underscore` platform, prod schema `Client_Nychh`. | dbchanges2/Client_Nychh/2026-08-18 - InventoryUnitsItemColumns.sql, dbchanges2/Client_Nychh/2026-08-24 - FixInventoryGroupingsUnitsTopologyOverrideIds.sql, dbchanges2/Client_Nychh/2026-08-27a - TransferOrderCustomFieldWritePermission.sql, dbchanges2/Client_Nychh/2026-08-27b - TransferOrderStageStatusIdentifier.sql, dbchanges2/Client_Nychh/2026-08-28a - TransferOrdersTableView.sql, dbchanges2/Client_Nychh/2026-08-28b - TransferOrdersNavigationEnable.sql, dbchanges2/Client_Nychh/2026-08-28c - TransferOrderRecordAcl.sql, dbchanges2/Client/2026-08-28b - TransferOrderItemsTimestamps.sql |
@@ -18,6 +18,8 @@ files:
18
18
  - dbchanges2/Core/2026-08-27a - TransferOrderUuidSearchableIdentifier.sql
19
19
  - dbchanges2/Client/2026-08-28a - TransferOrderItemsTimestampColumns.sql
20
20
  - dbchanges2/Client/2026-08-28b - TransferOrderItemsTimestampColumnsReposition.sql
21
+ - dbchanges2/Client/2026-08-29 - ItemReceiptTransferOrderIdFieldPermission.sql
22
+ - dbchanges2/Client/2026-08-29b - ItemReceiptItemTransferOrderItemIdFieldPermission.sql
21
23
  related:
22
24
  - ../../../1.0/apps/worker/features/netsuite-togasupply-per-client-sync.md
23
25
  - ../../../1.0/apps/library/features/toga2-api-client-and-bridge.md
@@ -160,8 +162,68 @@ that rethrows warnings as a catchable `ErrorException` with exact `file:line`. T
160
162
  `transferOrderStage` crash after the stale `Logs.Issue` trace misdirected. **When a 1.0 cron freeze's
161
163
  `Logs.Issue` trace looks wrong, trust a single-record harness over the aggregated trace.**
162
164
 
165
+ ## Real NetSuite Transfer Order (TrnfrOrd) item receipts — DEFERRED / SKIPPED (2026-08-29)
166
+
167
+ Everything above concerns transfer orders that arrive **as NetSuite sales orders** (the stocking-flag
168
+ routing). A *separate* case is a NetSuite item receipt whose **`createdFrom` is a real
169
+ `TrnfrOrd` record** ("Transfer Order #N"), not a stocking sales order presenting as `'Sales Order'`.
170
+ Importing that path was **attempted this session and INTENTIONALLY DISABLED** — a documented skip in
171
+ `common_sync_togasupply.php`, exactly like the existing Return-Authorization (`RtnAuth`) receipt skip —
172
+ because the feature is **not production-ready**. Do not re-enable it without doing the rebuild below.
173
+
174
+ **Why it was disabled (three blockers):**
175
+
176
+ 1. **The shared `syncTransferOrderFromNetsuite` item-sync is NON-IDEMPOTENT.** It appends the TO's items
177
+ every run instead of reconciling — the attempted TO #15 reached **22 duplicate items all `lineNumber`
178
+ 1**, and a **pre-existing stocking-SO** transfer order (`Client_Nychh.TransferOrders` id **1** /
179
+ number **62541**) already carries **13 duplicates**. So the duplicate bug is in the **shared** item
180
+ loop and affects **stocking-SO TOs too**, not just the real-TO path (see the shared
181
+ [per-client-sync doc](../../../1.0/apps/worker/features/netsuite-togasupply-per-client-sync.md)).
182
+ 2. **api2 cannot resolve the receipt's `transferOrderItem` reference — `EV-12` "no searchable
183
+ identifiers."** `TransferOrderItems` is **not** set up to be referenced by `(transferOrder,
184
+ lineNumber)` the way `PurchaseOrderItems` is (which is how a receipt line normally links to its
185
+ order line). This needs api2 **reference metadata** making a `TransferOrderItems` identifier
186
+ searchable.
187
+ 3. **Receipt-line ↔ TO-line mapping is wrong.** The receipt line's `orderLine` did not match the TO
188
+ item's `lineNumber`, so even past the EV-12 the line link would be incorrect.
189
+
190
+ **Cleanup done:** deleted the TO #15 duplicate artifact (`TransferOrder` 114 + its items).
191
+
192
+ **Inactive scaffolding left STAGED for the rebuild (present but not wired live):**
193
+
194
+ - `App_Api_Netsuite_Rest::fetchTransferOrderById` — real `TrnfrOrd` shim (`source` = location,
195
+ `dest` = `transferLocation`, `ordPicked`/`ordReceived` flags).
196
+ - `syncTransferOrderFromNetsuite($isRealTransferOrderRecord = true)` — dest resolved from the
197
+ `transferLocation` warehouse; stage derived from the picked/received flags.
198
+ - a location → client lookup in the cron.
199
+ - the guarded `transferOrderItem` link in the receipt-item builder.
200
+
201
+ **ACL grants written + deployed this session (correct, but DORMANT until real-TO receipts are
202
+ re-enabled):** two standard-field write grants cloning a working sibling per the
203
+ [ACL permission chain](../../../2.0/apps/_underscore/features/acl-permission-chain.md) recipe, using
204
+ **hardcoded `Core.RecordFields` id literals** (the cross-cluster caveat — a `Client_*`→`Core` JOIN is
205
+ refused on the prod client cluster):
206
+
207
+ - `dbchanges2/Client/2026-08-29 - ItemReceiptTransferOrderIdFieldPermission.sql` —
208
+ `ItemReceipts.transferOrderId` (Core RecordField **2153**), cloning `purchaseOrderId` (**85**).
209
+ - `dbchanges2/Client/2026-08-29b - ItemReceiptItemTransferOrderItemIdFieldPermission.sql` —
210
+ `ItemReceiptItems.transferOrderItemId` (Core RecordField **2152**), cloning `purchaseOrderItemId`
211
+ (**471**).
212
+
213
+ **REBUILD SPEC (all four, in order, before re-enabling + removing the skip):**
214
+ 1. Make the shared `syncTransferOrderFromNetsuite` item-sync **idempotent** (reconcile against existing
215
+ `TransferOrderItems` by a stable key) — this also fixes the stocking-SO dups and must **clean the
216
+ pre-existing TO 1** (13 dups).
217
+ 2. Add api2 **`TransferOrderItems` reference metadata** so a receipt line can resolve its TO item by a
218
+ searchable identifier (the analogue of the `PurchaseOrderItems` `(purchaseOrder, lineNumber)` setup).
219
+ 3. **Verify the receipt-line ↔ TO-line mapping** (the `orderLine` ↔ TO-item `lineNumber` correspondence).
220
+ 4. Re-enable the path, confirm the (now non-dormant) ACL grants take effect, and **remove the skip**.
221
+
163
222
  ## Gotchas / known issues
164
223
 
224
+ - **Real-TrnfrOrd item receipts are disabled — see the DEFERRED section above.** A receipt whose
225
+ `createdFrom` is a real Transfer Order is skipped by design; do not read that as the importer being
226
+ broken.
165
227
  - **Order 128829 (no location anywhere) is unresolved** — see the origin-resolution section. Do not
166
228
  assume the importer is broken if this single order is stuck; every located order imports.
167
229
  - **Routing is gated by the wrapper constant.** If a future client should route transfer orders,
@@ -174,6 +236,18 @@ that rethrows warnings as a catchable `ErrorException` with exact `file:line`. T
174
236
 
175
237
  ## Change history
176
238
 
239
+ - 2026-08-29 — Recorded the **real NetSuite `TrnfrOrd` item-receipt feature as DEFERRED/SKIPPED**
240
+ (documented skip in `common_sync_togasupply.php`, like the `RtnAuth` skip) after attempting it and
241
+ hitting three blockers: the shared `syncTransferOrderFromNetsuite` item-sync is **non-idempotent**
242
+ (TO #15 got 22 dup items; pre-existing stocking-SO TO 1/62541 has 13 — shared-loop bug, affects
243
+ stocking-SO TOs too), api2 can't resolve the receipt's `transferOrderItem` reference (`EV-12`, no
244
+ searchable identifier on `TransferOrderItems`), and the receipt-line `orderLine` ≠ TO-item `lineNumber`.
245
+ Deleted the TO #15 dup artifact (TransferOrder 114). Left inactive scaffolding staged
246
+ (`fetchTransferOrderById`, `syncTransferOrderFromNetsuite($isRealTransferOrderRecord=true)`,
247
+ location→client lookup, guarded `transferOrderItem` link). Wrote + deployed (but **dormant**) two ACL
248
+ field-permission grants using hardcoded Core RecordFields literals: `ItemReceipts.transferOrderId`
249
+ (RF 2153, clone `purchaseOrderId` 85) and `ItemReceiptItems.transferOrderItemId` (RF 2152, clone
250
+ `purchaseOrderItemId` 471). Documented the four-step rebuild spec. (jcardinal)
177
251
  - 2026-08-28 — Unfroze the `SALES_ORDERS` section (NYCHH + GroWrk) by peeling **three** stacked faults
178
252
  on `syncTransferOrderFromNetsuite`: (1) `Undefined property $transferOrderStage` — api2 **omits a
179
253
  null FK object** from a depth-2 GET, guarded `transferOrderStage`/`originLocation`/`destinationLocation`
@@ -40,6 +40,12 @@ This is a team directive. It applies to every session, both frameworks, every re
40
40
  write, or spawn agents.
41
41
  5. **Ordering is unchanged.** The team KB stays the **first** source, ahead of all other
42
42
  research — that part was already correct and stays.
43
+ 6. **Search the KB before reverse-engineering — mid-session, not just at kickoff.** Before
44
+ reverse-engineering any code, schema, or framework mechanism from the source or the live DB,
45
+ FIRST search the team KB (`node "<TEAM_REPO>/knowledge.js" search --q=…`). The KB is large and
46
+ usually already documents the mechanism — reaching for DB/code archaeology first is the mistake.
47
+ This extends rule 5 (KB is the first source) from kickoff to every point in a session where you
48
+ are about to derive how something works.
43
49
 
44
50
  ## How it is enforced
45
51
 
@@ -68,6 +74,12 @@ than the old subagent approach did. Mitigate by not re-reading absorbed docs and
68
74
  only the needed section of a large spec — but priming in the main session is the requirement.
69
75
 
70
76
  ## Change history
77
+ - 2026-08-29 — Added rule 6: search the KB before reverse-engineering any code/schema/framework
78
+ mechanism, mid-session and not only at kickoff. Prompted by a NetSuite→TOGa Supply session that
79
+ reverse-engineered the entire 2.0 field-ACL model (AclFieldPermissions, Core.RecordFields,
80
+ EV-9/EV-12, the add-a-writable-field recipe, the cross-cluster hardcoded-literal caveat) from
81
+ DB/code before discovering it was fully documented in
82
+ `2.0/apps/_underscore/features/acl-permission-chain.md` all along. (jcardinal)
71
83
  - 2026-08-13 — Standard recorded. Kickoff priming must run in the main session, synchronously,
72
84
  and no subagent may start until priming is complete; released by the new
73
85
  `knowledge.js kickoff-primed` sentinel. Retired the `context-primer` subagent from the
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "toga-ai",
3
- "version": "1.0.700",
3
+ "version": "1.0.701",
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",