toga-ai 1.0.136 → 1.0.137

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.
@@ -8,5 +8,5 @@
8
8
  | [Elite Freshservice Sync (worker2)](features/elite-freshservice-sync.md) | `_Worker_Elite` processes Freshservice webhook events and syncs them into TOGA 2. | worker2/Worker/Elite.php, worker2/Config/dev-kmaramreddy-laptop.ini |
9
9
  | [Monitoring Framework (Orchestrator + Child Monitors)](features/monitoring-framework.md) | A unified, DB-driven monitoring framework for business-critical data flows (Compass POs, Prudential asset imports, AIG closed claims, …). | worker2/Worker/Monitor.php, worker2/Worker/Monitors/, worker2/Worker/Notification/Email.php, dbchanges2/Core/2026-05-21 - Monitors.sql |
10
10
  | [NetSuite → TOGA Opportunity Sync (API Message Queue + worker2 webhook)](features/netsuite-opportunity-sync.md) | Outbound sync from NetSuite to TOGA for the record types the Forecast2 importer pulls (opportunities first; sales/items/etc. | worker2/Worker/Netsuite.php, worker2/Worker/Netsuite/Opportunity.php, worker2/Controller/Index.php, _underscore/Worker.php, test/@dave/NetSuite/api-message-queue/lib_amq_queue.js, test/@dave/NetSuite/api-message-queue/ue_api_msg_queue_enqueue.js, test/@dave/NetSuite/api-message-queue/ue_amq_drain.js, test/@dave/NetSuite/api-message-queue/ss_amq_drain.js, test/@dave/NetSuite/api-message-queue/DEPLOY_RUNBOOK.md, test/@dave/clickup/backfill_opportunity_numbers.php, test/@dave/clickup/probe_opportunity_fields.php, test/@dave/probe_clickup_desc_match.php, worker/crons/toga2/forecast2/common_import_sales_from_netsuite.php |
11
- | [NetSuite → Forecast Open-Orders Sync (salesOrder webhook → OpenOrderItems)](features/netsuite-salesorder-open-orders-sync.md) | Webhook-driven, single-record port of the legacy open-orders importer (TRUE-79142). | worker2/Worker/Netsuite/SalesOrder.php, worker2/Worker/Netsuite.php, test/@dave/probe_salesorder_rest_shape.php, worker/crons/toga2/forecast2/import_open_orders.php, worker/crons/toga2/forecast2/common_import_sales_from_netsuite.php |
11
+ | [NetSuite → Forecast Open-Orders Sync (salesOrder webhook → OpenOrderItems)](features/netsuite-salesorder-open-orders-sync.md) | Webhook-driven, single-record port of the legacy open-orders importer (TRUE-79142). | worker2/Worker/Netsuite/SalesOrder.php, worker2/Worker/Netsuite.php, test/@dave/probe_salesorder_rest_shape.php, test/@dave/probe_open_order_lines.php, test/@dave/check_so_status.php, test/@dave/check_so_history.php, test/@dave/probe_so_rest_lines.php, worker/crons/toga2/forecast2/import_open_orders.php, worker/crons/toga2/forecast2/common_import_sales_from_netsuite.php |
12
12
  | [Teams Meeting Transcript Export](features/teams-transcript-export.md) | `_Worker_Team_Transcripts` (action `Team/Transcripts/Export`) polls Microsoft Graph for Teams meeting transcripts produced by a set of organizers, classifies ea | worker2/Worker/Team/Transcripts.php, worker2/Config/production.ini |
@@ -6,12 +6,16 @@ project: Worker
6
6
  client: shared
7
7
  type: feature
8
8
  status: active
9
- updated: 2026-06-17
9
+ updated: 2026-06-18
10
10
  owners: ["dfranks"]
11
11
  files:
12
12
  - worker2/Worker/Netsuite/SalesOrder.php
13
13
  - worker2/Worker/Netsuite.php
14
14
  - test/@dave/probe_salesorder_rest_shape.php
15
+ - test/@dave/probe_open_order_lines.php
16
+ - test/@dave/check_so_status.php
17
+ - test/@dave/check_so_history.php
18
+ - test/@dave/probe_so_rest_lines.php
15
19
  - worker/crons/toga2/forecast2/import_open_orders.php
16
20
  - worker/crons/toga2/forecast2/common_import_sales_from_netsuite.php
17
21
  related:
@@ -77,6 +81,25 @@ None — uniform (platform-wide Forecast2 sync).
77
81
 
78
82
  ## Gotchas / known issues
79
83
 
84
+ - **Async-recalc race → a transiently-empty REST read PURGES a genuinely-open order (silent data loss).**
85
+ The open-status gate is not the only way rows get deleted: on an **open** order, if the REST GET
86
+ momentarily returns **zero importable open lines**, `syncOpenLines` runs its reconcile-delete with an
87
+ empty kept-set and wipes every existing row — there is no separate "removeAll", but the effect is the
88
+ same. This fires when NetSuite's own **asynchronous `-System-` line recompute** (e.g. the open
89
+ gross-profit pass, `CUSTCOL_OPEN_GP`) is still in flight at the instant the webhook's REST GET runs —
90
+ the record reads back open but with its open lines not yet materialized. The webhook is often *triggered
91
+ by that very recompute*, so the GET races it. **Confirmed live 2026-06-18 on SO 7151688** (tranId
92
+ 279345): 4 edit webhooks at 14:02:35–48 CDT all succeeded with **0 rows**, while NetSuite's `-System-`
93
+ open-GP recompute wrote the line values at the same 14:02:30–45 window (NS Eastern = worker Central +1h).
94
+ A manual re-sync 22 min later imported all 4 lines correctly. Nothing self-heals it — there is no retry,
95
+ and the batch cron is being decommissioned. **Fix direction (not yet implemented):** never let "open
96
+ status + zero importable lines" trigger a destructive delete — re-fetch after a short delay, or treat it
97
+ as retryable (`throw` → `isSuccess=0` → re-runs) — and only let a **confidently-closed** status
98
+ (Billed / Closed / Cancelled) purge. Diagnose with `check_so_history.php` (systemnote status timeline)
99
+ + `check_so_status.php` (current REST status) + `probe_so_rest_lines.php` (REST `item.items` as the
100
+ handler sees them). NB: `App_ApiTransaction::execute(false)` returns a raw JSON **string** for a record
101
+ GET — `json_decode` it directly; double-encoding it (`json_decode(json_encode($resp))`) silently yields
102
+ an empty object and a false "empty record" reading.
80
103
  - **REST shape ≠ SOAP shape.** The cron reads the SOAP-shim shape; this handler reads the REST record
