toga-ai 1.0.291 → 1.0.292

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.
@@ -92,6 +92,15 @@ project-local `_Component_*`/`_Model_*` must still declare the project namespace
92
92
  unqualified `catch (Exception $e)` fails **silently** — the more insidious of the two.
93
93
 
94
94
  ## Change history
95
+ - 2026-07-08 — **Production recurrence** of this exact fatal (concrete fingerprint, no doc change
96
+ to the mechanism): a worker2 deploy declared `_Component_Forecast_SaleImport`
97
+ (`Worker/Netsuite/Invoice.php` autoload) and its sibling `_Component_Forecast_Db`
98
+ (`Component/Forecast/Db/Db.php`, opportunity jobs) in the global namespace → every
99
+ Forecast-writing NetSuite webhook type threw at autoload the moment the deploy landed, flipping
100
+ a Forecast-wide ~98%-success window to ~72%-failure at a single timestamp. Confirms the
101
+ invisible-to-`php -l`, blocks-all-writes-before-DB behavior in prod. Diagnostic path (how the
102
+ before/after `isSuccess` split is read out of `Core.WorkerJobs`) is captured in the
103
+ [Forecast.Sales import doc's diagnostic playbook](./forecast-sale-import.md). (dfranks)
95
104
  - 2026-07-08 — Documented the `_Component_*`/`_Model_*` project-namespace registration
96
105
  requirement (Loader.php eval-bridge mechanism, ~71-83), the runtime-only failure mode (not
97
106
  caught by `php -l`), and the three constructs that must be backslash-qualified inside a
@@ -447,6 +447,46 @@ Keep **create→delete windows short** so most records slip between cron runs; o
447
447
  `import_sales` during a batch. (The daily discrepancy-fix also cleans stragglers since the
448
448
  record is deleted in NetSuite.)
449
449
 
450
+ ## Diagnostic playbook — proving whether inbound webhooks actually wrote Forecast.Sales (run cold)
451
+ Durable procedure for answering "did the real-time webhook path work, or is a `fixer.php`
452
+ sweep masking a broken pipeline?" It spans **two DB clusters** — correlate them; never infer
453
+ success from a `Forecast.Sales` row alone.
454
+ 1. **Receipt (prod-logs `Logs.Webhook`)** — proves the POST *arrived*, nothing more. Columns:
455
+ `id, uuid, dtStamp, sourceIp, method, route, requestPayload` (JSON with
456
+ `recordType`/`internalId`/`eventType`/`action`). NetSuite hits land on route `/netsuite`.
457
+ **There is NO status/error column** — a row here means "received", not "succeeded".
458
+ 2. **Success/failure (prod-core `Core.WorkerJobs`)** — the authoritative outcome:
459
+ `isSuccess` (NULL=pending/running, 0=fail, 1=success), `failureReason` (mediumtext, full
460
+ stack trace), `action`, `dtCreated`.
461
+ 3. **Two-stage fan-out.** The router job `action='Netsuite/Webhook'` fans out to per-record-type
462
+ **child** jobs (`Netsuite/Invoice/post`, `Netsuite/SalesOrder/put`, `Netsuite/Opportunity/*`,
463
+ etc.). The **router almost always succeeds**; the Forecast writes — and their failures — live
464
+ in the **child** jobs. Filter WorkerJobs on the child `action`, not the router, to judge the
465
+ pipeline.
466
+ 4. **CRITICAL — a `Forecast.Sales` row does NOT prove the webhook worked.** `fixer.php` (the
467
+ reconciliation sweep) independently writes/repairs rows, so a present row can mask a fully
468
+ broken webhook path. **Always correlate `Logs.Webhook.dtStamp` against
469
+ `WorkerJobs.isSuccess`/`dtCreated` by uuid/timestamp** — never conclude success from row
470
+ presence.
471
+ 5. **Bucket errors by class.** `failureReason` embeds a per-row `[uuid] ` prefix; strip it with
472
+ `CASE`/`REGEXP_REPLACE` to collapse per-row UUIDs into error classes and get clean counts —
473
+ the fast way to distinguish a deployment-wide fatal from long-tail noise.
474
+
475
+ **Known WorkerJobs failure classes on this pipeline (durable):**
476
+ - **Autoload fatal blocks ALL Forecast writes before any DB work** — a global-namespace
477
+ `_Component_Forecast_SaleImport`/`_Component_Forecast_Db` throws at autoload (see the
478
+ [namespace-registration doc](./component-model-namespace-registration.md)); every
479
+ Forecast-writing child type (Invoice/SalesOrder/Opportunity/JournalEntry/CashSale/CreditMemo)
480
+ fails identically and the fatal is **invisible to `php -l`**. A clean before/after `isSuccess`
481
+ split at a single deploy timestamp is the fingerprint (a bad push flips a Forecast-wide
482
+ ~98%-success window to ~72%-failure). Recurred in production 2026-07-08.
483
+ - **`Netsuite/InventoryItem/*` / `NonInventoryItem/post` — `watchdog: exceeded maxExecutionTime
484
+ without completing`**: item syncs timing out at the 300s watchdog.
485
+ - **`Netsuite/Invoice/post` — MySQL error 1213 `Deadlock found when trying to get lock`** on the
486
+ Forecast DB: pre-existing, low-volume, ~1.6–3.9s each.
487
+ - **`Netsuite/Webhook` router — `Unsupported NetSuite eventType: approve`** (`Worker/Netsuite.php`
488
+ ~line 53): salesOrder `approve` events aren't handled by the router and fail at fan-out.
489
+
450
490
  ## Gotchas / known issues
451
491
  - **Testing locally pollutes PROD unless you UNDEPLOY the prod AMQ enqueuer first.** Each sale
452
492
  transaction type has **TWO** NetSuite UE enqueuer deployments: **"AMQ — Enqueuer"**
@@ -499,6 +539,19 @@ record is deleted in NetSuite.)
499
539
  - The cron's sign handling is not portable here — see Sign convention.
