toga-ai 1.0.1171 → 1.0.1173

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,8 +6,8 @@ project: _Underscore
6
6
  client: shared
7
7
  type: feature
8
8
  status: active
9
- updated: 2026-10-15
10
- owners: [jcardinal, rgirish]
9
+ updated: 2026-10-27
10
+ owners: [jcardinal, rgirish, bala]
11
11
  files:
12
12
  - _underscore/Model/Client/ItemFulfillment.php
13
13
  - _underscore/Model/Client/ItemFulfillmentItem.php
@@ -80,7 +80,7 @@ Engine is shared `_Model_Client_*` logic. Compass uses **empty pass-through subc
80
80
  ## Gotchas / known issues
81
81
  - **Interceptors are DB-driven.** `postPost`/`postPut` PHP does nothing without matching `ApiPayloadInterceptors` rows (recordId 28/29/30/41). **Deploying to any env requires running BOTH Core SQL files** (`…Creation.sql` for POST/POST + RecordFields, and `…Put.sql` for POST/PUT) against that env's Core DB — the PHP won't fire on PUT otherwise.
82
82
  - **`depth: -1` always** on orchestration calls. `depth => 0` means *unlimited* FK traversal → OOM. Never use 0.
83
- - **Bundle scaling is gated on DISTINCT DOWNSTREAM ITEMS, not downstream SOI lines (corrected 2026-08-18).** A "Recipe" query, per upstream SOI, takes **one ordered-qty slot per distinct downstream `itemId`** (MAX ordered qty across duplicate same-item sourcing lines) and counts the downstream lines. Scaling (`upstreamQty = totalDownstreamFulfilled × upstreamOrderedQty / Σ distinctDownstreamOrderedQty`, **capped at the raw sum** so it can only reduce, never inflate) applies **only when `distinctDownstreamItemCount > 1`** — a genuine multi-component bundle. A qty-1 bundle of 6 fully-shipped distinct components still → upstream qty 1 (6×1/6); 3×2/6=1 preserved. When a single distinct downstream item is sourced (incl. an accepted over-shipment split across several downstream SOs/SOIs), the raw fulfilled qty mirrors up **unscaled** (round() applied symmetrically on this branch too).
83
+ - **Bundle scaling is gated on DISTINCT DOWNSTREAM ITEMS, not downstream SOI lines (corrected 2026-08-18).** A "Recipe" query, per upstream SOI, takes **one ordered-qty slot per distinct downstream `itemId`** (MAX ordered qty across duplicate same-item sourcing lines) and counts the downstream lines. Scaling (`upstreamQty = totalDownstreamFulfilled × upstreamOrderedQty / Σ distinctDownstreamOrderedQty`, **capped at the raw sum** so it can only reduce, never inflate) applies **only when `distinctDownstreamItemCount > 1`** — a genuine multi-component bundle. A qty-1 bundle of 6 fully-shipped distinct components still → upstream qty 1 (6×1/6); 3×2/6=1 preserved. When a single distinct downstream item is sourced (incl. an accepted over-shipment split across several downstream SOs/SOIs), the raw fulfilled qty mirrors up **unscaled** (round() applied symmetrically on this branch too). **A partly shipped bundle leaves a fractional upstream IFI** (5 of 6 parts = 0.83); any other writer on that SOI must cap its qty at `ordered − fulfilled`, as the [Compass ASN handler](../../../../clients/compass-usa/features/asn-to-item-fulfillment.md) does since 2026-10-27.
84
84
  - **Same-item over-shipment used to mis-scale into fractions (root cause, fixed 2026-08-18).** Pre-fix the denominator summed ordered qty over *distinct downstream SOI lines*. A vendor (e.g. Office Depot / ODP) can create **multiple downstream SalesOrders for the SAME item off ONE upstream PO line** (accepted over-ship / re-send); each duplicate is another distinct downstream SOI, inflating the denominator and shrinking the factor. Compass SA135471's hero SOI (item 2682, qty 1) mapped to two ODP SOIs (same item, each qty 1) via one POI, so factor = 1/2 and each of two customer IFs showed **qty 0.50** ("Quantity Fulfilled" 0.5 + 0.5). Two physically distinct serials had shipped — a real over-ship, not a phantom duplicate. Fix: distinct-item denominator (above) mirrors the raw qty (→ 2). Data repaired by `dbchanges2/Client_Compass/2026-08-18 - FixSA135471HeroItemHalfQuantityFulfillment.sql`.