81
104
  (`status->refName`, line `quantityBilled`, `class->refName`, `entity->id`, `salesRep->id`,
82
105
  `shippingCost`). Verified against live orders via `test/@dave/probe_salesorder_rest_shape.php`.
@@ -131,6 +154,15 @@ and are a candidate to extract into a shared `_Component_Forecast_Db` before the
131
154
 
132
155
  ## Change history
133
156
 
157
+ - 2026-06-18 — **Verified live in production** after the SalesOrder enqueuer left Testing. 14 orders
158
+ reconciled against NetSuite SuiteQL (`probe_open_order_lines.php`); 13 at $0.00 delta. Found one
159
+ miss — SO 7151688 (open, ~$20.31, 0 rows) — traced to the **async-recalc race** above (see new
160
+ gotcha): the order was approved (Pending Approval → Pending Fulfillment) *before* the enqueuer was
161
+ released (so the approval itself never enqueued — one-time cutover artifact), then NetSuite's
162
+ `-System-` open-GP recompute fired the edit webhooks and the handler's REST GET raced that recompute,
163
+ reading zero open lines → reconcile-delete. A manual re-sync corrected it. Added diagnostics
164
+ `check_so_status.php`, `check_so_history.php`, `probe_so_rest_lines.php`. No code change yet — fix
165
+ direction recorded in the gotcha. (dfranks)
134
166
  - 2026-06-17 — **Fixed dropped zero-revenue/open-cost lines.** REST omits `rate` for $0 lines, and the
135
167
  `if (!is_numeric($rate)) continue;` guard was skipping them — but the cron inserts these (profit =
136
168
  −qtyOpen × unitCost; 575 such rows in prod). Now defaults missing/non-numeric rate to 0 so the both-zero
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "toga-ai",
3
- "version": "1.0.136",
3
+ "version": "1.0.137",
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",