toga-ai 1.0.180 → 1.0.182

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.
@@ -5,7 +5,7 @@
5
5
  | [Worker (1.0 Framework) Architecture](architecture.md) | `worker` is the legacy (**1.0** `App_` framework) **background-job tier**. | worker/index.php, worker/_/app/framework.php, worker/crons/, worker/schedules/, worker/ebs/cron.worker.php, worker/.ebextensions/035_cron.worker.config |
6
6
  | [Compass MA Sales Order Exception Report](features/compass-ma-sales-order-exception-report.md) | A worker cron that emails operations the "Compass Refresh Exception Report" — Compass `MA%` sales orders whose corresponding Office Depot (ODP) sales order has | worker/crons/toga2/compass/workflow/7_generate_ma_sales_order_exception_report.php |
7
7
  | [Compass Partial In-Transit & Delivered Emails (per package)](features/compass-partial-in-transit-delivered-emails.md) | Compass USA and Compass Canada send a **per-package** in-transit email (and a matching delivered email) instead of one email listing the whole order. | worker/crons/toga2/compass/update_salesorder_status_from_odp.php, worker/crons/toga2/compasscanada/workflow/3_update_salesorder_status_from_grand_and_toy.php |
8
- | [Forecast2 ↔ NetSuite Reconciliation & Trueup Tooling](features/forecast2-netsuite-reconciliation.md) | CLI tools to **audit** and **repair** drift between the production `Forecast` DB (core2) and NetSuite. | test/@dave/reconcile_netsuite_totals.php, test/@dave/analyze_netsuite_forecast_diff.php, test/@dave/trueup_sales.php, test/@dave/trueup_open_orders.php, test/@dave/trueup_opportunities.php, test/@dave/probe_sales_gap_direct.php, test/@dave/probe_missing_oo_timing.php, test/@dave/probe_missing_oo_createdby.php, worker/crons/toga2/forecast2/common_import_sales_from_netsuite.php |
8
+ | [Forecast2 ↔ NetSuite Reconciliation & Trueup Tooling](features/forecast2-netsuite-reconciliation.md) | CLI tools to **audit** and **repair** drift between the production `Forecast` DB (core2) and NetSuite. | test/@dave/reconcile_netsuite_totals.php, test/@dave/analyze_netsuite_forecast_diff.php, test/@dave/trueup_sales.php, test/@dave/trueup_open_orders.php, test/@dave/trueup_opportunities.php, test/@dave/probe_sales_gap_direct.php, test/@dave/probe_missing_oo_timing.php, test/@dave/probe_missing_oo_createdby.php, test/@dave/probe_drift_so_dates.php, test/@dave/probe_profit_invoices.php, test/@dave/probe_profit_gap.php, worker/crons/toga2/forecast2/common_import_sales_from_netsuite.php |
9
9
  | [NetSuite → TOGa Supply Per-Client Sync (thin wrappers)](features/netsuite-togasupply-per-client-sync.md) | Syncs NetSuite transactions (sales orders, purchase orders, invoices, item receipts, item fulfillments, inventory adjustments) into each TOGa Supply (2.0) clien | worker/crons/toga2/netsuite/common_sync_togasupply.php, worker/crons/toga2/netsuite/sync_togasupply_canon.php, worker/schedules/cron.worker.sync.json, dbchanges2/_modules/netsuite/2026-04-01 - Parameters.sql |
10
10
  | [Prudential: Send Shipments for the Day report (daily cron)](features/send-shipments-for-the-day.md) | Daily cron (9:00 PM) that emails Prudential and Dell stakeholders an Excel report of all devices shipped that day, including tracking number, serial number, emp | worker/crons/notifications/reports/send_shipments_for_the_day.php |
11
11
  | [Onboarding a Client to the NetSuite TOGa Supply Sync](workflows/onboarding-client-to-netsuite-togasupply-sync.md) | How to add a new TOGa 2 client to the per-client NetSuite → TOGa Supply importer (`worker/crons/toga2/netsuite/`). | worker/crons/toga2/netsuite/sync_togasupply.php, worker/crons/toga2/netsuite/common_sync_togasupply.php, worker/schedules/cron.worker.sync.json, dbchanges2/_modules/netsuite/2026-04-01 - Parameters.sql |
@@ -6,7 +6,7 @@ project: Worker
6
6
  client: shared
7
7
  type: feature
8
8
  status: active
