toga-ai 1.0.732 → 1.0.734

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.
@@ -530,6 +530,20 @@ Parameters are stored **per client DB** but accessed **through the TOGa2 API**,
530
530
  a new line is actually created. Full root cause, the prod cleanup migration, and the
531
531
  deploy-fix-before-cleanup sequencing live in the
532
532
  [Compass ODP Duplicate PO-Line Cleanup](../../../clients/compass-usa/workflows/odp-duplicate-po-line-cleanup.md).
533
+ - **Customer-PO created from a sales order was missing `dateOrder` and had `cost` hardcoded 0 (both
534
+ fixed 2026-09-01, shared, `library` only).** In `syncSalesOrderFromNetsuite`
535
+ (`library/app/api/toga2.php`), the "create the customer PO" `POST /purchase-orders` branch
536
+ (~L1961) never set `dateOrder`, so every customer PO built from a sales order had
537
+ `PurchaseOrders.dateOrder` NULL. Fix: add
538
+ `'dateOrder' => App_Date::convertToTimezone($nsOrder->tranDate, 'Y-m-d', 'America/New_York')`
539
+ (the same expression that sets the SO's own `dateOrder` at ~L1394). Separately, all **three**
540
+ PO-item creation sites (`POST /purchase-order-item-sales-order-items`, ~L1863/1937/2051) sent
541
+ `'cost' => 0`; because this PO is the customer PO **for** the sales order, the PO line's cost is
542
+ the SO line's price — changed to `'cost' => $salesOrderItem['price']` (`$itemPrice`, from the
543
+ NetSuite rate or amount/qty fallback built ~L1223, always present). **Both are INSERT-ONLY:** the
544
+ create branches run only when the PO / line does not already exist and nothing PUTs `dateOrder` or
545
+ `cost`, so re-syncs and a second SO sharing the same customer-PO number never overwrite them.
546
+ **Existing NULL/`cost=0` rows are NOT backfilled** — new records only.
533
547
  - **⚠ A single un-deletable renumbered line could freeze the entire SALES_ORDERS section (fixed
534
548
  2026-08-17).** When NetSuite renumbers a line, `syncSalesOrderFromNetsuite` renumbers by
535
549
  **DELETE `/sales-order-items/{uuid}` then re-create in the per-item loop**. Its "pinned row" guard
@@ -881,9 +895,43 @@ Parameters are stored **per client DB** but accessed **through the TOGa2 API**,
881
895
  `'catalog' => ['name' => 'Agilant']` on the inventory-adjustment item-create. Stops all new duplicates.
882
896
  The one-time cleanup of the accumulated ~4,300 duplicate part numbers across all 17 clients is the
883
897
  [NetSuite duplicate-Items dedup workflow](../workflows/netsuite-duplicate-items-dedup.md).