85
85
  - **Mixed topology (a real bundle whose component is ALSO sourced across multiple downstream lines) is logged, not silently averaged.** When the Recipe's `lineCount > distinctItemCount`, the engine `error_log()`s a diagnostic instead of averaging — same log-don't-swallow rule as the broken-bridge guard.
86
86
  - **Diagnosing over-ship vs. phantom duplicate:** traverse the bridge — upstream SOI → `SalesOrderItems_PurchaseOrderItems` → POI → `PurchaseOrders_SalesOrders` → downstream ODP SOs (`customerId=1`); compare downstream vs. upstream `itemId` (**same itemId ⇒ over-ship / duplicate sourcing, NOT a bundle**); then check `ItemFulfillmentItemUnits` serials — distinct serials ⇒ genuine over-shipment, identical ⇒ phantom duplicate.
@@ -102,6 +102,7 @@ Engine is shared `_Model_Client_*` logic. Compass uses **empty pass-through subc
102
102
  - `api2` architecture (V2 metadata engine that fires these interceptors; the commit-logs / rollback-data-on-failure transaction invariant the throws rely on).
103
103
 
104
104
  ## Change history
105
+ - 2026-10-27 — Noted that a partly shipped bundle mirrors as a fractional IFI, which the Compass ASN handler now caps against. (bala)
105
106
  - 2026-10-06 — Added `reconcileUpstreamItemTracking()`: line-level tracking (318) now mirrors up, add-only (645/645 chain lines lacked it). Recorded cron 3a's last-line link bug as the main broken-bridge source on Compass. (jcardinal)
106
107
  - 2026-09-22 — Narrowed the "no 317 interceptor" decision: the three tracking bridges (317/318/319) had **zero** `ApiPayloadInterceptors` rows — lost when `ItemFulfillmentPackages`/record 41 was consolidated — so the upstream walk never ran when tracking was attached after the chain existed (1,819 stranded Compass orders). Added `postPost`/`postPut` to the three bridge models + a Core registration migration. Recorded that units mirror on `unitId` only (32,585 legitimate cross-part links) and that the bridge models live in plural-named subfolders. (rgirish)
107
108
  - 2026-08-18 — Bundle scaling re-gated on distinct downstream **items** rather than downstream SOI lines, fixing same-item over-shipments mirroring as fractional quantities (Compass SA135471). (jcardinal)
@@ -6,7 +6,7 @@ project: Worker
6
6
  client: wje
7
7
  type: client-feature
8
8
  status: active
9
- updated: 2026-10-01
9
+ updated: 2026-10-27
10
10
  owners: ["snaredla", "mhammontree"]
11
11
  files:
12
12
  - worker2/Worker/Wje.php