9
- updated: 2026-06-22
9
+ updated: 2026-06-24
10
10
  owners: [dfranks]
11
11
  files:
12
12
  - test/@dave/reconcile_netsuite_totals.php
@@ -17,6 +17,9 @@ files:
17
17
  - test/@dave/probe_sales_gap_direct.php
18
18
  - test/@dave/probe_missing_oo_timing.php
19
19
  - test/@dave/probe_missing_oo_createdby.php
20
+ - test/@dave/probe_drift_so_dates.php
21
+ - test/@dave/probe_profit_invoices.php
22
+ - test/@dave/probe_profit_gap.php
20
23
  - worker/crons/toga2/forecast2/common_import_sales_from_netsuite.php
21
24
  related:
22
25
  - ../architecture.md
@@ -34,9 +37,14 @@ by reconciling a chosen tranDate range directly against NetSuite.
34
37
 
35
38
  ## Key files / entry points
36
39
 
37
- - `reconcile_netsuite_totals.php [from] [to]` — category grand totals NS vs Forecast2 (Sales,
38
- Sales Profit, Open Orders, Opportunities) with deltas. Read-only. Connects explicitly to the
39
- **prod core2 reader**; NetSuite via SuiteQL SUMs.
40
+ - `reconcile_netsuite_totals.php [from] [to] [--verbose]` — category grand totals NS vs Forecast2
41
+ (Sales, Sales Profit, Open Orders, Opportunities) with deltas. Read-only. Connects explicitly to
42
+ the **prod core2 reader**; NetSuite via SuiteQL SUMs. `--verbose` additionally prints the
43
+ per-transaction presence diffs (Sales `salesPerTransactionDiff()` + Open Orders
44
+ `openOrderPerOrderDiff()`), each under a large `#`-banner heading with a date column in every
45
+ bucket. The diffs are **gated** because they materialize every transaction/order in the window
46
+ into PHP arrays and OOM on multi-year windows — default is summary-only; positional `[from] [to]`
47
+ work alongside the flag.
40
48
  - `analyze_netsuite_forecast_diff.php [from] [to]` — decomposes the delta **per transaction** into
41
49
  NS_ONLY (missing from FC), FC_ONLY (stale/extra), DRIFT (value differs). Read-only.
42
50
  - `trueup_sales.php --from --to [--chunk-days N] [--prod] [--dry-run]` — makes `Forecast.Sales`
@@ -48,6 +56,11 @@ by reconciling a chosen tranDate range directly against NetSuite.
48
56
  ~4,000 opps in ~13s to a $0 delta.
49
57
  - `probe_sales_gap_direct.php` / `probe_open_orders_gap.php` — read-only NS-vs-FC2 revenue
50
58
  breakdowns by type+month using **direct prod mysqli** (demonstrate the sandbox-dev bypass below).
59
+ - `probe_profit_gap.php` — read-only: attributes a Sales **profit** delta **by transaction type**
60
+ and measures the actual contribution of the `tl.iscogs='F'` filter (see the COGS gotcha below).
61
+ - `probe_profit_invoices.php` — read-only per-invoice profit localizer: lists drifted invoices
62
+ (`id` / `tranid` / `tranDate` / NS profit / FC profit / delta) over a tranDate window. Bootstraps
63
+ worker2's `_Component_Api_Netsuite` adapter + a direct prod core2 `Forecast` reader.
51
64
 
52
65
  ## How it works
53
66
 
@@ -67,6 +80,13 @@ by reconciling a chosen tranDate range directly against NetSuite.
67
80
  - **Chunking + keepalive.** trueup_sales chunks by `--chunk-days` (default 7) and commits per
68
81
  chunk so a crash resumes. Both ping/reconnect `db_forecast2` before DB work (Aurora drops idle
69
82
  links during long NS calls).
83
+ - **`probe_drift_so_dates.php` (read-only drift classifier).** For a list of NS SO internalIds it
84
+ reports, per order: NS `trandate` / `lastmodifieddate` (ET) / status / total; NS current open
85
+ product-line count + open $; and the matching `Forecast.OpenOrderItems` row count + $ (direct mysqli
86
+ to the core2 `Forecast` DB, column `netsuiteSalesOrderInternalId`). Bootstraps the 2.0
87
+ `_Component_Api_Netsuite` adapter via the worker2 chdir+require `index.php` pattern. Use it to
88
+ **classify a drift set as whole-missing vs. present-but-different**, and to scope a
89
+ `Logs.Webhook`/`WorkerJobs` search window from the lastmodified timestamps.
70
90
 