898
+ - **⚠ Inventory-adjustment `/units` reconciliation missed an existing unit on a serial-CASE mismatch →
899
+ 1062 dup-key freeze (fixed 2026-09-01, shared, `library` only).** In
900
+ `syncInventoryAdjustmentFromNetsuite` (`library/app/api/toga2.php` ~L5985) the PHP lookup of existing
901
+ units was keyed by the **returned** unit serial (`$thisLookupUnitsBySerialNumber[$unit->serialNumber]`,
902
+ stored `'n/a'`) but looked up with the NetSuite serial **uppercased** via `strtoupper` (`'N/A'`). **PHP
903
+ array keys are case-sensitive; the `Units.itemId_serialNumber` unique key is case-insensitive** — so
904
+ the existing unit was missed, re-INSERTed, and collided MySQL **1062** ("Duplicate entry '560-N/A'")
905
+ → `EV-10` → the section aborted (fail-loud, no per-record try/catch) and froze at `1-RUNNING`. **Fix:**
906
+ key the lookup by `strtoupper($unit->serialNumber)` so it matches the uppercased NetSuite serial;
907
+ section self-heals on deploy (mode→IDLE, watermark advances). The `Items.uuid=$uuidItem` scoping was
908
+ left intact deliberately — copying the fulfillment "serial-only" lookup would be wrong for the shared
909
+ `'N/A'` placeholder serial (it would reuse another item's placeholder unit). **Durable lesson: when a
910
+ PHP lookup mirrors a case-insensitive DB unique key, normalize the KEY the same way you normalize the
911
+ probe — case-fold both, not one.**
912
+ - **Diagnosis playbook (durable) — localize a frozen inventory-adjustment section without NetSuite
913
+ access.** The 2026-09-01 NYCHH freeze above was diagnosed purely from DB logs, and item-matching was
914
+ *ruled out* (item 560 / partNumber B11B242201 is the only Agilant-catalog item for that part — no
915
+ duplicate second-catalog item, `$uuidItem` resolved correctly; the inventory-adjustment item-create
916
+ already stamps catalog `Agilant`). Chain: **`Client_<Id>.Parameters`** (localize the stuck section:
917
+ `1-RUNNING` + frozen watermark) → **`Logs.Event` filtered by `clientId`** (crash `file:line`, the
918
+ chain `toga2.php → common_sync_togasupply.php → sync*FromNetsuite`, plus the `transactionId`) →
919
+ **`Logs_<Client>.Api` on that `transactionId`** (the untruncated `EV-*`/MySQL rejection + payload).
920
+ A "silent freeze" here is a serial/data collision, not necessarily a catalog/item-matching problem —
921
+ read the untruncated Api-log rejection before assuming item matching.
884
922
 
885
923
  ## Change history
886
924
 
925
+ - 2026-09-01 — **Customer-PO `dateOrder`/`cost` fixes + NYCHH inventory-adjustment serial-case unfreeze
926
+ (shared, `library` only, working-tree — not yet committed/deployed).** `syncSalesOrderFromNetsuite`
927
+ now sets the customer PO's `dateOrder` from the SO `tranDate` (was always NULL) and its PO lines'
928
+ `cost` from the SO line `price` (was hardcoded 0) across all three PO-item create sites — both
929
+ INSERT-only, no backfill of existing rows. `syncInventoryAdjustmentFromNetsuite` now keys the `/units`
930
+ reconciliation lookup by `strtoupper($unit->serialNumber)` so a case-mismatched serial (`'n/a'` vs
931
+ `'N/A'`) matches the case-insensitive `Units.itemId_serialNumber` unique key — was missing the
932
+ existing unit and 1062-colliding, freezing NYCHH `INVENTORY_ADJUSTMENTS` at `1-RUNNING` (watermark
933
+ frozen 2022-12-28). Ruled out item-matching as the cause (single Agilant-catalog item, `$uuidItem`
934
+ correct). All three benefit every togasupply client. (jcardinal)
887
935
  - 2026-09-01 — **TRUE-81576: stopped runaway duplicate `ShippingCarriers` creation (worker + library).**
888
936
  `common_sync_togasupply.php` now seeds the carrier lookup from its own paged `GET /shipping-carriers`
889
937
  loop instead of harvesting carriers out of the `/shipping-methods` loop — a carrier with zero
@@ -6,8 +6,8 @@ project: TOGa Blox
6
6
  client: shared
7
7
  type: workflow
8
8
  status: active
9
- updated: 2026-08-31
10
- owners: [jcardinal, apeterson]
9
+ updated: 2026-09-02
10
+ owners: [jcardinal, apeterson, tcox]
11
11
  files:
12
12
  - toga-blox/.github/workflows/publish.yml
13
13
  - toga-blox/package.json
@@ -86,12 +86,13 @@ environment needs its own maintained `_<mode>` branch in `toga-blox-npm`.
86
86
  ## Gotchas
87
87
 
88
88
  - **⚠ There is a STALE UNDERSCORED dist-tag on the registry — `_sandbox-client` (with the
89
- underscore) is NOT the channel.** Verified 2026-08-31, the registry carries **both**
90
- `_sandbox-client` = `1.0.316-sandbox-client.1` (vestigial, predates the current pipeline) **and**
91
- `sandbox-client` = `1.1.1-sandbox-client.140` (the live channel head). Branch `_<mode>` maps to
89
+ underscore) is NOT the channel.** Re-verified 2026-09-02, the registry still carries **both**
90
+ `_sandbox-client` = `1.0.316-sandbox-client.1` (vestigial, predates the current pipeline; it has
91
+ **not** moved in over a month) **and** `sandbox-client` = `1.1.2-sandbox-client.142` (the live
92
+ channel head). Branch `_<mode>` maps to
92
93
  channel `<mode>` with the underscore **stripped** (same convention as `amplify.yml`), so the
