toga-ai 1.0.544 → 1.0.545

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.
@@ -23,6 +23,7 @@
23
23
  | [Etilize Catalog Item Import & Refresh](features/etilize-catalog-item-import.md) | Client-generic catalog onboarding from an S3 CSV plus an Etilize re-pull. | worker2/Worker/Etilize/Items.php |
24
24
  | [Etilize Item Translation Import](features/etilize-item-translation-import.md) | The abstract worker class `_Worker_Etilize_ItemTranslations` imports **non-English** item text from Etilize into the client's `ItemTranslations` table. | worker2/Worker/Etilize/ItemTranslations.php |
25
25
  | [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/Monitors/RateEntitlement.php, worker2/Worker/Notification/Email.php, worker2/Worker/Rate.php, dbchanges2/Core/2026-05-21 - Monitors.sql, dbchanges2/Core/2026-06-29a - Rate Entitlement Contract Monitor.sql |
26
+ | [NetSuite Integrations Monitor (Monitor/Operations/NetsuiteIntegrations)](features/netsuite-integrations-monitor.md) | `_Worker_Monitor_Operations::NetsuiteIntegrations()` is a cross-client health check that detects **stuck NetSuite ↔ 2.0 integrations**. | worker2/Worker/Monitor/Operations.php, dbchanges2/Core/2026-08-10a - Netsuite Integrations Monitor.sql, _underscore/Database.php, _underscore/Query.php |
26
27
  | [NetSuite ↔ ClickUp / 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/Worker/Clickup.php, worker2/Worker/Clickup/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, test/@dave/test_model_load_behavior.php, dbchanges2/Forecast/2026-06-25a - Add unique index on Opportunities netsuiteOpportunityInternalId.sql, _underscore/Model/Forecast/Opportunity.php, test/@dave/approach/TRUE-80044.md, worker/crons/toga2/forecast2/common_import_sales_from_netsuite.php |
27
28
  | [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, worker2/Component/Forecast/Db/Db.php, worker2/Worker/Netsuite/Location.php, test/@dave/Junk Drawer/NetSuite/api-message-queue/ue_api_msg_queue_enqueue.js, test/@dave/Junk Drawer/NetSuite/api-message-queue/ue_amq_invoice_resync_salesorder.js, 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, test/@dave/probe_open_order_gating.php, worker/crons/toga2/forecast2/import_open_orders.php, worker/crons/toga2/forecast2/common_import_sales_from_netsuite.php |
28
29
  | [NetSuite Supporting-Record Webhook Importer (the reusable recipe)](features/netsuite-supporting-record-webhook-importer.md) | A single **repeatable recipe** for porting a legacy daily-pull NetSuite *supporting-record* importer (the lookup/dimension tables behind Forecast2 — Employees, | worker2/Worker/Netsuite/Employee.php, worker2/Worker/Netsuite/Account.php, worker2/Worker/Netsuite/Classification.php, worker2/Worker/Netsuite/Customer.php, worker2/Worker/Netsuite/Item.php, worker2/Worker/Netsuite.php, _underscore/Model/Forecast/Employee.php, _underscore/Model/Forecast/Account.php, _underscore/Model/Forecast/Classification.php, _underscore/Component/Forecast/Db/Db.php, test/@dave/test_employee_lifecycle.php, test/@dave/test_account_lifecycle.php, test/@dave/test_classification_lifecycle.php, test/@dave/NetSuite/api-message-queue/ue_api_msg_queue_enqueue.js, worker/crons/toga2/forecast2/import_supporting_records.php |
@@ -15,6 +15,7 @@ files:
15
15
  - dbchanges2/Logs_Client/2026-05-21 - Email.sql
16
16
  related:
17
17
  - ./oneuptime-worker2-monitoring.md
18
+ - ./netsuite-integrations-monitor.md
18
19
  - ./monitoring-framework.md
19
20
  - ../../_underscore/features/email-send-pipeline.md
20
21
  - ../../_underscore/features/per-client-database-connections.md
@@ -0,0 +1,139 @@
1
+ ---
2
+ title: NetSuite Integrations Monitor (Monitor/Operations/NetsuiteIntegrations)
3
+ framework: "2.0"
4
+ repo: worker2
5
+ project: Worker
6
+ client: shared
7
+ type: feature
8
+ status: active
9
+ updated: 2026-08-10
10
+ owners: ["jcardinal"]
11
+ files:
12
+ - worker2/Worker/Monitor/Operations.php
13
+ - dbchanges2/Core/2026-08-10a - Netsuite Integrations Monitor.sql
14
+ - _underscore/Database.php
15
+ - _underscore/Query.php
16
+ related:
17
+ - ./all-client-email-queue-monitor.md
18
+ - ./oneuptime-worker2-monitoring.md
19
+ - ./monitoring-framework.md
20
+ - ../../_underscore/features/per-client-database-connections.md
21
+ ---
22
+
23
+ ## Summary
24
+
25
+ `_Worker_Monitor_Operations::NetsuiteIntegrations()` is a cross-client health check that
26
+ detects **stuck NetSuite ↔ 2.0 integrations**. It sweeps **every** client's business
27
+ database (`Client_<X>`), looks in the `Parameters` table for rows where
28
+ `` `key` `` LIKE `NETSUITE_EXECUTION_MODE_%` and `` `value` `` LIKE `1-RUNNING`, and pushes
29
+ one aggregate payload to a single OneUptime Incoming Request monitor with the **offending
30
+ integrations named** in the body. A row parked at the `1-RUNNING` sentinel means an
31
+ integration hung mid-run.
32
+
33
+ This is the second all-client monitor in `_Worker_Monitor_Operations`, modeled exactly on
34
+ its sibling [EmailQueue](./all-client-email-queue-monitor.md) — a "dumb reporter, smart
35
+ monitor": the worker only reports measured values and OneUptime holds the alarm
36
+ thresholds. It is **shared internal operational infrastructure**, not a client feature.
37
+
38
+ ## Key files / entry points
39
+
40
+ - `worker2/Worker/Monitor/Operations.php` — `abstract class _Worker_Monitor_Operations`.
41
+ - `public static function NetsuiteIntegrations(): string` — the action, route
42
+ `Monitor/Operations/NetsuiteIntegrations`.
43
+ - `private static function resolveClientDatabases(): array` — one `DB_CORE` query
44
+ returning every client + its **business** database name (`Client_<X>`) + this
45
+ environment's hosts. Mirrors `resolveClientLogDatabases()` but joins `Core.Clients` on
46
+ `clientDatabaseId` (not `logDatabaseId`).
47
+ - `dbchanges2/Core/2026-08-10a - Netsuite Integrations Monitor.sql` — the `Core.CronJobs`
48
+ seed row (see below).
49
+ - Dispatched by action string, not PHP import — the autoloader + `_Worker::runTask()`
50
+ reach the class via the cron action path.
51
+
52
+ ## How it works
53
+
54
+ 1. **Resolve every client's business database.** `resolveClientDatabases()` joins
55
+ `Clients` → `Databases` (via `Clients.clientDatabaseId`) → `DatabaseHosts` →
56
+ `Environments`/`Regions`, all **LEFT OUTER JOINs** so a misconfigured client is
57
+ reported, not silently dropped (`unresolvedClients`). Closest region first; later rows
58
+ for the same client are skipped.
59
+ 2. **Register each client's business DB under its REAL database name, no alias**, guarded
60
+ by `if (!isset(_Database::$_registers[$dbName]))`, then query via `_Query`. A per-client
61
+ loop cannot reuse a shared alias — every iteration would clobber it.
62
+ 3. **Sweep `Parameters` per client:**
63
+ `` SELECT `key`, `value` FROM Parameters WHERE `key` LIKE 'NETSUITE_EXECUTION_MODE_%' AND `value` LIKE '1-RUNNING' ``.
64
+ The integration **type** is the key with the `NETSUITE_EXECUTION_MODE_` prefix stripped
65
+ via `substr()` (e.g. `NETSUITE_EXECUTION_MODE_SALES_ORDERS` → `SALES_ORDERS`).
66
+ 4. **Bucket each client** into `checked`, `skipped` (no `Parameters` table —
67
+ `isMissingTableError()`), or `unreachable` (any other query/connection failure). A
68
+ per-client `try/catch (Throwable)` keeps one bad client from failing the sweep.
69
+ 5. **Decide the tokens and push once** to OneUptime.
70
+
71
+ ### Payload contract — `alarm` and `probe` are SEPARATE tokens
72
+
73
+ Follows the [OneUptime push-metric pattern](./oneuptime-worker2-monitoring.md): OneUptime
74
+ can only string-match a body, so the worker decides and emits tokens.
75
+
76
+ | Field | Meaning |
77
+ |---|---|
78
+ | `status` | `reporting`, or `error` (reserved for the Core lookup itself failing → `error:client_lookup_failed`) |
79
+ | `alarm` | `DEGRADED` when any stuck integration is found, else `OK` |
80
+ | `probe` | `DEGRADED` when any client DB is unreachable, else `OK` |
81
+ | `stuckCount` | number of stuck integrations found |
82
+ | `offendingIntegrations` | comma-joined `"ClientName: TYPE"` |
83
+ | `clientsChecked`, `clientsSkipped` | coverage counters |
84
+ | `unreachableClients`, `unresolvedClients` | named coverage gaps |
85
+ | `checkedAtUtc` | `gmdate('c')` |
86
+
87
+ ## Cron seed (dbchanges2)
88
+
89
+ `dbchanges2/Core/2026-08-10a - Netsuite Integrations Monitor.sql` — INSERT into
90
+ `Core.CronJobs`: action `Monitor/Operations/NetsuiteIntegrations`, schedule
91
+ `*/10 * * * *` (every 10 min), `maxExecutionTime` 120, `isActive` 1, `parameters` NULL,
92
+ fixed random uuid. Targets **only** `Core` (no cross-cluster refs), INSERT-only and
93
+ unguarded per dbchanges2 conventions. Modeled on
94
+ `Core/2026-07-29a - AIG Entitlement API Failure Monitor.sql`.
95
+
96
+ **Shipped `isActive=1` while the OneUptime URL is still a placeholder** (developer's
97
+ choice): the sweep runs but the push fails soft (`error_log`) until the real URL is pasted
98
+ into the worker method.
99
+
100
+ ## Gotchas / known issues
101
+
102
+ - **`` `key` `` and `` `value` `` are MySQL reserved words** — backtick-quote them in the
103
+ `Parameters` query.
104
+ - **Single source of truth for the key prefix.** `$KEY_PREFIX` builds both the `LIKE`
105
+ pattern (via `_Database::escape`) and the `substr()` that derives the integration type;
106
+ never hardcode the prefix twice.
107
+ - **Never-throw / 200-on-all-paths.** The method always returns a summary string. The
108
+ OneUptime push is `setLogging(false)` + `setThrowExceptionsOnFailure(false)` **and**
109
+ wrapped in its own `try/catch`, so a failed ping never fails the worker job.
110
+ - **OneUptime push URL is a credential** — it lives only as an in-file value in the worker
111
+ method, never committed to dbchanges2 and never recorded in a doc. It is currently a
112
+ placeholder.
113
+ - **Business DB, not logs DB.** Join `Clients.clientDatabaseId` for `Client_<X>`; the
114
+ sibling EmailQueue monitor joins `logDatabaseId` for `Logs_<X>`. See
115
+ [per-client database connections](../../_underscore/features/per-client-database-connections.md).
116
+ - The all-client resolution gotchas from
117
+ [EmailQueue](./all-client-email-queue-monitor.md) apply verbatim: LEFT OUTER JOIN every
118
+ level, null host columns unless the environment join matched, detect missing table by
119
+ MySQL error **number** 1146 (not message text), and remember a passing dev run (single
120
+ all-in-one endpoint) does not prove cross-cluster correctness.
121
+
122
+ ## Change history
123
+
124
+ - 2026-08-10 — Built `NetsuiteIntegrations()` + `resolveClientDatabases()` on
125
+ `_Worker_Monitor_Operations` (all-client `Parameters` sweep for
126
+ `NETSUITE_EXECUTION_MODE_%` = `1-RUNNING`), plus the `Core.CronJobs` seed
127
+ (`*/10 * * * *`, `isActive=1`) and an OneUptime Incoming Request monitor import. OneUptime
128
+ URL still a placeholder; sweep runs and pushes fail-soft until pasted. (jcardinal)
129
+
130
+ ## Related docs
131
+
132
+ - [All-Client Email Queue Monitor](./all-client-email-queue-monitor.md) — the sibling
133
+ all-client monitor this one is modeled on.
134
+ - [OneUptime push-metric monitors for 2.0 workers](./oneuptime-worker2-monitoring.md) — the
135
+ push/token pattern and OneUptime criteria (incl. `{{requestBody.*}}` incident templating).
136
+ - [Per-Client Database Connections](../../_underscore/features/per-client-database-connections.md)
137
+ — business/log database name resolution.
138
+ </content>
139
+ </invoke>
@@ -111,6 +111,25 @@ log dive. `status: "error"` stays reserved for the checker being **fully** blind
111
111
  resolution query failed). Worked example:
112
112
  [All-Client Email Queue Monitor](./all-client-email-queue-monitor.md).
113
113
 
114
+ ### Incident / alert title & description templating (`{{requestBody.*}}`)
115
+
116
+ OneUptime incident and alert **titles and descriptions support `{{variable}}` templating**
117
+ (verified against OneUptime docs 2026-08-10). For an **Incoming Request** monitor, the
118
+ POSTed JSON body is addressed via the `requestBody` prefix — e.g.
119
+ `{{requestBody.offendingIntegrations}}`, `{{requestBody.stuckCount}}`,
120
+ `{{requestBody.checkedAtUtc}}`, `{{requestBody.message}}`. Other available vars:
121
+ `{{requestHeaders}}`, `{{requestMethod}}`, `{{incomingRequestReceivedAt}}`.
122
+
123
+ - **Missing paths resolve to empty string**, so one template covers both a normal degraded
124
+ payload and an edge case like `error:client_lookup_failed` without a second template.
125
+ - Body-content criteria use `checkOn: "Request Body"` / `filterType: "Contains"`; the
126
+ heartbeat criterion uses `"Not Recieved In Minutes"` (note OneUptime's misspelling).
127
+ - A degraded criterion can OR several tokens, e.g. Contains `"alarm":"DEGRADED"` **OR**
128
+ `"error":"client_lookup_failed"`.
129
+ - Reuse the project's existing status/severity ObjectIDs when authoring the import JSON.
130
+ - Docs: https://oneuptime.com/docs/monitor/incident-alert-templating and
131
+ https://oneuptime.com/docs/en/monitor/incoming-request-monitor
132
+
114
133
  ### Heartbeat / cron cadence timing
115
134
 
116
135
  The 10-min-Degraded / 15-min-Offline heartbeat thresholds pair with a **5-minute** cron
@@ -192,6 +211,10 @@ check for another client.
192
211
  monitors must pass the region (see [Cloud S3 helpers](../../_underscore/features/cloud-s3-helpers.md)).
193
212
 
194
213
  ## Change history
214
+ - 2026-08-10 — Documented OneUptime incident/alert `{{requestBody.*}}` templating (missing
215
+ paths → empty string; `Not Recieved In Minutes` heartbeat spelling), discovered while
216
+ wiring the second all-client `_Worker_Monitor_Operations` monitor —
217
+ [NetSuite Integrations Monitor](./netsuite-integrations-monitor.md). (jcardinal)
195
218
  - 2026-07-30 — Added the multi-client refinement of the payload contract: a **separate `probe`
196
219
  token** (`OK`/`DEGRADED`) alongside `alarm`, so a partial logs-cluster outage cannot read as a
197
220
  healthy metric, plus naming the offending clients in the body. Recorded that the previously
@@ -19,7 +19,7 @@ _Auto-generated by `knowledge.js index`. Do not hand-edit._
19
19
  ## 2.0 framework
20
20
 
21
21
  - **_underscore** (_Underscore) _(framework core)_ — 48 doc(s) → [2.0/apps/_underscore/INDEX.md](2.0/apps/_underscore/INDEX.md)
22
- - **worker2** (Worker) — 42 doc(s) → [2.0/apps/worker2/INDEX.md](2.0/apps/worker2/INDEX.md)
22
+ - **worker2** (Worker) — 43 doc(s) → [2.0/apps/worker2/INDEX.md](2.0/apps/worker2/INDEX.md)
23
23
  - **api2** (API) — 22 doc(s) → [2.0/apps/api2/INDEX.md](2.0/apps/api2/INDEX.md)
24
24
  - **dbchanges2** (Database Changes) _(framework core)_ — 8 doc(s) → [2.0/apps/dbchanges2/INDEX.md](2.0/apps/dbchanges2/INDEX.md)
25
25
  - **toga2-supply** (TOGa Supply) — 5 doc(s) → [2.0/apps/toga2-supply/INDEX.md](2.0/apps/toga2-supply/INDEX.md)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "toga-ai",
3
- "version": "1.0.544",
3
+ "version": "1.0.545",
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",