toga-ai 1.0.265 → 1.0.266

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.
@@ -13,7 +13,7 @@
13
13
  | [_Model magic-field access (__get without __isset)](features/model-magic-field-access.md) | `_Model` exposes DB columns as "magic" properties via `__get()`, but it defines **no** `__isset()`. | _underscore/Model/Core/Model.php |
14
14
  | [NetSuite REST Client (_Component_Api_Netsuite) — record writes & SuiteQL](features/netsuite-rest-client.md) | `_Component_Api_Netsuite` is the **2.0 `_underscore` NetSuite REST client** — the shared primitive every worker2/api2 NetSuite caller uses for record GETs, Suit | _underscore/Component/Api/Netsuite/Netsuite.php |
15
15
  | [Per-Client Database Connections & the Local Logs Trap](features/per-client-database-connections.md) | When `_underscore` serves a request for a client it opens **three distinct per-client database connections**, not one. | _underscore/Database.php, _underscore/ApiRequest.php, _underscore/Model/Client/Logs/Api.php |
16
- | [Recursive Item Fulfillments (upstream mirroring)](features/recursive-item-fulfillments.md) | In a multi-tier supply chain a sales order (SO) spawns a purchase order (PO) that becomes another SO downstream, and so on. | _underscore/Model/Client/ItemFulfillment.php, _underscore/Model/Client/ItemFulfillmentItem.php, _underscore/Model/Client/ItemFulfillmentItemUnit.php, _underscore/Model/Client/ItemFulfillmentPackage.php, _underscore/Model/Compass/AdvanceShippingNotice.php, dbchanges2/Core/2026-02-13 - 75601 - RecursiveItemFulfillmentCreation.sql, dbchanges2/Core/2026-06-04 - RecursiveItemFulfillmentPut.sql |
16
+ | [Recursive Item Fulfillments (upstream mirroring)](features/recursive-item-fulfillments.md) | In a multi-tier supply chain a sales order (SO) spawns a purchase order (PO) that becomes another SO downstream, and so on. | _underscore/Model/Client/ItemFulfillment.php, _underscore/Model/Client/ItemFulfillmentItem.php, _underscore/Model/Client/ItemFulfillmentItemUnit.php, _underscore/Model/Client/ItemFulfillmentPackage.php, _underscore/Model/Compass/AdvanceShippingNotice.php, dbchanges2/Core/2026-02-13 - 75601 - RecursiveItemFulfillmentCreation.sql, dbchanges2/Core/2026-06-04 - RecursiveItemFulfillmentPut.sql, dbchanges2/Client_Compass/2026-07-02a - FixSA133377TrackingSerialAndDuplicateIF.sql |
17
17
  | [Surface Resolver (_Model_Core_Surface::resolve — replaces Page::meta)](features/surface-resolver.md) | The runtime for the platform-wide **Surface** UI presentation layer: 9 `_underscore` models plus a cached resolver, `_Model_Core_Surface::resolve(&$api, string | _underscore/Model/Core/Surface.php, _underscore/Model/Client/AclRecordScript.php, _underscore/Model/Core/RecordScript.php, dbchanges2/Core/2026-06-30a - SurfaceMetaGroupAndSalesOrderSections.sql, dbchanges2/Client/2026-06-30a - SurfaceMetaGroupAcl.sql, dbchanges2/Client_Quad/2026-07-01a - GrantSurfacesMetaGroupScriptAcl.sql, dbchanges2/Client_CompassCanada/2026-07-01a - GrantSurfacesMetaGroupScriptAcl.sql, dbchanges2/Core/2026-06-29b - SurfaceMetaPublicReadAcl.sql, dbchanges2/Client/2026-06-29c - SurfaceRecordScriptAcl.sql, dbchanges2/Core/2026-06-29c - SurfaceDebugPhpMethodFix.sql, _underscore/Model/Core/SurfaceElement.php, _underscore/Model/Core/Action.php, _underscore/Model/Core/Vocabulary.php, _underscore/Model/Core/VocabularyTerm.php, _underscore/Model/Core/Message.php, _underscore/Model/Client/SurfaceOverride.php, _underscore/Model/Client/MessageTranslation.php, _underscore/Model/Client/ThemeToken.php, _underscore/Model/Core/Page.php |
18
18
  | [Tracking-Number Bridge Migration (ASN / Item Fulfillment / Item Receipt)](features/tracking-number-bridges.md) | Shipment tracking numbers used to live as **scalar FK columns** (`trackingNumberId`, `returnTrackingNumberId`) directly on the lowest-level "unit"/"item" tables | api2/Component/Api/V2/V2.php, _underscore/Model/Client/AdvanceShippingNoticeItemUnit.php, _underscore/Model/Client/AdvanceShippingNoticeItemUnits/TrackingNumber.php, _underscore/Model/Client/ItemFulfillmentItemUnits/TrackingNumber.php, _underscore/Model/Client/ItemFulfillment.php, _underscore/Model/Prudential/AdvanceShippingNotice.php, _underscore/Model/Compass/AdvanceShippingNotice.php, _underscore/Trait/Netsuite/ItemFulfillment.php, api2/Component/Api/Cxml/Cxml.php, dbchanges2/Client/2026-06-10 - TrackingNumberBridges.sql, dbchanges2/Core/2026-06-10 - TrackingNumberBridges.sql, dbchanges2/Client_Prudential/2026-06-15 - ItemFulfillmentTrackingNumberAclLogicGroups.sql, dbchanges2/Client_Quad/2026-06-18a - ItemFulfillmentItemReceiptTrackingNumberAclLogicGroups.sql, dbchanges2/Client_Nychh/2026-06-19b - ItemFulfillmentItemReceiptTrackingNumberAclLogicGroups.sql, dbchanges2/Client_Nychh/2026-06-19a - FixItemFulfillmentTableViewTrackingAndRoot.sql, dbchanges2/Client_Quad/2026-06-19a - FixItemFulfillmentTableViewTrackingAndRoot.sql |
19
19
  | [Units for Items for Purchase Orders — Data Structure](features/units-for-items-for-purchase-orders.md) | Describes how unit (serialized inventory) data is linked to sales-order and purchase-order line items behind the `units-for-items-for-purchase-orders` TableView | |
@@ -6,7 +6,7 @@ project: _Underscore
6
6
  client: shared
7
7
  type: feature
8
8
  status: active
9
- updated: 2026-06-19
9
+ updated: 2026-07-02
10
10
  owners: [jcardinal, rgirish]
11
11
  files:
12
12
  - _underscore/Model/Client/ItemFulfillment.php
@@ -16,6 +16,7 @@ files:
16
16
  - _underscore/Model/Compass/AdvanceShippingNotice.php
17
17
  - dbchanges2/Core/2026-02-13 - 75601 - RecursiveItemFulfillmentCreation.sql
18
18
  - dbchanges2/Core/2026-06-04 - RecursiveItemFulfillmentPut.sql
19
+ - dbchanges2/Client_Compass/2026-07-02a - FixSA133377TrackingSerialAndDuplicateIF.sql
19
20
  related:
20
21
  - ../architecture.md
21
22
  - ../../api2/architecture.md
@@ -102,12 +103,42 @@ inheritance. Verified against prod `Client_Compass` chains (≥3 levels deep).
102
103
  upstream mirror. A later reconciling PUT cleans up stale records via set comparison. Full
103
104
  delete handling would need a `preDelete` mechanism + `PRE/DELETE` interceptor rows.
104
105
  - **Transfer-order fulfillments are explicitly excluded from the upstream walk.** `reconcileUpstreamLevel()` returns `null` immediately when the downstream IF has `transferOrderId` set (no `salesOrderId`). This is intentional for standalone TOs (GroWrk v1). When SO→TO→SO chain scenarios are needed, Phase 5 of the GroWrk transfer order plan extends this method to walk `TransferOrders_SalesOrders` bridge tables — see `clients/growrk/features/transfer-order-flow.md`.
106
+ - **Broken SOI↔POI bridge → empty header-only duplicate IFs (root cause, guard added
107
+ 2026-07-02).** The item walk maps a downstream IFI up via
108
+ `IFI.salesOrderItemId → PurchaseOrderItems_SalesOrderItems → SalesOrderItems_PurchaseOrderItems`.
109
+ When that upstream `SalesOrderItems_PurchaseOrderItems` bridge is **missing/broken** (the same
110
+ integrity class as the ~116 off-by-one Compass orders — see the Compass
111
+ `asn-to-item-fulfillment` doc), `$desiredItems` resolves to **zero** even though the downstream
112
+ IF has items. Pre-guard, step 4's eager resolve-or-create then POSTed an **empty header-only
113
+ upstream IF** (e.g. Compass SA133377 got duplicate empty customer-facing IFs 71724/71725
114
+ alongside the legit direct-ASN IF 70635). **Guard:** `reconcileUpstreamLevel` now, after
115
+ building `$desiredItems`, if it is empty **AND** the downstream IF has >0 ItemFulfillmentItems,
116
+ `error_log()`s a diagnostic and returns `null` (skips the upstream mirror) — distinguished from
117
+ the legitimate header-first POST case (downstream IF genuinely has 0 items), which is unchanged.
118
+ - **Duplicate customer-facing IF: the engine only adopts an upstream IF it created itself, never a
119
+ pre-existing one (known gap, adoption DEFERRED).** `reconcileUpstreamLevel` finds an existing
120
+ upstream IF only via `upstreamItemFulfillmentId`; it does **not** adopt a pre-existing IF that
121
+ already sits on the upstream SO from another flow (e.g. a direct-ASN-created IF). So a broken
122
+ bridge could yield two customer-facing IFs on the same SO (SA133377: direct-ASN IF 70635 + a
123
+ mirror-created duplicate). **Naive adoption is dangerous and was intentionally not implemented:**
124
+ step 5's delete-stale step deletes upstream IFIs with no matching downstream group, so adopting
125
+ an IF while the item walk is empty/broken would **DELETE the adopted IF's legitimate line items**.
126
+ Requires a CTO design review before implementing adoption semantics. The 2026-07-02 broken-bridge
127
+ guard prevents the duplicate cascade for this failure mode without needing adoption.
105
128
  - **Out of scope:** NetSuite sync of upstream IFs; transfer-order fulfillments
106
129
  (`transferOrderId`) are skipped.
107
130
  - **Performance:** every child write re-runs a full upstream walk (read-heavy, but each
108
131
  upstream record is written at most once — idempotent). Fine for normal fulfillment sizes.
109
132
 
110
133
  ## Change history
134
+ - 2026-07-02 — Added a **broken-bridge guard** to `reconcileUpstreamLevel`: when `$desiredItems`
135
+ is empty but the downstream IF has items (broken SOI↔POI bridge), it logs and returns null
136
+ instead of creating an empty header-only duplicate upstream IF. Root-caused on Compass SA133377
137
+ (SO 107609), where a broken bridge + a pre-existing direct-ASN IF produced duplicate empty
138
+ customer-facing IFs (71724/71725) and left the correct serial/tracking off the customer IF;
139
+ data repaired by `dbchanges2/Client_Compass/2026-07-02a`. Companion fix — adopting a pre-existing
140
+ upstream IF instead of creating a duplicate — was **DEFERRED** pending CTO review (the delete-stale
141
+ step would delete the adopted IF's line items when the item walk is empty). (jcardinal)
111
142
  - 2026-06-19 — Documented explicit TO exclusion in reconcileUpstreamLevel; cross-linked GroWrk transfer order plan. (rgirish)
112
143
  - 2026-06-08 — Documented the Recursive Item Fulfillments engine (interceptor-driven upstream fulfillment mirroring, bundle scaling, reconcile loop). (jcardinal)
113
144
 
@@ -6,7 +6,7 @@ project: Database Changes
6
6
  client: shared
7
7
  type: architecture
8
8
  status: active
9
- updated: 2026-06-23
9
+ updated: 2026-07-02
10
10
  owners: [jcardinal, mhammontree]
11
11
  files:
12
12
  - Core/
@@ -198,6 +198,41 @@ WHERE
198
198
  - Keep the single statement well under `max_allowed_packet` (64 MB default) — a few thousand
199
199
  rows is comfortably fine.
200
200
 
201
+ ## Self-referencing DELETE — wrap the subquery in a derived table
202
+
203
+ MySQL forbids a `DELETE` (or `UPDATE`) whose `WHERE` subquery reads **the same table being
204
+ modified** — it fails with **error 1093: "You can't specify target table 'X' for update in
205
+ FROM clause."** This bites cleanup migrations that delete duplicate rows by selecting which
206
+ rows to keep from the very table being pruned.
207
+
208
+ The fix is to **materialize the subquery through an extra derived-table layer**, forcing MySQL
209
+ to snapshot the row set before the delete runs:
210
+
211
+ ```sql
212
+ -- WRONG — error 1093, ItemFulfillments is both the DELETE target and read in the subquery
213
+ DELETE FROM ItemFulfillments
214
+ WHERE id NOT IN (
215
+ SELECT MIN(id)
216
+ FROM ItemFulfillments
217
+ GROUP BY salesOrderItemId, trackingSerial
218
+ );
219
+
220
+ -- CORRECT — wrap the inner SELECT in a derived table so it is materialized first
221
+ DELETE FROM ItemFulfillments
222
+ WHERE id NOT IN (
223
+ SELECT keepId FROM (
224
+ SELECT MIN(id) AS keepId
225
+ FROM ItemFulfillments
226
+ GROUP BY salesOrderItemId, trackingSerial
227
+ ) AS keep
228
+ );
229
+ ```
230
+
231
+ The extra `SELECT … FROM ( … ) AS keep` wrapper is the whole idiom: MySQL evaluates the inner
232
+ query into a temporary derived table, so the outer `DELETE` no longer "sees" a live read of
233
+ its own target. Used in `2026-07-02a - FixSA133377TrackingSerialAndDuplicateIF.sql` to prune
234
+ duplicate `ItemFulfillments` rows.
235
+
201
236
  ## Relationship to the rest of 2.0
202
237
 
203
238
  `dbchanges2` is registered as a **2.0 core repo** (`role: core` in `registry.json`) — it is
@@ -2,7 +2,7 @@
2
2
 
3
3
  | Doc | Framework | Summary | Files |
4
4
  |-----|-----------|---------|-------|
5
- | [Compass ASN → ItemFulfillment Auto-Creation](features/asn-to-item-fulfillment.md) | 2.0 | For Compass USA, posting an AdvanceShippingNotice (ASN) auto-creates the ItemFulfillment (IF) on the upstream SalesOrder. | _underscore/Model/Compass/AdvanceShippingNotice.php, _underscore/Model/Compass/PurchaseOrder.php, api2/Component/Api/Cxml/Cxml.php, dbchanges2/Client_Compass/2026-06-11 - AsnItemTrackingNumberAcl.sql, dbchanges2/Client_Compass/2026-06-15b - BackfillSA132781ItemFulfillmentTracking.sql, dbchanges2/Client_Compass/2026-06-16 - CleanupSA132763CrossLineTracking.sql, dbchanges2/Client_Compass/2026-06-16b - CleanupSA132743CrossLineTracking.sql, dbchanges2/Client_Compass/2026-06-16c - BackfillSA132763C40QYUCTracking.sql, dbchanges2/Client_Compass/2026-06-18a - CleanupSA132898DuplicateTracking.sql, dbchanges2/Client_Compass/2026-06-18b - CleanupSA132881DuplicateTracking.sql |
5
+ | [Compass ASN → ItemFulfillment Auto-Creation](features/asn-to-item-fulfillment.md) | 2.0 | For Compass USA, posting an AdvanceShippingNotice (ASN) auto-creates the ItemFulfillment (IF) on the upstream SalesOrder. | _underscore/Model/Compass/AdvanceShippingNotice.php, _underscore/Model/Compass/PurchaseOrder.php, api2/Component/Api/Cxml/Cxml.php, dbchanges2/Client_Compass/2026-06-11 - AsnItemTrackingNumberAcl.sql, dbchanges2/Client_Compass/2026-06-15b - BackfillSA132781ItemFulfillmentTracking.sql, dbchanges2/Client_Compass/2026-06-16 - CleanupSA132763CrossLineTracking.sql, dbchanges2/Client_Compass/2026-06-16b - CleanupSA132743CrossLineTracking.sql, dbchanges2/Client_Compass/2026-06-16c - BackfillSA132763C40QYUCTracking.sql, dbchanges2/Client_Compass/2026-06-18a - CleanupSA132898DuplicateTracking.sql, dbchanges2/Client_Compass/2026-06-18b - CleanupSA132881DuplicateTracking.sql, dbchanges2/Client_Compass/2026-07-02a - FixSA133377TrackingSerialAndDuplicateIF.sql |
6
6
  | [Compass: Item-Fulfillment TableViews (for-sales-order-items & for-sales-orders, tracking via bridge)](features/item-fulfillment-tracking-tableview.md) | 2.0 | Two sibling Compass TableViews in `Client_Compass` display fulfilled items in toga2-supply, both driven by `TableViews` / `TableViewJoins` / `TableViewFields` c | dbchanges2/Client_Compass/2026-06-10 - ItemFulfillmentsForSalesOrderItemsTableView.sql, dbchanges2/Client_Compass/2026-06-11 - ItemFulfillmentsForSalesOrdersTableView.sql, dbchanges2/Client_Compass/2026-06-15a - FixItemFulfillmentTrackingNumberJoins.sql |
7
7
  | [Compass MITS PO → SO Item Linking](features/mits-po-to-so-item-linking.md) | 2.0 | MITS sends Compass inbound Purchase Orders (`POST /v2/purchase-orders`) against a Sales Order (`mitsSalesOrder`). | _underscore/Model/Compass/PurchaseOrder.php, worker/crons/toga2/compass/workflow/3a_import_office_depot_purchase_orders.php |
8
8
  | [Compass MITS PO Transmission to Vendors](features/mits-po-transmission-to-vendors.md) | 2.0 | The 1.0 worker cron `2_transmit_mits_purchase_orders_to_vendors.php` transmits Compass PurchaseOrders to their vendors (Office Depot, Strategic Systems, Compass | worker/crons/toga2/compass/workflow/2_transmit_mits_purchase_orders_to_vendors.php, library/app/client/compass.php |
@@ -5,7 +5,7 @@ project: _Underscore
5
5
  client: compass-usa
6
6
  type: client-feature
7
7
  status: active
8
- updated: 2026-06-30
8
+ updated: 2026-07-02
9
9
  owners: [jcardinal, bala]
10
10
  files:
11
11
  - _underscore/Model/Compass/AdvanceShippingNotice.php
@@ -18,6 +18,7 @@ files:
18
18
  - dbchanges2/Client_Compass/2026-06-16c - BackfillSA132763C40QYUCTracking.sql
19
19
  - dbchanges2/Client_Compass/2026-06-18a - CleanupSA132898DuplicateTracking.sql
20
20
  - dbchanges2/Client_Compass/2026-06-18b - CleanupSA132881DuplicateTracking.sql
21
+ - dbchanges2/Client_Compass/2026-07-02a - FixSA133377TrackingSerialAndDuplicateIF.sql
21
22
  related:
22
23
  - ../../../2.0/apps/_underscore/features/recursive-item-fulfillments.md
23
24
  - ../../../2.0/apps/_underscore/features/item-fulfillment-stage-lifecycle-and-order-status.md
@@ -207,6 +208,18 @@ Compass Canada (`Model/Compass/Canada/`) is a separate sub-client. The Compass c
207
208
 
208
209
  ## Change history
209
210
  Dated one-liners, newest first.
211
+ - 2026-07-02 — Repaired SA133377 (SO 107609, line 1 MD7F4LL/A-S): the customer IF (IFI 276821)
212
+ had no serial and the wrong tracking (`522944658493` — the first of two tracking-only vendor
213
+ ASNs on PO 103641 that reconciled onto the direct-ASN IF 70635) instead of `873840444805`
214
+ (the real device unit 63108 / serial SKRX9Y4W537 arrived up the mirror chain from downstream
215
+ IF 71723). Migration `2026-07-02a` adds the missing IFIU + correct unit-level tracking, swaps
216
+ the stale header/item tracking, and retires the empty duplicate IF 71725 (idempotent, sql-reviewer
217
+ SAFE). Root cause was a broken `SalesOrderItems_PurchaseOrderItems` bridge on the downstream
218
+ shipped SO item (PO item 179306, no upstream bridge — same off-by-one integrity class as the
219
+ ~116 orders above) feeding the recursive mirror engine, which then created empty duplicate
220
+ customer-facing IFs; forward engine guard added in the
221
+ [Recursive Item Fulfillments](../../../2.0/apps/_underscore/features/recursive-item-fulfillments.md)
222
+ doc (2026-07-02). (jcardinal)
210
223
  - 2026-06-30 — `resolveOrCreateItemFulfillment` now resolves the shipped `ItemFulfillmentStage` by
211
224
  status slug and sets `itemFulfillmentStage` on the IF payload, so ASN-created fulfillments land
212
225
  shipped (not stage-less) on both Compass USA + Canada. Part of the platform-wide IF stage
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "toga-ai",
3
- "version": "1.0.265",
3
+ "version": "1.0.266",
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",