93
94
  **branch** `_sandbox-client` tracks the **tag** `sandbox-client`. Asking npm for "the latest
94
- `_sandbox-client` tag" literally installs something **139 publishes behind**. Always resolve the
95
+ `_sandbox-client` tag" literally installs something **141 publishes behind**. Always resolve the
95
96
  head with `npm view @agilant/toga-blox dist-tags` and install the underscore-less name.
96
97
  - **Publish before build.** `npm install @agilant/toga-blox@<mode>` fails `ETARGET No matching
97
98
  version` in the app build until this repo has published that channel. GitHub Actions runs the
@@ -131,8 +132,19 @@ environment needs its own maintained `_<mode>` branch in `toga-blox-npm`.
131
132
  was then made **on `_sandbox-client`**, at the developer's explicit direction. That **deviates
132
133
  from the `deploy-sandbox-client` skill's "never commit the bump directly to `_sandbox-client`"
133
134
  rule.** It was safe *only* because the feature branch does not carry the version line, so nothing
134
- could be clobbered — **record this as a one-off, not a new default.** CI then published base+1 →
135
- `1.0.334-sandbox-client.136`.
135
+ could be clobbered. CI then published base+1 → `1.0.334-sandbox-client.136`.
136
+ - **Bumping the base directly on `_sandbox-client` is the ACTUAL practice — it is safe under three
137
+ preconditions, and it is no longer a one-off.** Second occurrence 2026-09-02 (`1.1.0` → `1.1.1`,
138
+ commit `543a7186`, published `1.1.2-sandbox-client.142`), same shape as 2026-08-26. Stop treating
139
+ the skill's "never bump on `_sandbox-client`" line as absolute; treat it as conditional. The bump
140
+ on the release branch is fine when **all three** hold:
141
+ 1. The working tree is **clean**.
142
+ 2. The branch is **in sync with `origin`** (nothing unpushed, nothing to pull).
143
+ 3. No feature branch pending merge **touches the `version` line** — which is normally true,
144
+ because feature branches never edit it (see the gotcha above).
145
+ If any one fails, bump on the feature branch instead. Note the bump commit lands *only* on
146
+ `_sandbox-client` and is never merged back, which is precisely what generates the "N behind"
147
+ release plumbing described two gotchas up.
136
148
  - **`gh` is not installed on every dev machine.** The skill's `gh run watch` monitoring step simply
137
149
  does not work there. Fall back to polling the registry:
138
150
  `npm view @agilant/toga-blox@sandbox-client version` (or `npm dist-tag ls @agilant/toga-blox`)
@@ -200,6 +212,17 @@ environment needs its own maintained `_<mode>` branch in `toga-blox-npm`.
200
212
  you push to a release branch.
201
213
 
202
214
  ## Change history