71
91
  ## Data model
72
92
 
@@ -80,6 +100,36 @@ None — Forecast2 is a single shared dataset.
80
100
 
81
101
  ## Gotchas / known issues
82
102
 
103
+ - **An audit must apply the sync's own inclusion rules or it manufactures phantom deltas.**
104
+ `reconcile`'s NetSuite Sales SUM/diff originally had **no transaction-status filter**, but the
105
+ sync (`trueup_sales` / `common_import_sales_from_netsuite.php`) deliberately **excludes** Invoice
106
+ *Voided*/*Rejected*, CashSale *"Unapproved Payment"*, and CreditMemo *Voided*. So the audit counted
107
+ rows the sync would never store → a **permanent phantom gap no true-up could ever close** (a single
108
+ Unapproved-Payment cash sale, $23.76, internalId 7184894, drove it). Fix: add a status filter
109
+ matching the sync to **both** Sales SUM queries and the per-transaction diff —
110
+ `BUILTIN.DF(t.status) NOT LIKE '%Unapproved Payment' / '%Voided' / '%Rejected'` (the display value
111
+ ends with the status name). Closed the revenue delta to `$0`. Generalize: when an audit reports a
112
+ delta no trueup closes, first check the audit applies the sync's exact inclusion filters.
113
+ - **Don't blame a profit delta on COGS lines without measuring it.** Adding `tl.iscogs='F'` to the
114
+ audit's profit SUM (to match the sync's line set) changed the profit number by **exactly $0.00** in
115
+ the tested window — COGS postings were **not** the cause of the profit gap. The filter is kept for
116
+ definitional consistency with the sync (which filters `iscogs='F'`), but it was not the fix.
117
+ `probe_profit_gap.php` measures the iscogs contribution directly so the next investigator doesn't
118
+ repeat the wrong hypothesis.
119
+ - **A Sales PROFIT-only gap (revenue matches, profit off) is a LINE-STRUCTURE mismatch, not value
120
+ drift — localize then window.** Production `Forecast.Sales` carries stale lines NetSuite no longer
121
+ returns *plus* is missing some, so the per-invoice profit SUM differs while revenue ties out.
122
+ `trueup_sales` surfaces this as **inserted>0 + deleted>0 with updated=0** (it reconciles by
123
+ `(type, txnId, lineNumber)`; a changed lineNumber is delete+insert, not update). **Key:**
124
+ `trueup_sales` *does* correct profit, but **only within its `--from/--to` tranDate window** — the
125
+ delta persists if the drifted transactions fall outside the window trued up. Diagnostic ladder:
126
+ (a) `reconcile` summary shows a profit delta with revenue at `$0`; (b) `probe_profit_gap.php`
127
+ attributes the delta to a transaction **type** (was 100% invoices); (c) `probe_profit_invoices.php`
128
+ lists the specific drifted invoices with tranDate; (d) run `trueup_sales --from/--to` over **that
129
+ exact window** with `--prod`. Resolved example: 23 invoices dated 2026-05-07/08 carried $1,277.37;
130
+ `trueup_sales --prod` over 2026-05-07..2026-05-08 applied 12 inserts + 35 deletes → `$0`. ⚠
131
+ `--prod` must be passed or trueup writes to the **laptop sandbox `db_forecast2`** (and that repoint
132
+ also drives which `Forecast.Items` lookup loads).
83
133
  - **NVL the cost in NS profit SUMs.** `reconcile`/`analyze` compute NS profit as
84
134
  `SUM(-foreignamount + NVL(costestimate,0))`. Without `NVL`, ShipItem lines (NULL cost) drop from
85
135
  the SUM and NS profit is understated, producing a **fake FC-over-NS "gap"** ($663K across 2025).
@@ -139,6 +189,18 @@ None — Forecast2 is a single shared dataset.
139
189
  `probe_missing_oo_createdby.php` (creator/context — integration/Web-Services-origin orders cluster
140
190
  here) + a `Core.WorkerJobs` lookup by `JSON_EXTRACT(parameters,'$.internalId')` (no row → bucket A;
141
191
  rows all `isSuccess=1` yet zero OOI rows → bucket B).
192
+ - **Open-orders drift is THREE distinct categories — diagnose per category, don't assume.**
193
+ Drift between NS open SOs and `Forecast.OpenOrderItems` maps to `trueup_open_orders.php`'s output
194
+ buckets:
195
+ - **(a) WHOLE-ORDER-MISSING** — NS has open lines, FC has **0 rows** → the **"Insert"** bucket.
196
+ **Dominant by dollars** (a single stale partially-fulfilled order can be **>$1M**).
197
+ - **(b) LINE-LEVEL value drift** — order present in FC but a line's revenue/qty changed → the
198
+ **"Update"** bucket. **Largest by count** (~1,612 in a mid-2026 run).
199
+ - **(c) STALE FC ROWS** — FC holds lines NS no longer returns as open → the **"Delete"/stale-cleanup**
200
+ bucket. **Smallest** (~13–19).
201
+ The common "drift = dropped line items still in production" assumption is category **(c) — the
202
+ SMALLEST**; the dominant/highest-$ problem is **whole-order-missing (a)**. Classify the set first
203
+ (`probe_drift_so_dates.php`) and diagnose per category.
142
204
  - **Legacy credit-memo wrong-sign rows.** Some `Forecast.Sales` credit-memo/cash-refund rows were
143
205
  stored with **flipped signs** by a prior sync version (positive instead of negative), inflating
144
206
  revenue. `trueup_sales.php` repairs them: it compares against NS by trandate, detects the sign
@@ -149,6 +211,20 @@ None — Forecast2 is a single shared dataset.
149
211
 
150
212
  ## Change history
151
213
 
214
+ - 2026-06-24 — **Sales audit correctness pass.** Fixed a phantom Sales delta: `reconcile`'s NS Sales
215
+ SUM/diff now applies the sync's status exclusions (Unapproved Payment / Voided / Rejected) so it can't
216
+ count rows the sync never stores (closed revenue delta to $0). Added a Sales per-transaction presence
217
+ diff and gated both diffs behind `--verbose` (they OOM on wide windows). Documented that a profit-only
218
+ gap is a line-structure mismatch fixed by `trueup_sales` **only within its tranDate window**
219
+ (localize-then-window ladder), and that `tl.iscogs='F'` contributed exactly $0.00 — COGS was not the
220
+ cause. Added `probe_profit_invoices.php` + `probe_profit_gap.php`. (dfranks)
221
+ - 2026-06-23 — **Framed open-orders drift as three distinct categories** mapping to
222
+ `trueup_open_orders`'s Insert/Update/Delete buckets — whole-order-missing (dominant by $),
223
+ line-level value drift (largest by count), and stale FC rows (smallest) — correcting the common
224
+ "dropped line items" assumption (which is the smallest category). Added the read-only drift
225
+ classifier `probe_drift_so_dates.php` (NS dates/status/open-$ vs `Forecast.OpenOrderItems` count/$
226
+ via direct core2 mysqli; bootstraps the 2.0 `_Component_Api_Netsuite` adapter) to classify a drift
227
+ set and scope a Logs.Webhook/WorkerJobs search window. (dfranks)
152
228
  - 2026-06-22 — **`reconcile` Open Orders made apples-to-apples.** Headline row now compares
153
229
  product-only on both sides (shipping is NS-only, prints as a separate `(NS-only)` info row) — kills
154
230
  a phantom shipping-only "delta". Documented the per-order presence diff as the real sync-health
@@ -10,7 +10,7 @@
10
10
  | [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 |
11
11
  | [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 |
12
12
  | [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 |
13
- | [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, test/@dave/probe_missing_oo_timing.php, test/@dave/probe_missing_oo_createdby.php, worker/crons/toga2/forecast2/import_open_orders.php, worker/crons/toga2/forecast2/common_import_sales_from_netsuite.php |
13
+ | [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, test/@dave/probe_missing_oo_timing.php, test/@dave/probe_missing_oo_createdby.php, test/@dave/probe_drift_so_dates.php, worker/crons/toga2/forecast2/import_open_orders.php, worker/crons/toga2/forecast2/common_import_sales_from_netsuite.php |
14
14
  | [Startech Webhook Handler (worker2)](features/startech-webhook-handler.md) | Receives inbound webhook events from Startech (Easeedesk) and creates or updates the corresponding ticket in TOGA 2.0. | worker2/Worker/Startech.php |
15
15
  | [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, worker2/Database/TeamsTranscriptExports.sql, dbchanges2/Core/2026-06-18a - Teams Transcript Export schedule.sql |
16
16
  | [WJE Freshservice Sync (worker2)](features/wje-freshservice-sync.md) | WJE ("WJE IT", helpdesk `wje.freshservice.com`) is a **Freshservice**-based help-desk client whose tickets, contacts, assets, groups, categories, and canned res | worker2/Worker/Wje.php, _underscore/Component/Api/Wje/Wje.php, _underscore/Model/Wje/Ticket.php, _underscore/Model/Wje/TicketNote.php, _underscore/Model/Wje/Contact.php, _underscore/Model/Wje/Unit.php, _underscore/Model/Wje/TicketTeam.php, _underscore/Model/Wje/TicketCategory.php, _underscore/Model/Wje/AssetType.php, _underscore/Model/Wje/PredefinedReply.php, library/app/api/wje.php, worker/crons/toga2/wje/import_supporting_records.php, worker/crons/toga2/wje/sync_togasupply_wje.php, worker/crons/notifications/reports/wje/wje_common.php, library/app/systemmonitor/wje.php, dbchanges2/Client_Wje/2024-10-04 - WjeOnboarding.sql |
@@ -6,7 +6,7 @@ project: Worker
6
6
  client: shared
7
7
  type: feature
8
8
  status: active
9
- updated: 2026-06-22
9
+ updated: 2026-06-23
10
10
  owners: ["dfranks"]
11
11
  files:
12
12
  - worker2/Worker/Netsuite/SalesOrder.php
@@ -18,6 +18,7 @@ files:
18
18
  - test/@dave/probe_so_rest_lines.php
19
19
  - test/@dave/probe_missing_oo_timing.php
20
20
  - test/@dave/probe_missing_oo_createdby.php
21
+ - test/@dave/probe_drift_so_dates.php
21
22
  - worker/crons/toga2/forecast2/import_open_orders.php
22
23
  - worker/crons/toga2/forecast2/common_import_sales_from_netsuite.php
23
24
  related:
@@ -77,12 +78,35 @@ Unique key `(netsuiteSalesOrderInternalId, lineNumber)`. Columns written: `netsu
77
78
  `classificationId`, `itemId` (nullable), `lineNumber`, `revenue`, `profit`. The `accountId` FK column
78
79
  exists but is **never populated** (the legacy writer never set it — parity).
79
80
 
81
+ The table lives in the **`Forecast` schema on the core2 cluster** (reader
82
+ `reader1.core.database.togahub.com` / writer `writer.core.database.togahub.com`), ~4,347 rows. The
83
+ sales-order FK column is **`netsuiteSalesOrderInternalId`** (`int unsigned`) — note this is *not* the
84
+ same column name used by `Forecast.Sales` (see the column-name gotcha below).
85
+
80
86
  ## Client variations
81
87
 
82
88
  None — uniform (platform-wide Forecast2 sync).
83
89
 
84
90
  ## Gotchas / known issues
85
91
 
92
+ - **Wrong FK column name → MySQL 1054 that masquerades as "order missing".** `OpenOrderItems`'s
93
+ sales-order key is **`netsuiteSalesOrderInternalId`**. Do **NOT** query it with
94
+ `netsuiteTransactionInternalId` — that is the **`Forecast.Sales`** column. On `OpenOrderItems` it
95
+ raises **MySQL ERROR 1054 "Unknown column"**, which (if the query error is swallowed) degrades into a
96
+ silent **false-negative "order missing"** reading. Verify the schema before concluding an order is
97
+ absent from Forecast.
98
+ - **A successful `WorkerJobs` row does NOT prove rows were written — two silent zero-write paths.** An
99
+ `isSuccess=1` / `failureReason = NULL` job is **not** evidence that any `OpenOrderItems` rows persisted.
100
+ `importOpenOrder` has two success-with-zero-write paths:
101
+ - **(a) STATUS GATE** — if NetSuite `status->refName` is not in `OPEN_STATUSES` (`Pending Fulfillment`,
102
+ `Partially Fulfilled`, `Pending Billing/Partially Fulfilled`, `Pending Billing`) or is missing, it
103
+ calls `removeAll()` (deletes all FC rows) and returns a success string.
104
+ - **(b) ALL-LINES-FILTERED** — every line is skipped (item group / excluded EWR item id /
105
+ `revenue == 0 && profit == 0`), leaving `lineRows` empty, so `syncOpenLines` takes its delete-all
106
+ branch and returns `"synced (0 open lines)"`.
107
+ Only an **unknown item** throws (`isSuccess=0`). The **only** thing that detects silent zero-rows is a
108
+ **post-write verification** (count persisted vs. intended); a handler lacking that verification cannot
109
+ self-detect this state.
86
110
  - **Async-recalc race → a transiently-empty REST read PURGES a genuinely-open order (silent data loss).**
87
111
  The open-status gate is not the only way rows get deleted: on an **open** order, if the REST GET
88
112
  momentarily returns **zero importable open lines**, `syncOpenLines` runs its reconcile-delete with an
@@ -142,6 +166,17 @@ None — uniform (platform-wide Forecast2 sync).
142
166
  up after `MAX_IMPORT_ATTEMPTS`). If every `WorkerJobs` row for the id has a **NULL `attempt`** and
143
167
  there are **no `OPEN_ORDER_IMPORT` breadcrumbs** for it, the self-heal/defer path isn't executing
144
168
  for that order — so a bucket-B miss has no retry and freezes at zero rows.
169
+ **Full 3-table diagnostic ladder for a single SO (all read-only):**
170
+ 1. **`Logs.Webhook`** (logs cluster `reader1.logs.database.togahub.com`, schema `Logs`) — confirms
171
+ NetSuite's AMQ push actually **delivered** the `salesOrder` event to `webhook.togahub.com/netsuite`.
172
+ Filter `route LIKE '%netsuite%'` and REGEXP the internalId in `requestPayload`.
173
+ 2. **`Core.WorkerJobs`** (core2, schema `Core`) — the **processing outcome**.
174
+ `action LIKE 'Netsuite/SalesOrder/%'`, `JSON_EXTRACT(parameters,'$.internalId')`.
175
+ 3. **`Logs.Api` with `source = 'OPEN_ORDER_IMPORT'`** — the **import breadcrumb**. `responseCode`:
176
+ **200** persisted (== intended) / **409** silent zero-rows / **202** deferred / **204** removeAll /
177
+ **500** gave up.
178
+ **SIGNAL:** zero `OPEN_ORDER_IMPORT` rows in `Logs.Api` despite a successful `WorkerJobs` row ⇒ the
179
+ running handler **predates the breadcrumb instrumentation**.
145
180
  - **REST shape ≠ SOAP shape.** The cron reads the SOAP-shim shape; this handler reads the REST record
146
181
  (`status->refName`, line `quantityBilled`, `class->refName`, `entity->id`, `salesRep->id`,
147
182
  `shippingCost`). Verified against live orders via `test/@dave/probe_salesorder_rest_shape.php`.
@@ -196,6 +231,15 @@ and are a candidate to extract into a shared `_Component_Forecast_Db` before the
196
231
 
197
232
  ## Change history
198
233
 
234
+ - 2026-06-23 — **Documented the OpenOrderItems schema/FK column, two silent zero-write paths, and the
235
+ full 3-table diagnostic ladder.** Recorded that the table lives in `Forecast` on core2 (~4,347 rows)
236
+ with FK `netsuiteSalesOrderInternalId`, and the **1054 false-negative** trap from querying it with the
237
+ `Forecast.Sales` column `netsuiteTransactionInternalId`. Added that an `isSuccess=1` job proves nothing
238
+ about rows written — STATUS GATE and ALL-LINES-FILTERED both succeed with zero writes, and only a
239
+ post-write count detects it. Extended the WorkerJobs/breadcrumb note into the full read-only ladder
240
+ (`Logs.Webhook` delivery → `Core.WorkerJobs` outcome → `Logs.Api` `OPEN_ORDER_IMPORT` breadcrumb), with
241
+ the signal that zero breadcrumbs despite a successful job ⇒ handler predates the instrumentation. Added
242
+ `probe_drift_so_dates.php`. (dfranks)
199
243
  - 2026-06-22 — **Separated "missing open order" into two distinct buckets** with a discriminating
200
244
  diagnostic (`Core.WorkerJobs` lookup by `parameters.internalId`): **(A) webhook never enqueued** (no
201
245
  `WorkerJobs` row — NetSuite-side enqueuer not firing for integration/Web-Services/backend-mass-edit
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "toga-ai",
3
- "version": "1.0.180",
3
+ "version": "1.0.182",
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",