500
540
 
501
541
  ## Change history
542
+ - 2026-07-08 — **Added the webhook-vs-fixer diagnostic playbook** for proving whether inbound
543
+ NetSuite webhooks actually wrote `Forecast.Sales`: correlate `Logs.Webhook` (prod-logs;
544
+ receipt-only, NO status column) against `Core.WorkerJobs` (prod-core; `isSuccess`/
545
+ `failureReason`), judge the **child** `Netsuite/<Type>/<verb>` jobs (not the almost-always-OK
546
+ `Netsuite/Webhook` router), and **never infer success from a `Forecast.Sales` row** because
547
+ `fixer.php` independently writes/masks rows — correlate timestamps. Bucket `failureReason` with
548
+ `CASE`/`REGEXP_REPLACE` (strip the `[uuid] ` prefix) into error classes. Recorded a concrete
549
+ **production recurrence (2026-07-08)** of the global-namespace autoload fatal on
550
+ `_Component_Forecast_SaleImport`/`_Component_Forecast_Db` (Forecast-wide, invisible to `php -l`,
551
+ clean before/after `isSuccess` split at a single deploy timestamp), plus three lower-volume
552
+ WorkerJobs failure classes on this pipeline (item-sync watchdog timeout; Invoice deadlock 1213;
553
+ router `Unsupported NetSuite eventType: approve`). Read-only diagnostic session, no code change.
554
+ (dfranks)
502
555
  - 2026-07-08 — **Clarified the revenue sign convention is NOT the `amountDue` "store raw
503
556
  positive" rule.** The TRUE-78923 "don't sign-flip credit memos/refunds" guidance applies
504
557
  only to `amountDue` (which this importer does not write); the line `amount`/revenue sign
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "toga-ai",
3
- "version": "1.0.291",
3
+ "version": "1.0.292",
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",