215
+ - 2026-09-02 — Released `1.1.2-sandbox-client.142` (base `1.1.0` → `1.1.1` on `_sandbox-client`,
216
+ commit `543a7186`) and rolled it into `toga25-supply` (pin `1.1.1-sandbox-client.140` →
217
+ `1.1.2-sandbox-client.142`, clean `npm install`, `tsc --noEmit` zero errors). The documented
218
+ workflow was accurate end-to-end — no new gotchas. **Promoted the on-branch version bump from a
219
+ "one-off deviation" to the actual practice with three stated preconditions** (clean tree, in sync
220
+ with origin, no pending merge touching the `version` line); this was its second occurrence under
221
+ identical conditions. Refreshed the live `sandbox-client` head in the stale-dist-tag gotcha
222
+ (`.140` → `.142`); the vestigial `_sandbox-client` tag is unmoved at `1.0.316-sandbox-client.1`.
223
+ Confirmed the registry-polling fallback (`npm view @agilant/toga-blox@sandbox-client version`)
224
+ is the working verification path where `gh` is absent, and that publish lands ~2 min after the
225
+ push. (tcox)
203
226
  - 2026-08-31 — Recorded the **stale underscored dist-tag trap**: the registry carries both
204
227
  `_sandbox-client` (`1.0.316-sandbox-client.1`, vestigial) and `sandbox-client`
205
228
  (`1.1.1-sandbox-client.140`, live head). Installing the underscored name silently pins 139
@@ -6,7 +6,7 @@ project: Library
6
6
  client: nychh
7
7
  type: client-feature
8
8
  status: active
9
- updated: 2026-08-30
9
+ updated: 2026-09-01
10
10
  owners: [jcardinal]
11
11
  files:
12
12
  - library/app/api/toga2.php
@@ -134,11 +134,22 @@ Both are framework-level NetSuite facts — full detail on
134
134
  **`mainline='F'` is the WRONG filter** — NetSuite sometimes marks the first real fulfilled line
135
135
  `mainline='T'`.
136
136
 
137
- ## Two shared-engine fixes in `syncInventoryAdjustmentFromNetsuite` (surfaced here, affect any client)
137
+ ## Three shared-engine fixes in `syncInventoryAdjustmentFromNetsuite` (surfaced here, affect any client)
138
138
 
139
- Both are in the shared library method — see also the
139
+ All in the shared library method — see also the
140
140
  [per-client sync](../../../1.0/apps/worker/features/netsuite-togasupply-per-client-sync.md):
141
141
 
