toga-ai 1.0.291 → 1.0.293
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.
- package/knowledge/2.0/apps/_underscore/INDEX.md +1 -1
- package/knowledge/2.0/apps/_underscore/features/component-model-namespace-registration.md +9 -0
- package/knowledge/2.0/apps/_underscore/features/error-reporting-issue-event.md +86 -54
- package/knowledge/2.0/apps/_underscore/features/forecast-sale-import.md +53 -0
- package/package.json +1 -1
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
| [Carrier Shipping Labels (UPS/FedEx) & NetSuite Item Fulfillment](features/carrier-shipping-labels.md) | Backend mechanics behind TOGa Supply's Fulfill & Ship: buying a carrier label (UPS/FedEx), persisting it, and creating the NetSuite Item Fulfillment with tracki | _underscore/Model/Client/ItemFulfillment.php, _underscore/Model/Client/TrackingNumber.php, _underscore/Model/Client/ItemFulfillments/TrackingNumber.php, _underscore/Component/Library/LabelPdf/LabelPdf.php, _underscore/Component/Library/Carriers/ShipmentRequest/ShipmentRequest.php, _underscore/Component/Library/Carriers/Ups/Ups.php, _underscore/Component/Library/Carriers/Fedex/Fedex.php, _underscore/Trait/Netsuite/ItemFulfillment.php, _underscore/Trait/Netsuite/SalesOrder.php, _underscore/Component/Library/NetSuite/NetSuite.php, _underscore/Model/Client/TrackingNumber.php, _underscore/Model/Client/ShippingMethod.php, _underscore/Model.php, _underscore/Cloud.php |
|
|
11
11
|
| [_Component_*/_Model_* project-namespace registration (autoloader) & backslash-qualify traps](features/component-model-namespace-registration.md) | Every **project-local** `_Component_*` and `_Model_*` class in a 2.0 app **must declare the project namespace** at the top of the file: ```php namespace <NAMESP | _underscore/Loader.php, worker2/_.php, api2/_.php, worker2/Component/Forecast/Db/Db.php, worker2/Component/Forecast/SaleImport/SaleImport.php, api2/Component/Api/Netsuite/Netsuite.php |
|
|
12
12
|
| [Client Email Template Sending](features/email-template-sending.md) | `_Model_Client_EmailTemplate` sends a stored, client-defined email template by UUID. | _underscore/Model/Client/EmailTemplate.php, _underscore/Model/Client/EmailTemplateOutgoingEmailAddress.php, _underscore/Email.php |
|
|
13
|
-
| [Error Reporting — Issue/Event Aggregation (
|
|
13
|
+
| [Error Reporting — Issue/Event Aggregation (agreed POST-to-receiver design)](features/error-reporting-issue-event.md) | Platform-wide error-reporting infrastructure for TOGA 2.0, built around a two-table **Issue / Event** aggregation model in the shared **Core Logs DB**. | _underscore/Error.php, _underscore/Model/Core/Logs/Issue.php, _underscore/Model/Core/Logs/Event.php, dbchanges2/Logs/2026-07-06 - Issue and Event tables.sql |
|
|
14
14
|
| [Record-Changed Event Publishing (_Event::publish to SQS)](features/event-publish-sqs.md) | `_Event::publish()` (in `_underscore/Event.php`) is the PHP side of the real-time event pipeline. | _underscore/Event.php |
|
|
15
15
|
| [Forecast.Sales NetSuite import engine (real-time webhook)](features/forecast-sale-import.md) | Real-time importer that takes a NetSuite **sale** record and writes its lines into `Forecast.Sales` (the Forecast2 revenue table). | _underscore/Component/Forecast/SaleImport/SaleImport.php, _underscore/Component/Forecast/Db/Db.php, _underscore/Component/Api/Netsuite/Netsuite.php, worker2/Worker/Netsuite/Invoice.php, worker2/Worker/Netsuite/CashSale.php, worker2/Worker/Netsuite/CreditMemo.php, worker2/Worker/Netsuite/CashRefund.php, worker2/Worker/Netsuite/JournalEntry.php, worker2/Worker/Netsuite/Opportunity.php, worker2/Worker/Netsuite/SalesOrder.php, dbchanges2/Forecast/2026-06-26a - Add journalEntry to Sales transaction type enum.sql, test/@dave/test_invoice_lifecycle.php, test/@dave/test_je_lifecycle.php, test/@dave/test_creditmemo_lifecycle.php, test/@dave/test_cashsale_lifecycle.php, test/@dave/test_cashrefund_lifecycle.php, test/@dave/test_fetchrecord_routes.php, test/@dave/verify_je_classification.php, test/@dave/probe_je_accounts.php, test/@dave/probe_je_shape.php, test/@dave/fixer.php, test/@dave/Junk Drawer/NetSuite/api-message-queue/ue_api_msg_queue_enqueue.js, test/@dave/Junk Drawer/NetSuite/api-message-queue/dev_ue_api_msg_queue_enqueue.js |
|
|
16
16
|
| [Item-Fulfillment Stage Lifecycle (picked/packed/shipped) & Order Status](features/item-fulfillment-stage-lifecycle-and-order-status.md) | Every ItemFulfillment (IF) now carries an explicit **stage** — picked → packed → shipped — resolved through `ItemFulfillmentStages → ItemFulfillmentStatuses` (m | _underscore/Model/Client/SalesOrder.php, _underscore/Model/Quad/SalesOrder.php, _underscore/Model/Compass/SalesOrder.php, _underscore/Model/Compass/SalesOrderStatus.php, _underscore/Model/Client/SalesOrderItem.php, _underscore/Model/Client/Item.php, _underscore/Model/Client/PurchaseOrderItem.php, library/app/api/toga2.php, dbchanges2/Client/2026-06-30a - BackfillNullStageItemFulfillmentsToShipped.sql, dbchanges2/Client/2026-06-30b - SalesOrderStatusesPickedPacked.sql, dbchanges2/Client/2026-06-30c - ItemFulfillmentStageIdNotNull.sql, dbchanges2/Client_CompassCanada/2026-06-30a - ItemFulfillmentLifecycleAndShippedBackfill.sql |
|
|
@@ -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
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
---
|
|
2
|
-
title: Error Reporting — Issue/Event Aggregation (
|
|
2
|
+
title: Error Reporting — Issue/Event Aggregation (agreed POST-to-receiver design)
|
|
3
3
|
framework: "2.0"
|
|
4
4
|
repo: _underscore
|
|
5
5
|
project: _Underscore
|
|
6
6
|
client: shared
|
|
7
7
|
type: feature
|
|
8
8
|
status: active
|
|
9
|
-
updated: 2026-07-
|
|
9
|
+
updated: 2026-07-08
|
|
10
10
|
owners: ["dfranks"]
|
|
11
11
|
files:
|
|
12
12
|
- _underscore/Error.php
|
|
@@ -21,39 +21,51 @@ related:
|
|
|
21
21
|
|
|
22
22
|
## Summary
|
|
23
23
|
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
24
|
+
Platform-wide error-reporting infrastructure for TOGA 2.0, built around a two-table
|
|
25
|
+
**Issue / Event** aggregation model in the shared **Core Logs DB**. An **Issue** is the
|
|
26
|
+
deduplicated record for a distinct error (keyed by a hash of the stack trace) carrying an
|
|
27
|
+
aggregate `occurrences` counter and an escalatable `urgency`; an **Event** is one row per
|
|
28
|
+
individual occurrence with its own trace and context.
|
|
29
|
+
|
|
30
|
+
**The agreed architecture (see below) decouples *reporting* from *persistence*:**
|
|
31
|
+
application error handlers **POST** error payloads to a centralized `/errors` receiver
|
|
32
|
+
endpoint; a **worker2 cron** consumes them and does all the hashing, upserting, aggregation,
|
|
33
|
+
escalation, and ClickUp sync into the Core Logs DB. Handlers do **not** write Issue/Event
|
|
34
|
+
rows to the database directly. This is the canonical target design for the whole
|
|
35
|
+
error-monitoring epic (TRUE-781xx) — the handler ticket, the dashboard ticket, and the
|
|
36
|
+
escalation-cron ticket must all conform to it.
|
|
37
|
+
|
|
38
|
+
## Agreed error-monitoring architecture
|
|
39
|
+
|
|
40
|
+
Source: the **"2026-05-21 - Errors, Monitoring and Alerts"** meeting design (approved;
|
|
41
|
+
cited via Talos DevCore meeting notes). Pipeline:
|
|
42
|
+
|
|
43
|
+
1. **Report (application side).** An application's error/exception handler serializes the
|
|
44
|
+
error (message, stack trace, context) and **POSTs it to a centralized `/errors` receiver
|
|
45
|
+
endpoint** (the meeting referenced `webhook.hub.com/error`). The handler's only job is to
|
|
46
|
+
transmit — it performs **no direct database persistence**.
|
|
47
|
+
2. **Ingest (worker2 cron).** A worker2 cron consumes the received error payloads and:
|
|
48
|
+
- builds `issueHash` from the stack trace to identify the distinct error,
|
|
49
|
+
- **upserts an Issue** and **inserts an Event** into the **Core** Logs DB,
|
|
50
|
+
- aggregates occurrence counts within a time window,
|
|
51
|
+
- auto-escalates / de-escalates the Issue `urgency` based on occurrence frequency,
|
|
52
|
+
- syncs the Issue to ClickUp.
|
|
53
|
+
3. **Scope.** API 2.0 handlers first; 1.0 is deferred.
|
|
54
|
+
|
|
55
|
+
The critical rule: **handlers POST; the cron persists (to Core).** Any approach where the
|
|
56
|
+
handler writes Issue/Event rows inline to a database contradicts this design.
|
|
57
|
+
|
|
58
|
+
## Data model (Core Logs DB)
|
|
59
|
+
|
|
39
60
|
- **`_underscore/Model/Core/Logs/Issue.php`** — `const DATABASE = _underscore::DB_LOGS;
|
|
40
61
|
const TABLE = 'Issues';`. Fields: `id, uuid, dtCreated, dtLastOccurred, urgency, subject,
|
|
41
|
-
description, clickupIdentifier, hash, errorMessage, occurrences, trace`.
|
|
62
|
+
description, clickupIdentifier, hash, errorMessage, occurrences, trace`. One row per
|
|
63
|
+
distinct error (dedup key = `hash`); `occurrences` is the rolling count, `dtLastOccurred`
|
|
64
|
+
the most-recent hit, `urgency` a list field, `clickupIdentifier` links to an escalation
|
|
65
|
+
ticket. There is **no `type` column**.
|
|
42
66
|
- **`_underscore/Model/Core/Logs/Event.php`** — `const DATABASE = _underscore::DB_LOGS;
|
|
43
67
|
const TABLE = 'Events';`. Fields: `id, uuid, issueId (FK → Issue), errorMessage, trace,
|
|
44
|
-
context, dtCreated`.
|
|
45
|
-
|
|
46
|
-
## How it works
|
|
47
|
-
|
|
48
|
-
1. On an uncaught exception, hash the backtrace to identify the distinct error.
|
|
49
|
-
2. Load the existing `Issue` by `hash`. On a miss, create it with `errorMessage`, `trace`,
|
|
50
|
-
`urgency = 'LOW'`, and `occurrences = 0`.
|
|
51
|
-
3. **Increment `occurrences`** (`($issue->occurrences ?? 0) + 1`) and set
|
|
52
|
-
`dtLastOccurred`, then save — so the counter advances on every occurrence, hit or new.
|
|
53
|
-
4. Insert one `Event` for this occurrence: `issueId`, `errorMessage`, per-occurrence
|
|
54
|
-
`trace`, and `context` (`print_r($GLOBALS, true)`).
|
|
55
|
-
5. Commit the `DB_LOGS` transaction; surface `Error {issueId}, Event {eventId}` in the
|
|
56
|
-
debug body.
|
|
68
|
+
context, dtCreated`. One row per occurrence.
|
|
57
69
|
|
|
58
70
|
The `Issues`/`Events` tables live in the shared **Core Logs** DB, provisioned from
|
|
59
71
|
`dbchanges2/Logs/` (the `Logs/` folder targets the framework-level, non-tenant `Logs` DB
|
|
@@ -61,35 +73,55 @@ with unqualified table names — see the dbchanges2 architecture folder→DB map
|
|
|
61
73
|
with per-client API/error logging in `Logs_<Tenant>` via `Model/Client/Logs/*`
|
|
62
74
|
(`DB_CLIENT_LOGS`, `dbchanges2/Logs_Client/`).
|
|
63
75
|
|
|
64
|
-
##
|
|
76
|
+
## Current state of `_underscore/Error.php` (important — reads differently than you'd expect)
|
|
77
|
+
|
|
78
|
+
`_underscore/Error.php` registers `exceptionHandler()` via `set_exception_handler`, but in
|
|
79
|
+
**`_production` it does NOT persist any Issue/Event rows.** It builds a debug HTML body,
|
|
80
|
+
writes it to an S3 error-details file, and (when a Logs register is present) records a
|
|
81
|
+
single `_Model_Client_Logs_Error` / `_Model_Core_Logs_Error` row — the older flat error log.
|
|
82
|
+
There is no Issue/Event aggregation, no `issueHash` upsert, and no ClickUp escalation in the
|
|
83
|
+
handler on `_production`.
|
|
65
84
|
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
85
|
+
**An inline-DB-persist approach exists on an in-flight branch, but it conflicts with the
|
|
86
|
+
agreed design and must not be treated as the model to follow:** that approach has the
|
|
87
|
+
handler compute `$issueHash = md5(serialize($backtrace))` and directly upsert
|
|
88
|
+
`_Model_Client_Logs_Issue` + insert `_Model_Client_Logs_Event`, committing a transaction —
|
|
89
|
+
i.e. handler-side direct persistence, and to the **Client** Logs DB rather than **Core**.
|
|
90
|
+
That contradicts the approved architecture on two counts: (a) handlers should POST to the
|
|
91
|
+
`/errors` receiver, not persist; and (b) aggregation belongs in the Core Logs DB, driven by
|
|
92
|
+
the worker2 cron. New work (TRUE-78178 handlers) should implement the POST-to-receiver path,
|
|
93
|
+
not extend the inline-persist branch.
|
|
71
94
|
|
|
72
95
|
## Gotchas / known issues
|
|
73
96
|
|
|
74
|
-
-
|
|
75
|
-
|
|
97
|
+
- **Do not assume error persistence "already works."** On `_production` the handler writes
|
|
98
|
+
only an S3 debug body and a flat `*_Logs_Error` row — no Issue/Event aggregation exists in
|
|
99
|
+
production yet. The Issue/Event model is the *target*, delivered by the POST→cron pipeline.
|
|
100
|
+
- **`occurrences` must be incremented, not just seeded** — a naive upsert that sets
|
|
101
|
+
`occurrences = 0` only at creation never advances the aggregate counter. The cron must
|
|
76
102
|
increment on every occurrence.
|
|
77
|
-
- **`Event.trace` must be
|
|
78
|
-
|
|
79
|
-
- **Persistence
|
|
80
|
-
`\_Database::$_registers[_underscore::DB_LOGS]` isn't set,
|
|
81
|
-
skipped
|
|
103
|
+
- **`Event.trace` must be populated per occurrence** — the per-Event `trace` column is easy
|
|
104
|
+
to leave empty; record it on every Event.
|
|
105
|
+
- **Persistence must be conditional on the Core-Logs register.** If the Core Logs register
|
|
106
|
+
(`\_Database::$_registers[_underscore::DB_LOGS]`) isn't set, an Issue/Event write must be
|
|
107
|
+
skipped rather than fatal — but this belongs in the cron, not the handler.
|
|
108
|
+
- **Client vs Core Logs DB mix-up.** The Issue/Event tables belong in the **Core** Logs DB
|
|
109
|
+
(`DB_LOGS`, `Model/Core/Logs/*`). Writing them to a per-client Logs DB
|
|
110
|
+
(`DB_CLIENT_LOGS`, `_Model_Client_Logs_*`) is the wrong target and does not match the
|
|
111
|
+
agreed design.
|
|
82
112
|
- **Shared-schema migration collision.** Because `Issues`/`Events` live in the *shared* Core
|
|
83
|
-
Logs DB,
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
`IssueEmailAddresses` table, one adding this error-class persistence). Only **one**
|
|
87
|
-
migration may create the shared tables; reconcile overlapping `Logs/` create-scripts at
|
|
88
|
-
merge so the tables are created exactly once and later migrations only `ALTER`.
|
|
113
|
+
Logs DB, two dbchanges2 `Logs/` migrations that both `CREATE TABLE Issues`/`Events` will
|
|
114
|
+
collide ("table already exists"). Only **one** migration may create the shared tables;
|
|
115
|
+
later migrations only `ALTER`.
|
|
89
116
|
|
|
90
117
|
## Change history
|
|
91
118
|
|
|
92
|
-
- 2026-07-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
119
|
+
- 2026-07-08 — Corrected KB drift: documented the approved 2026-05-21 error-monitoring
|
|
120
|
+
architecture (handlers POST to a centralized `/errors` receiver; a worker2 cron builds
|
|
121
|
+
issueHash, upserts Issues + inserts Events into the Core Logs DB, aggregates/escalates,
|
|
122
|
+
syncs ClickUp — handlers do not persist). Clarified that `_production` Error.php performs
|
|
123
|
+
no Issue/Event persistence (only an S3 debug body + flat `*_Logs_Error` row), and that the
|
|
124
|
+
inline-DB-persist approach (handler-side upsert to the *Client* Logs DB) conflicts with the
|
|
125
|
+
agreed design and is not the pattern to follow. (dfranks)
|
|
126
|
+
- 2026-07-06 — Prior doc described the exceptionHandler as directly persisting Issue/Event
|
|
127
|
+
rows to the Core Logs DB with dedup; superseded by the 2026-07-08 correction above. (dfranks)
|
|
@@ -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