@@ -134,9 +134,11 @@ Hardcoded IDs: `CLIENT_UUID_WJE=c2f5180f-…`, `API_UUID_WJE=4750650f-…` (and
134
134
  - **W10** Lazy-transaction rule: write back `c_wje*` identifiers via direct model `->save()`, not an internal API round trip inside an open transaction (cf. Startech G4).
135
135
  - **W11** `json_decode($s, true)` + top-level `(object)` cast leaves nested fields as arrays — cast each level you traverse.
136
136
  - **W12** TicketStages use Freshservice status codes (Open=2 … Closed=5); status sync depends on those matching live Freshservice statuses.
137
+ - **W13** Duplicate webhooks: WJE automator rules `13000322929` and `13000322930` send the same payload at the same second for every ticket update/reply, so two `Wje/Webhook` jobs run in parallel and race GET-then-POST → loser hits MySQL 1062 (e.g. `TicketNotes_UNIQUE` on `c_wjeConversationId`, shown as EV-10 "Operation Failed"). No data lost. Handled (TRUE-83301): `Webhook()` wraps private `processWebhook()` and re-runs it once when the error message contains `Error #: 1062` (second run finds the saved rows and PUTs); other errors still fail the job. Team rule: duplicates must never break the WJE sync — don't narrow this to a per-note try/catch (misses tickets/contacts). Open: ask WJE to disable one duplicate rule; rule failures since 2026-10-01 (929/930: 54 each, `13000322933`: 76) not yet investigated. EV-10 also on TRUE-82892.
137
138
  - **Open item — hardcoded credentials (parked 2026-10-01, separate ticket needed):** `API_SECRET_WJE` is a class const literal in `worker2/Worker/Wje.php`; the Freshservice API key is a literal in `_underscore/Component/Api/Wje/Wje.php` `initialize()` (plus the 1.0 copy, W9). Fix = move to config + rotate. Never copy the values into docs or commits.
138
139
 
139
140
  ## Change history
141
+ - 2026-10-27 — Added W13 (duplicate automator webhooks → 1062 race; one retry on 1062). (snaredla, TRUE-83301)
140
142
  - 2026-10-01 — Corrected W3 (`c_wjeTicketId` = `id_numeric`, `INC-…` → `number`); recorded hardcoded-credential locations as an open item; linked the monitor suite. (mhammontree)
141
143
  - 2026-10-01 — Corrected dept 290 (not synced 1.0 → 2.0); linked the new ticket-writes monitor. (mhammontree)
142
144
 
@@ -5,7 +5,7 @@ project: _Underscore
5
5
  client: compass-usa
6
6
  type: client-feature
7
7
  status: active
8
- updated: 2026-10-15
8
+ updated: 2026-10-27
9
9
  owners: [jcardinal, bala]
10
10
  files:
11
11
  - _underscore/Model/Compass/AdvanceShippingNotice.php
@@ -19,6 +19,7 @@ files:
19
19
  - dbchanges2/Client_Compass/2026-06-18a - CleanupSA132898DuplicateTracking.sql
20
20
  - dbchanges2/Client_Compass/2026-06-18b - CleanupSA132881DuplicateTracking.sql
21
21
  - dbchanges2/Client_Compass/2026-07-02a - FixSA133377TrackingSerialAndDuplicateIF.sql
22
+ - dbchanges2/Client_Compass/2026-10-27a - AsnFulfillmentOvershoot.sql
22
23
  related:
23
24
  - ../../../2.0/apps/api2/features/cxml-shipnotice-gateway.md
24
25
  - ../../compass-canada/features/grand-and-toy-asn-import.md
@@ -48,7 +49,7 @@ As of 2026-06-15 the handler **reconciles tracking onto whatever fulfillment exi
48
49
  1. Read ASN → resolve PO → SO via `SalesOrders_PurchaseOrders`; skip if no SO.
49
50
  2. Select **all** ASN items mapped to a SalesOrderItem (NO "unfulfilled only" filter), carrying each SOI's `quantity` and already-`SUM`med fulfilled qty.
50
51
  3. Per ASN item, compute `remaining = SOI.quantity − fulfilled` (rounded to kill float dust):
51
- - **`remaining > 0`** (create path, unchanged): reuse the SO-numbered IF or POST a new one, POST an IFI; that new IFI is the tracking target. Additional SO-numbered IFs are acceptable.
52
+ - **`remaining > 0`** (create path): reuse the SO-numbered IF or POST a new one, POST an IFI with quantity **`min(ASN item quantity, remaining)`** (since 2026-10-27, see bundle overshoot gotcha); that new IFI is the tracking target. Additional SO-numbered IFs are acceptable.
52
53
  - **`remaining <= 0`** (reconcile path, new): tracking targets are the **existing IFIs** for that SOI (via `ItemFulfillmentItems.salesOrderItemId` → their `itemFulfillmentId`), NOT an SO-number lookup — NetSuite/toga2-supply IFs are `F`-numbered, not SO-numbered. As of 2026-06-18 an existing IFI that **already carries item-level tracking is skipped** here (`itemFulfillmentItemHasTracking()`): reconcile only attaches onto untracked out-of-band IFIs. An already-tracked, fully-fulfilled line revisited by a later ASN is a vendor re-transmission and must not gain a second tracking number (see duplicate-tracking gotcha).
53
54
  4. Copy the ASN item's tracking (`AdvanceShippingNoticeItems_TrackingNumbers`) onto each target IFI via `POST /item-fulfillment-item-tracking-numbers` → `ItemFulfillmentItems_TrackingNumbers` — **idempotent** (skipped if the bridge row already exists; ASNs get re-POSTed).
54
55
  5. Per ASN unit: serialized (has a Unit) → attach unit tracking to the matching IFIU (`POST /item-fulfillment-item-unit-tracking-numbers`; create the IFIU only on the create path); tracking-only (`unitId IS NULL`) → collect its tracking for an IF package.
@@ -107,6 +108,7 @@ Something was fixed around May/June 2026. Not fully fixed.
107
108
  **Ruled out: "ODP numbers lines per shipment."** ODP uses real PO line numbers: 45,995 ASN items land on line 1, 12,774 on line 2, 5,772 on line 3, continuing past line 12. On two-line POs it referenced line 2 correctly in 3,447 of 4,797 cases.
108
109
 
109
110
  ## Gotchas
111
+ - **Partly shipped kit + ASN overshot Qty Fulfilled (fixed 2026-10-27, TRUE-83312).** When one kit part does not ship (from 2026-10-26: `ODP-COMPASS-PCINSERT`), the upstream mirror's [bundle scaling](../../../2.0/apps/_underscore/features/recursive-item-fulfillments.md) writes a **fractional** NetSuite-side IFI on the Compass laptop line (5 of 6 parts = 0.83, `F`/`FF`-numbered IF). `remaining` = 1 − 0.83 = 0.17 > 0 sent the ASN down the create path, which POSTed an IFI for the **full** ASN qty (1), so `SalesOrderItem._qtyFulfilled` (sum of shipped-stage IFI qty) read **1.83**. One box was recorded twice. Fix: create-path IFI qty is capped at `remaining`. Whole-number cases are unchanged; an ASN for more than is left now stops at the ordered qty. Data: `dbchanges2/Client_Compass/2026-10-27a - AsnFulfillmentOvershoot.sql` lowers the SO-numbered ASN IFI to `soi.quantity − NetSuite IFI qty` on 42 prod lines (41 at 1.83, 1 at 1.50). **Diagnose:** a line with two IFIs, one on an SO-numbered IF and one on an `FF`-numbered IF, summing above the ordered qty.
110
112
  - **⚠ Supply's order tracking column never reads ASN header or item tracking.** `_Model_Compass_SalesOrder::_trackingNumbers` (~line 319) is a UNION of two paths only: (1) ASN **unit** tracking, `AdvanceShippingNoticeItemUnits_TrackingNumbers` → `PurchaseOrderItems` → `SalesOrderItems_PurchaseOrderItems` → this SO's `SalesOrderItems`; (2) header tracking on the Agilant SO's IF, through the full Compass → ODP → Agilant header chain. An ASN with header + item tracking but no `advanceShippingNoticeItemUnits` (the 3b CSV cron's ODP shape) shows no tracking when the Agilant leg is missing. The cXML ShipNotice shape (units with `advanceShippingNoticeItemUnitTrackingNumbers`) does show. The in-transit and delivered email crons use the same unit join.