142
+ - **(c) Serial-CASE mismatch missed the existing unit → 1062 freeze (fixed 2026-09-01).** The `/units`
143
+ reconciliation lookup was keyed by the **returned** unit serial (`$unit->serialNumber`, stored `'n/a'`)
144
+ but probed with the NetSuite serial **uppercased** via `strtoupper` (`'N/A'`). PHP array keys are
145
+ case-sensitive but `Units.itemId_serialNumber` is a **case-insensitive** unique key, so the existing
146
+ unit (id 546, itemId 560, serial `'n/a'`) was missed and re-INSERTed → MySQL **1062**
147
+ ("Duplicate entry '560-N/A'") → `EV-10` → section aborted (no per-record try/catch). This froze NYCHH
148
+ `INVENTORY_ADJUSTMENTS` at `1-RUNNING`, watermark stuck 2022-12-28 (integration was recently
149
+ restarted). **Fix:** key the lookup by `strtoupper($unit->serialNumber)`. The `Items.uuid=$uuidItem`
150
+ scoping was **kept deliberately** — copying the fulfillment "serial-only" lookup would be wrong for the
151
+ shared `'N/A'` placeholder serial (would reuse another item's placeholder unit). Section self-heals on
152
+ deploy. `library` only, working-tree, not yet committed/deployed.
142
153
  - **(a) Prune-order FK violation.** The stale-item prune deleted an `InventoryAdjustmentItem` while
143
154
  its child `InventoryAdjustmentItemUnits` still referenced it (FK **RESTRICT**, no cascade) → MySQL
144
155
  **1451** → `EV-11`. **Fix:** delete the child units (`/inventory-adjustment-item-units/{uuid}`)
@@ -197,9 +208,29 @@ this feature on the scheduled cron.
197
208
  - **Empty lookups drop or duplicate items.** The item loop filters to known warehouses and creates
198
209
  items on lookup-miss, so any manual/diagnostic invocation must seed the warehouse/item/manufacturer
199
210
  lookups (the harness does).
211
+ - **A frozen NYCHH `INVENTORY_ADJUSTMENTS` section is NOT necessarily an item-matching / two-catalog
212
+ problem (verified 2026-09-01).** When the section stuck at `1-RUNNING`, item matching was suspected
213
+ (wrong of two intentional catalogs). Verified in prod it was fine: item **560**
214
+ (uuid `4c7b93d2-…`, partNumber **B11B242201**, HYBRID) is the Agilant-catalog item and the **only**
215
+ item with that part number — no duplicate second-catalog item, `$uuidItem` resolved correctly, and the
216
+ inventory-adjustment item-create already stamps catalog `Agilant`. The freeze was purely the serial-CASE
217
+ bug (fix (c) above). **Diagnosis chain (no NetSuite access):** `Client_Nychh.Parameters` (localize the
218
+ stuck section) → `Logs.Event` `clientId=3` (crash `toga2.php:6080` via
219
+ `common_sync_togasupply.php:1180` `syncInventoryAdjustmentFromNetsuite`, + `transactionId`) →
220
+ `Logs_Nychh.Api` on that `transactionId` (untruncated `EV-10`/1062 rejection + payload).
200
221
 
201
222
  ## Change history
202
223
 
224
+ - 2026-09-01 — **Unfroze NYCHH `INVENTORY_ADJUSTMENTS` — serial-CASE mismatch in the `/units` lookup
225
+ (shared `library`, working-tree, not yet committed/deployed).** `syncInventoryAdjustmentFromNetsuite`
226
+ keyed the units lookup by the returned serial (`'n/a'`) but probed with the uppercased NetSuite serial
227
+ (`'N/A'`); PHP array keys are case-sensitive while `Units.itemId_serialNumber` is case-insensitive, so
228
+ the existing unit was missed, re-INSERTed, and 1062-collided → `EV-10` → section aborted at
229
+ `1-RUNNING` (watermark frozen 2022-12-28). Fixed by keying on `strtoupper($unit->serialNumber)`; kept
230
+ the `Items.uuid=$uuidItem` scoping (the shared `'N/A'` placeholder makes serial-only wrong here). Ruled
231
+ out item-matching / two-catalog as the cause (item 560 / B11B242201 is the only Agilant-catalog item,
232
+ `$uuidItem` correct). Diagnosed entirely from `Parameters` → `Logs.Event` (clientId 3) → `Logs_Nychh.Api`
233
+ by `transactionId`, no NetSuite access. (jcardinal)
203
234
  - 2026-08-30 — Verified that NYCHH adjustments with a NULL `itemFulfillmentId`/`itemFulfillmentItemId`
204
235
  are **correct, not a bug**: 0 of 27 NYCHH adjustments populate NetSuite `custbody_stock_adjustment`
205
236
  (~1 in 40 account-wide), so most carry no link. Confirmed the shim's scalar SuiteQL read of the field
@@ -187,4 +187,9 @@ table views. Client-specific DB change-sets live in `dbchanges2/Client_Nychh/`.
187
187
  path on any branch, and neither is in the working tree — so the transfer-order **custom-field
188
188
  write permission** and the **stage status identifier** are, like the ACL layers above, prod/local
189
189
  state with no file behind them. Rewrite them before anyone builds a fresh environment.
190
+ - 🚨 **OPEN, not investigated (found 2026-09-01): recurring api2 `1054` errors for NYCHH.**
191
+ `Logs.Event` **issueId 64**, `clientId 3`: *"Database Error Occurred Error #: 1054 Unknown column
192
+ 'vendor.uuid' in 'where clause'"* on a `PurchaseOrders` GET (`_Query->getFoundRows` via
193
+ `V2.php processRoutePairs`), several occurrences ~2026-09-01 12:29. Flagged for a future look; no
194
+ change made. Likely a TableView/query join referencing `vendor.uuid` that isn't in scope.
190
195
  - This profile is a starting point; expand as more NYCHH-specific behavior is captured.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "toga-ai",
3
- "version": "1.0.732",
3
+ "version": "1.0.734",
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",