111
113
  - **The same tracking number twice in one ASN payload fails the whole POST with MySQL 1062** (unique keys on `AdvanceShippingNotices_TrackingNumbers` / `AdvanceShippingNoticeItems_TrackingNumbers`); api2 rolls back the ASN. Dedupe tracking numbers per ASN before posting.
112
114
  - **⚠ OPEN BUG — an already-tracked, fully-fulfilled line makes `postPost` return 500 `EO-1` (2026-08-20).** When an ASN item resolves to a SalesOrderItem whose only `ItemFulfillmentItem` already carries item-level tracking, `remaining = 0` routes to reconcile, `itemFulfillmentItemHasTracking()` (added 2026-06-18) skips that IFI, and the handler then **fails** instead of completing cleanly — caller gets an operation error, not the intended no-op, and the ASN is lost. Deterministic: reproduced three times across both cXML and JSON transports (repro: PO `50305097-1` line 4; Sentry ids `AV-30`, `69-68`). Found on Compass Canada but lives in the shared Compass parent, so Office Depot re-shipments on already-tracked lines hit it too. Recurs whenever a vendor sends a second package for a line that already has tracking. Fix direction: the skip should complete the request successfully (nothing to attach), not fall through to failure.
@@ -131,6 +133,7 @@ Something was fixed around May/June 2026. Not fully fixed.
131
133
  - **⚠ Contract weakness to record — ASN identifies the PO item positionally.** The ASN payload identifies the PO item **positionally**, by `purchaseOrderItem.lineNumber`, NOT by uuid. So any line-numbering drift — on either side — silently credits the wrong item, with no error. Same positional-identity class as the PO-local `lineNumber` collision fixed in 2026-06, and it is why [Compass USA's per-line fulfillment rule was rejected](order-fulfillment-status-per-line.md): the feed cannot currently support a line-level completeness test.
132
134
 
133
135
  ## Change history
136
+ - 2026-10-27 — Create-path IFI qty capped at `remaining`, stopping the 1.83 overshoot on partly shipped kits (TRUE-83312). (bala)
134
137
  - 2026-10-15 — Linked cron 3a's last-line link bug as a third bad-bridge source; bulk repair now via retrofix2. (jcardinal)
135
138
  - 2026-10-13 — Recorded that supply's tracking column reads only ASN unit tracking (or the Agilant IF), the 1062 on a repeated tracking number in one payload, and that Presidio sends no ASNs. (bala)
136
139
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "toga-ai",
3
- "version": "1.0.1171",
3
+ "version": "1.0.1173",
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",