toga-ai 1.0.597 → 1.0.598

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.
@@ -34,7 +34,7 @@
34
34
  | [DB-Driven Notification (Internal) Email](features/notification-email.md) | Internal/notification emails (merge-conflict alerts, ops notices — anything system-generated, not client-facing transactional mail) are sent through one worker | worker2/Worker/Notification/Email.php, _underscore/Model/Client/EmailTemplate.php, dbchanges2/Client/2026-06-23a - EmailTemplateWrapper.sql, dbchanges2/Client_True/2026-06-23a - EmailTemplateWrapper.sql |
35
35
  | [NYCHH Asset-Tag Backfill (worker2)](features/nychh-asset-tag-backfill.md) | Keeps NYC Health & Hospitals (`Client_Nychh`) unit asset tags and MAC addresses synced from NetSuite. | worker2/Worker/Client/Nychh.php, worker2/Worker/Client/Nychh/AssetTagBackfill.php, worker/crons/toga2/netsuite/verify_fulfillment_asset_tag_sync_nychh.php, worker/crons/toga2/netsuite/backfill_all_asset_tags_from_netsuite_nychh.php |
36
36
  | [OneUptime Incident → ClickUp Task Sync (Monitor/Oneuptime/SyncIncidents)](features/oneuptime-incident-clickup-sync.md) | An internal/shared TOGA ops feature: worker2 polls the OneUptime API every 15 minutes for **currently-open** incidents and ensures a ClickUp task exists for eac | worker2/Component/Api/Oneuptime/Oneuptime.php, worker2/Worker/Monitor/Oneuptime.php, worker2/Config/production.ini, dbchanges2/Team/2026-08-10a - OneUptime Incident ClickUp Tasks.sql, dbchanges2/Core/2026-08-10b - OneUptime Incident Sync CronJob.sql, dbchanges2/Team/2026-08-12a - OneUptime Incident Tasks Closed Marker.sql |
37
- | [OneUptime push-metric monitors for 2.0 workers](features/oneuptime-worker2-monitoring.md) | A second, **OneUptime-reporting** monitoring pattern for the 2.0 worker2 tier, ported from the 1.0 `App_SystemMonitor_Compass` monitors. | worker2/Worker/Monitor/Compass.php, worker2/Worker/Client/Compass.php, worker2/composer.json, _underscore/Cloud.php, worker2/Worker/Monitor/Operations.php |
37
+ | [OneUptime push-metric monitors for 2.0 workers](features/oneuptime-worker2-monitoring.md) | A second, **OneUptime-reporting** monitoring pattern for the 2.0 worker2 tier, ported from the 1.0 `App_SystemMonitor_Compass` monitors. | worker2/Worker/Monitor/Compass.php, worker2/Worker/Client/Compass.php, worker2/composer.json, _underscore/Cloud.php, worker2/Worker/Monitor/Operations.php, worker2/Worker/Monitor/Aig.php, worker2/Worker/Monitor/Prudential.php, worker2/Worker/Monitor/Nycdoe.php, worker2/Config/production.ini, worker2/Config/beta.ini, worker2/Config/sandbox-dev.ini |
38
38
  | [Platform Cache Cleanup (_Worker_Platform_Cache — Clean + Truncate)](features/platform-cache-cleanup.md) | `_Worker_Platform_Cache` owns maintenance of the shared **Cache** cluster that backs api2's [multi-client data retrieval](../../api2/features/cross-client-data- | worker2/Worker/Platform/Cache.php, worker2/Controller/Index.php, worker2/_.php, dbchanges2/Core/2026-07-27a - PlatformCacheCleanCron.sql |
39
39
  | [Service Request → Sales Order → Purchase Order generation (Sync/ServiceRequest)](features/service-request-sales-order-generation.md) | `_Worker_Sync_ServiceRequest` turns a **Service Request into a Sales Order, and then into one Purchase Order per vendor**, for **any** tenant. | worker2/Worker/Sync/ServiceRequest.php, _underscore/Model/Elite/ServiceRequest.php, dbchanges2/Core/2026-08-06a - Service request and sales order payload interceptors.sql |
40
40
  | [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 |
@@ -6,14 +6,20 @@ project: Worker
6
6
  client: shared
7
7
  type: feature
8
8
  status: active
9
- updated: 2026-07-30
10
- owners: ["jcardinal"]
9
+ updated: 2026-08-17
10
+ owners: ["jcardinal", "mhammontree"]
11
11
  files:
12
12
  - worker2/Worker/Monitor/Compass.php
13
13
  - worker2/Worker/Client/Compass.php
14
14
  - worker2/composer.json
15
15
  - _underscore/Cloud.php
16
16
  - worker2/Worker/Monitor/Operations.php
17
+ - worker2/Worker/Monitor/Aig.php
18
+ - worker2/Worker/Monitor/Prudential.php
19
+ - worker2/Worker/Monitor/Nycdoe.php
20
+ - worker2/Config/production.ini
21
+ - worker2/Config/beta.ini
22
+ - worker2/Config/sandbox-dev.ini
17
23
  related:
18
24
  - ./oneuptime-incident-clickup-sync.md
19
25
  - ./monitoring-framework.md
@@ -136,6 +142,69 @@ POSTed JSON body is addressed via the `requestBody` prefix — e.g.
136
142
  The 10-min-Degraded / 15-min-Offline heartbeat thresholds pair with a **5-minute** cron
137
143
  cadence. An initial 3/5-min setting false-alarmed on a single missed ping.
138
144
 
145
+ ### Two kinds of monitor — liveness probes and log-scrapers are complements, not duplicates
146
+
147
+ A monitor that **scrapes log rows** for arrived-and-failed calls (`Monitor/Aig/EntitlementApiFailures`,
148
+ the five 1.0 `App_SystemMonitor_ServiceNow*` classes) is **structurally blind to a fully dead
149
+ endpoint**: a refused connection logs nothing, produces zero failure rows, and reads as healthy.
150
+ Pair every failure-rate monitor on an integration endpoint with a **liveness probe** that actually
151
+ touches it. Rules for the probe:
152
+
153
+ - Use an **unauthenticated GET** so the probe can never create or mutate a record.
154
+ - **Any structured HTTP response below 500 is HEALTHY** — it proves DNS, TLS, the load balancer,
155
+ routing, and the auth layer are all alive. A `401` from an unauthenticated call to a protected
156
+ route is a *passing* result, not a failure. Only ≥500 / connect failure / timeout means dead.
157
+ (Verified 2026-08-17: `api-writer.togahub.com/v2/health` → 200; unauthenticated `/v2/entitlements`
158
+ → 401; both `alarm:"OK"`.)
159
+ - api2 short-circuits its health routes before any DB or Sentry work
160
+ (`api2/Controller/Index.php` `HEALTH_CHECK_ROUTES = ['/health','/v2/health']` → 200), so probing
161
+ them is cheap and does not depend on a client DB being reachable.
162
+ - **Token validity is out of scope for a worker2 probe** when the OAuth client lives in 1.0
163
+ (e.g. NYCDOE ServiceNow's `App_Api_NYCDOEV2` in `library`). Re-implementing it in worker2 would
164
+ duplicate credentials into a second repo and race the shared access token. Accept and record the
165
+ coverage gap rather than forking the client.
166
+
167
+ ### Tunable thresholds live in `Core.CronJobs.parameters`, everything else stays a local
168
+
169
+ The alarm threshold is the **one** setting that goes in the DB, as
170
+ `parameters = '{"alertThreshold":N}'`, so it can be retuned without a deploy. worker2 spreads
171
+ `parameters` as **named PHP arguments**, so the JSON key must match the method's parameter name
172
+ **exactly** or the call fails. Every other monitor setting (URLs, cutoffs, window sizes) stays an
173
+ `ALL_CAPS` in-method local.
174
+
175
+ **Echo `alertThreshold` in the pushed body next to the measured count.** A historical alarm is
176
+ otherwise unreadable once the threshold has been retuned.
177
+
178
+ ### Backlog monitors must exclude known-permanently-stranded rows
179
+
180
+ A backlog count that includes rows which will *never* drain pins the monitor HIGH from its first
181
+ run, and a monitor that is always red trains people to ignore it. Define a `BACKLOG_CUTOFF_DATE`
182
+ local at the date the monitor was written and count only rows on/after it. Report stranded/unmapped
183
+ row counts as **diagnostic fields that deliberately do not drive the alarm**.
184
+
185
+ ### Monitors are safe to deploy inert
186
+
187
+ Ship with the `Core.CronJobs` row `isActive = 0`. Each method must guard on an empty push URL, log
188
+ `no OneUptime push URL configured`, and still record its measurement in `WorkerJobs` — so the code
189
+ can deploy and be hand-run before the monitor exists in OneUptime.
190
+
191
+ ### Calibrate thresholds against the tier that runs the business logic
192
+
193
+ Several monitors live in worker2 but watch a pipeline whose crons run in the **1.0 worker** tier
194
+ against a 2.0 client DB (worker2 only READs it) — Compass and Prudential both work this way.
195
+ Thresholds must be derived from the **1.0 cron cadences and per-run throughput**, not from
196
+ worker2's own `*/5` schedule.
197
+
198
+ ### End-to-end test procedure (no deploy required)
199
+
200
+ Testing **recovery** is the step people skip and the most valuable one:
201
+
202
+ 1. `UPDATE Core.CronJobs SET parameters = '{"alertThreshold":-1}'` for the action.
203
+ 2. Hand-run the action → confirm `alarm:"HIGH"`, the monitor flips **Offline**, an incident opens,
204
+ and the notification arrives.
205
+ 3. Restore the real threshold and hand-run again → confirm the OK-gated criterion returns the
206
+ monitor to **Online** and resolves the incident.
207
+
139
208
  ## Provisioning a monitor (runbook)
140
209
 
141
210
  1. Write the monitor method on `_Worker_Monitor_Compass` (or a new `_Worker_Monitor_<X>`),
@@ -144,9 +213,14 @@ cadence. An initial 3/5-min setting false-alarmed on a single missed ping.
144
213
  **push credential**; never log it and never record its value in a doc.
145
214
  3. Provision the OneUptime monitor by importing the monitor's OneUptime resource-export
146
215
  JSON (authored under a local staging dir, e.g. `d:\STAGE` — these are import artifacts,
147
- not repo files) and wiring the Contains criteria above.
148
- 4. Add the `Core.CronJobs` row: `action = 'Monitor/Compass/<Method>'`, schedule
149
- `*/5 * * * *`, `maxExecutionTime` 120s, `parameters` NULL. Then deploy worker2.
216
+ not repo files) and wiring **only the body-`Contains` criteria** at this point.
217
+ The **push URL does not exist until the monitor is saved** — OneUptime generates it, it cannot
218
+ be pre-derived, so the monitor must exist before the worker code can be finished.
219
+ 4. Add the `Core.CronJobs` row: `action = 'Monitor/<Class>/<Method>'`, schedule
220
+ `*/5 * * * *`, `maxExecutionTime` 120s, `parameters` NULL (or
221
+ `{"alertThreshold":N}`), `isActive = 0`. Then deploy worker2.
222
+ 5. Flip `isActive = 1` and **confirm a push has landed**. Only then add the
223
+ **absence criteria** (not-received 10/15 min) — see the activation-ordering trap below.
150
224
 
151
225
  ### CronJob scheduling for these monitors
152
226
 
@@ -200,8 +274,15 @@ check for another client.
200
274
  - OneUptime Incoming Request bodies are matched **as strings only** (Contains/NotContains) —
201
275
  no numeric comparison, no JSON key targeting. Always emit a decided token, never a raw
202
276
  number, for push monitors.
203
- - The OneUptime push URL is a credential — keep it as an in-file constant in the method;
204
- never log it or write its value into knowledge docs.
277
+ - **ACTIVATION-ORDERING TRAP.** Creating a monitor with the **absence** criteria already wired
278
+ while its cron is still `isActive = 0` makes it go **Offline and open an incident within 15
279
+ minutes** with nothing pushing. Wire body-match criteria at creation; add absence criteria only
280
+ after `isActive = 1` and a confirmed push — or mute the monitor until then.
281
+ - The OneUptime push URL is a credential — a holder can spoof a healthy heartbeat and thereby
282
+ silence a real outage. Keep it as an in-file `$ONEUPTIME_URL` local in the method (the
283
+ established team pattern — 11 pre-existing instances across `Monitor/Compass.php`,
284
+ `Monitor/Aig.php`, `Monitor/Operations.php`); never log it and never write its value into a doc.
285
+ Moving these to `Config/production.ini` is a recognized future improvement, not yet done.
205
286
  - **`App_Client_Compass` (1.0) is unavailable in worker2.** Referencing it throws a
206
287
  class-not-found `Error`, which `catch (Exception)` does NOT catch — the whole monitor
207
288
  dies uncaught. Use `_Worker_Client_Compass::` constants only.
@@ -212,6 +293,19 @@ check for another client.
212
293
  monitors must pass the region (see [Cloud S3 helpers](../../_underscore/features/cloud-s3-helpers.md)).
213
294
 
214
295
  ## Change history
296
+ - 2026-08-17 — TRUE-80587: added five monitors and generalized this doc beyond Compass —
297
+ `Monitor/Prudential/{SalesOrderGenerationBacklog,DellPurchaseOrderTransmissionBacklog,
298
+ OrderShippedUpdateBacklog}`, `Monitor/Aig/EntitlementApiHealth`,
299
+ `Monitor/Nycdoe/ServiceNowEndpointHealth` (cron rows in
300
+ `dbchanges2/Core/2026-08-07a - Prudential AIG ServiceNow OneUptime monitor crons.sql`).
301
+ New durable sections: **liveness probes vs. log-scrapers are complements** (a scraper cannot see
302
+ a dead endpoint; <500 including 401 is healthy), the **`alertThreshold` DB parameter** convention
303
+ (named-argument spread; echo it in the body), **excluding permanently-stranded rows** via a
304
+ `BACKLOG_CUTOFF_DATE`, **deploy-inert** (`isActive = 0` + empty-URL guard), calibrating against
305
+ the **1.0** tier's cadences, the no-deploy **end-to-end + recovery test procedure**, and the
306
+ **activation-ordering trap** (absence criteria before first push = false incident). See
307
+ [Prudential OneUptime order-pipeline monitors](../../../clients/prudential/features/oneuptime-order-pipeline-monitors.md).
308
+ (mhammontree)
215
309
  - 2026-08-10 — Noted that worker2 gained its **first OneUptime API *reader*** (this doc covers
216
310
  the push-only half; OneUptime was previously push-only, "dumb reporter, smart monitor"). The
217
311
  reader polls open incidents and files ClickUp tasks — see
@@ -5,7 +5,7 @@ project: _Underscore
5
5
  client: shared
6
6
  type: standard
7
7
  status: active
8
- updated: 2026-08-10
8
+ updated: 2026-08-17
9
9
  owners: [jcardinal, mhammontree, dfranks]
10
10
  files: []
11
11
  related:
@@ -269,6 +269,22 @@ WHERE
269
269
 
270
270
  * If necessary, add comments to clarify complex sections of SQL code. Comments should be concise and informative.
271
271
  * Prefer to use the `#` character to begin a comment rather than `--`.
272
+ * **Do not write a prose preamble on a migration file.** Comment each statement with a
273
+ short description of what it does and why. A multi-paragraph narrative over mechanical
274
+ `INSERT`s is not documentation — it rots, it repeats the ticket, and it buries the SQL.
275
+ Rollout/runbook steps belong on the ClickUp ticket; a reusable pattern belongs in the
276
+ knowledge base; the migration file gets one line per statement.
277
+ * **This is not a licence to strip PHP DocBlocks.** DocBlocks on classes and methods remain
278
+ required (see DocBlocks below) and are a different artifact. A docblock that records *why*
279
+ a query is written a certain way — e.g. "`Customers` is `LEFT` joined on purpose so the
280
+ monitor can see rows the transmission crons cannot" — is load-bearing: it prevents a future
281
+ "cleanup" to `INNER JOIN` that would silently blind the monitor. Keep those.
282
+ * Do not comment a migration with a dependency warning about an earlier file **in the same
283
+ folder**. The dbchanges2 executor applies each folder's files in **alphabetical order**, so an
284
+ earlier `YYYY-MM-DD` file in that folder is guaranteed already applied. A **cross-folder**
285
+ dependency (e.g. a `Client_<Name>/` file relying on a `Core/` change) is a different matter —
286
+ those are separate executor passes against separate clusters, so noting the ordering there is
287
+ legitimate and should be kept.
272
288
 
273
289
  ```sql
274
290
  # Bad
@@ -832,5 +848,11 @@ See: 2.0/apps/worker2/features/cross-account-aws-access.md
832
848
 
833
849
  ## Change history
834
850
 
851
+ - 2026-08-17 — SQL Comments: banned prose preambles on migration files (one short description per
852
+ statement; runbooks go on the ticket, patterns in the knowledge base), while explicitly protecting
853
+ PHP DocBlocks that record *why* a query is shaped a certain way. Also scoped the "no
854
+ earlier-file dependency warning" rule to **within one folder** — `Core/` and `Client_<Name>/` are
855
+ separate executor passes against separate clusters, so a cross-folder ordering note is legitimate
856
+ and must be kept. (mhammontree)
835
857
  - 2026-08-10 — Added "AWS access (worker2)" section: new worker2 AWS code must use `_Component_Aws_Workloads` (instance-role → per-account `WorkloadsRuntime` STS assume-role); legacy static key grandfathered for existing paths only. (jcardinal)
836
858
  - 2026-08-01 — Recorded the Logs-cluster singular table-naming exception to the otherwise-plural table convention. (jcardinal)
@@ -3,5 +3,5 @@
3
3
  | Doc | Framework | Summary | Files |
4
4
  |-----|-----------|---------|-------|
5
5
  | [AIG Contract Reconciliation & Dealer Programs (STS_001 / SA_001)](features/contract-reconciliation.md) | 2.0 | How to reconcile an **AIG contract sales sheet** against the `Client_Aig` tenant, and the durable finding that the **Staples Advantage.com (`SA_001`) program do | _underscore/Model/Client/Entitlement.php, _underscore/Model/Aig/Entitlement.php, _underscore/Model/Client/Contact.php, _underscore/Model/Aig/Contact.php |
6
- | [AIG Entitlement Intake & SaleItem Code Resolution](features/entitlement-intake.md) | 2.0 | The `_Model_Aig_Entitlement::prePost` interceptor below is the code path for AIG protection-plan entitlements arriving as `api2` V2 JSON POSTs — and that path * | _underscore/Model/Aig/Entitlement.php, _underscore/Model/Aig/Unit.php, api2/Component/Api/V2/V2.php, api2/Config/sandbox-dev.ini, dbchanges2/Client_Aig/2026-06-18a - TRUE-79534 AIG SaleItem codes.sql, dbchanges2/Client_Aig/2026-08-17a - TRUE-80562 AIG API identifier overrides.sql |
6
+ | [AIG Entitlement Intake & SaleItem Code Resolution](features/entitlement-intake.md) | 2.0 | The `_Model_Aig_Entitlement::prePost` interceptor below is the code path for AIG protection-plan entitlements arriving as `api2` V2 JSON POSTs — and that path * | _underscore/Model/Aig/Entitlement.php, _underscore/Model/Aig/Unit.php, api2/Component/Api/V2/V2.php, api2/Config/sandbox-dev.ini, dbchanges2/Client_Aig/2026-06-18a - TRUE-79534 AIG SaleItem codes.sql, dbchanges2/Client_Aig/2026-08-17a - TRUE-80562 AIG API identifier overrides.sql, worker2/Worker/Monitor/Aig.php, api2/Controller/Index.php |
7
7
  | [AIG (Staples Protection Plan)](profile.md) | 2.0 | AIG is the warranty underwriter behind the **Staples Protection Plan** retail program. | |
@@ -14,7 +14,10 @@ files:
14
14
  - api2/Config/sandbox-dev.ini
15
15
  - dbchanges2/Client_Aig/2026-06-18a - TRUE-79534 AIG SaleItem codes.sql
16
16
  - dbchanges2/Client_Aig/2026-08-17a - TRUE-80562 AIG API identifier overrides.sql
17
+ - worker2/Worker/Monitor/Aig.php
18
+ - api2/Controller/Index.php
17
19
  related:
20
+ - 2.0/apps/worker2/features/oneuptime-worker2-monitoring.md
18
21
  - clients/aig/features/contract-reconciliation.md
19
22
  - 1.0/apps/library/features/toga2-api-client-and-bridge.md
20
23
  - 2.0/apps/api2/features/request-logging.md
@@ -379,6 +382,23 @@ When AIG sends a new "Active SaleItemID" spreadsheet (columns `SaleItemID`, `Des
379
382
  local pass is not evidence.** See the gotcha and
380
383
  [local vs prod MySQL config parity](../../../2.0/apps/dbchanges2/workflows/local-vs-prod-mysql-config-parity.md).
381
384
 
385
+ ## Monitoring the intake endpoint — the failure-scraper cannot see a dead endpoint
386
+
387
+ `Monitor/Aig/EntitlementApiFailures` (worker2 → OneUptime) counts **arrived-and-failed** intake
388
+ calls from the log tables. It is structurally blind to a **fully dead** endpoint: a refused
389
+ connection logs nothing, yields zero failure rows, and reads as healthy. TRUE-80587 therefore added
390
+ `Monitor/Aig/EntitlementApiHealth`, a **liveness probe** alongside it — an **unauthenticated GET**
391
+ (so it can never create a record) where **any structured HTTP response below 500 is a PASS**.
392
+
393
+ Verified 2026-08-17: `api-writer.togahub.com/v2/health` → **200**; unauthenticated
394
+ `/v2/entitlements` → **401**. The 401 is a *healthy* answer — it proves DNS, TLS, the load balancer,
395
+ routing, and the auth layer are all alive. `api2/Controller/Index.php`
396
+ `HEALTH_CHECK_ROUTES = ['/health','/v2/health']` short-circuits to 200 before any DB or Sentry work,
397
+ so the probe does not depend on `Client_Aig` being reachable.
398
+
399
+ See the shared pattern:
400
+ [OneUptime push-metric monitors for 2.0 workers](../../../2.0/apps/worker2/features/oneuptime-worker2-monitoring.md).
401
+
382
402
  ## Client variations
383
403
 
384
404
  This is AIG-specific behavior (the `_Model_Aig_Entitlement` override); other clients do not run
@@ -485,6 +505,10 @@ this interceptor or use this dual-purpose Items pattern.
485
505
  This is a general dbchanges2 temp-table-staging rule, not AIG-specific.
486
506
 
487
507
  ## Change history
508
+ - 2026-08-17 — TRUE-80587: added `Monitor/Aig/EntitlementApiHealth`, an unauthenticated-GET liveness
509
+ probe on the intake endpoint, because the existing `EntitlementApiFailures` log-scraper cannot see
510
+ a fully dead endpoint. Documented that <500 (including the unauthenticated **401**) is a PASS.
511
+ (mhammontree)
488
512
 
489
513
  - 2026-08-17 (later pass) — **Correction + final verification, supersedes two points in the entry
490
514
  below.** (1) The nested-`Units` "cross-tenant field list" open question is **WITHDRAWN** — the six
@@ -6,11 +6,12 @@ apps:
6
6
  - api2
7
7
  - dbchanges2
8
8
  - library
9
+ - worker2
9
10
  project: API
10
11
  client: aig
11
12
  type: profile
12
13
  status: active
13
- updated: 2026-07-28
14
+ updated: 2026-08-17
14
15
  owners: ["mhammontree"]
15
16
  files: []
16
17
  related:
@@ -36,6 +37,9 @@ go out under the Staples Protection Plan brand and link to `staplesprotection.to
36
37
  NetSuite item/SO/invoice traits).
37
38
  - **`dbchanges2`** — `Client_Aig/` schema + reference-data migrations (e.g. the SaleItem code
38
39
  catalog in `Client_Aig.Items`).
40
+ - **`worker2`** — OneUptime monitors: `Monitor/Aig/EntitlementApiFailures` (log-scraper) plus
41
+ `Monitor/Aig/EntitlementApiHealth` (liveness probe of the intake endpoint). See
42
+ features/entitlement-intake.md and the shared worker2 OneUptime monitoring doc.
39
43
  - **`library` (1.0)** — the `App_Api_Toga2` bridge syncs 2.0 AIG contacts/entitlements down into the
40
44
  legacy 1.0 `TOGA_AIG` DB (`Customers`/`ServiceRequests`), run by a worker cron every ~10 min. Note
41
45
  the two AIG databases: **`TOGA_AIG`** = V1 legacy sync target; **`Client_Aig`** = V2 prod tenant. See
@@ -3,5 +3,5 @@
3
3
  | Doc | Framework | Summary | Files |
4
4
  |-----|-----------|---------|-------|
5
5
  | [NYCDOE Ticket Hold-Status Sync (ServiceNow ⇄ TOGaDesk)](features/hold-status-sync.md) | 1.0 | DOE ticket **hold** status must round-trip between ServiceNow (SNOW) and TOGaDesk and **stay held** — holds are SLA-bearing in both systems. | worker/crons/sync/nycdoe/send_ticket_updates.php, worker/crons/sync/nycdoe/process_tickets.php, worker/crons/sync/nycdoe/send_request_item_updates.php, library/app/model/togadesk/repairorder.php, library/app/api/nycdoev2.php, togadesk/desk/includes/classes/class.repair.php |
6
- | [NYCDOE ServiceNow / ASN Integration](features/servicenow-integration.md) | 1.0 | The NYCDOE/ServiceNow integration mirrors DOE's ServiceNow tickets (Incidents + RITMs) into local tables, turns vendor shipment notices into NetSuite Sales Orde | worker/crons/sync/nycdoe/import_asn.php, worker/crons/sync/nycdoe/import_inc.php, worker/crons/sync/nycdoe/legacy_import_asn.php, worker/crons/sync/nycdoe/legacy_process_asn_queue.php, worker/crons/sync/nycdoe/process_tickets.php, worker/crons/sync/nycdoe/1_send_asn_to_netsuite.php, worker/crons/sync/nycdoe/2_send_serials_to_netsuite.php, worker/crons/sync/nycdoe/3_create_installation_ticket.php, worker/crons/sync/nycdoe/test_multi_po_receipt_resolution.php, worker/crons/sync/nycdoe/send_ticket_updates.php, worker/crons/sync/nycdoe/send_request_item_updates.php, worker/crons/sync/nycdoe/send_nycdoe_proof_of_delivery.php, worker/crons/sync/nycdoe/sync_nycdoe_locations.php, worker/crons/sync/nycdoe/receive_edi_purchase_orders.php, worker/crons/sync/nycdoe/send_edi_open_invoices.php, worker/crons/notifications/nycdoe/, worker/schedules/cron.worker.sync.json, worker/schedules/cron.worker.notification.json, library/app/api/nycdoe.php, library/app/api/nycdoev2.php, library/app/nycdoe.php, library/app/asnprocessor/manufacturer.php, library/app/asnprocessor/apple.php, library/app/asnprocessor/lenovo.php, library/app/asnprocessor/lexmark.php, library/app/asnprocessor/acer.php, library/app/edi.php, library/app/netsuite.php, dbchanges/Core/SK/, dbchanges/TOGaDeskSupport/SK/ |
6
+ | [NYCDOE ServiceNow / ASN Integration](features/servicenow-integration.md) | 1.0 | The NYCDOE/ServiceNow integration mirrors DOE's ServiceNow tickets (Incidents + RITMs) into local tables, turns vendor shipment notices into NetSuite Sales Orde | worker/crons/sync/nycdoe/import_asn.php, worker/crons/sync/nycdoe/import_inc.php, worker/crons/sync/nycdoe/legacy_import_asn.php, worker/crons/sync/nycdoe/legacy_process_asn_queue.php, worker/crons/sync/nycdoe/process_tickets.php, worker/crons/sync/nycdoe/1_send_asn_to_netsuite.php, worker/crons/sync/nycdoe/2_send_serials_to_netsuite.php, worker/crons/sync/nycdoe/3_create_installation_ticket.php, worker/crons/sync/nycdoe/test_multi_po_receipt_resolution.php, worker/crons/sync/nycdoe/send_ticket_updates.php, worker/crons/sync/nycdoe/send_request_item_updates.php, worker/crons/sync/nycdoe/send_nycdoe_proof_of_delivery.php, worker/crons/sync/nycdoe/sync_nycdoe_locations.php, worker/crons/sync/nycdoe/receive_edi_purchase_orders.php, worker/crons/sync/nycdoe/send_edi_open_invoices.php, worker/crons/notifications/nycdoe/, worker/schedules/cron.worker.sync.json, worker/schedules/cron.worker.notification.json, library/app/api/nycdoe.php, library/app/api/nycdoev2.php, library/app/nycdoe.php, library/app/asnprocessor/manufacturer.php, library/app/asnprocessor/apple.php, library/app/asnprocessor/lenovo.php, library/app/asnprocessor/lexmark.php, library/app/asnprocessor/acer.php, library/app/edi.php, library/app/netsuite.php, dbchanges/Core/SK/, dbchanges/TOGaDeskSupport/SK/, worker2/Worker/Monitor/Nycdoe.php |
7
7
  | [New York City Department of Education](profile.md) | 1.0 | NYC DOE (New York City Department of Education) is a TOGA client whose entire integration runs in the **1.0 worker tier** (~30 cron scripts under `worker/crons/ | |
@@ -6,7 +6,7 @@ project: Worker
6
6
  client: nycdoe
7
7
  type: client-feature
8
8
  status: active
9
- updated: 2026-08-14
9
+ updated: 2026-08-17
10
10
  owners: [mhammontree, sking]
11
11
  files:
12
12
  - worker/crons/sync/nycdoe/import_asn.php
@@ -39,8 +39,10 @@ files:
39
39
  - library/app/netsuite.php
40
40
  - dbchanges/Core/SK/
41
41
  - dbchanges/TOGaDeskSupport/SK/
42
+ - worker2/Worker/Monitor/Nycdoe.php
42
43
  related:
43
44
  - ../profile.md
45
+ - ../../../2.0/apps/worker2/features/oneuptime-worker2-monitoring.md
44
46
  - hold-status-sync.md
45
47
  - ../../../1.0/apps/worker/architecture.md
46
48
  - ../../../1.0/apps/dbchanges/workflows/authoring-and-shipping-sql-files.md
@@ -551,6 +553,28 @@ use the toga DB MCP + `Logs.API` instead of running prod code locally.
551
553
  `__get` throws "No field or quick query defined for key …". Read that column via **raw SQL**,
552
554
  not the model.
553
555
 
556
+ ## Monitoring the ServiceNow endpoint (worker2 liveness probe — TRUE-80587)
557
+
558
+ The five 1.0 `App_SystemMonitor_ServiceNow*` monitors scrape logs for **arrived-and-failed** calls,
559
+ so they are **structurally blind to a fully dead endpoint**: a refused connection logs nothing,
560
+ produces zero failure rows, and reads as healthy. `Monitor/Nycdoe/ServiceNowEndpointHealth`
561
+ (`worker2/Worker/Monitor/Nycdoe.php`, OneUptime push) is the complement — a **liveness probe** that
562
+ actually touches the endpoint.
563
+
564
+ - **Unauthenticated GET**, so it can never create a record.
565
+ - **Any structured HTTP response below 500 is a PASS** — it proves DNS, TLS, the load balancer,
566
+ routing, and the auth layer are alive. A `401` is a *healthy* answer, not a failure.
567
+ - **Accepted coverage gap: token validity is NOT monitored.** The OAuth client
568
+ `App_Api_NYCDOEV2` lives in the 1.0 `library` repo, which worker2 cannot reach. Re-implementing
569
+ it in worker2 would duplicate DOE's credentials into a second repo and race the shared access
570
+ token. An expired/revoked token will surface via the 1.0 failure monitors, not this probe — do
571
+ not "fix" this by forking the client.
572
+
573
+ This probe is the client's **only 2.0 footprint**. DOE is **1.0 by default** — `worker2` appears in
574
+ the client's `apps:` scope solely so this monitor loads, and its presence is **not** the start of a
575
+ 2.0 migration (there is no `Client_Nycdoe` 2.0 tenant). All business logic stays in the 1.0 tier. See
576
+ [OneUptime push-metric monitors for 2.0 workers](../../../2.0/apps/worker2/features/oneuptime-worker2-monitoring.md).
577
+
554
578
  ## Operating rules when changing this integration
555
579
 
556
580
  1. **Trace the full pipeline downstream before fixing** — the constraint that makes an edit
@@ -563,6 +587,10 @@ use the toga DB MCP + `Logs.API` instead of running prod code locally.
563
587
  of the consumer query; `php -l` every touched file.
564
588
 
565
589
  ## Change history
590
+ - 2026-08-17 — TRUE-80587: added `Monitor/Nycdoe/ServiceNowEndpointHealth` (worker2 → OneUptime), an
591
+ unauthenticated-GET liveness probe complementing the log-scraping 1.0 `App_SystemMonitor_ServiceNow*`
592
+ monitors, which cannot see a dead endpoint. Token validity is a deliberately accepted coverage gap
593
+ (`App_Api_NYCDOEV2` is 1.0-only). First 2.0 code for this client. (mhammontree)
566
594
  - 2026-08-14 — Tied the DOE **missing-asset-tag** class to the existing "Lenovo Off-Layout ASN File"
567
595
  bug: the +1 shift puts the serial in `assetTag` and means the real asset-tag column (`$cols[24]`)
568
596
  is **never read**, so no unit from an off-layout file can get a tag (still **open** — needs the
@@ -6,11 +6,12 @@ apps:
6
6
  - library
7
7
  - togadesk
8
8
  - dbchanges
9
+ - worker2
9
10
  project: Worker
10
11
  client: nycdoe
11
12
  type: profile
12
13
  status: active
13
- updated: 2026-07-29
14
+ updated: 2026-08-17
14
15
  owners: [mhammontree, sking]
15
16
  files: []
16
17
  related:
@@ -27,8 +28,13 @@ proof-of-delivery back to ServiceNow. A parallel EDI relationship (850 POs in, i
27
28
  runs over DOE's SFTP.
28
29
 
29
30
  ## Platforms & data
30
- - **1.0 worker tier only** — no 2.0 footprint. Local mirror tables live in `db_common`
31
+ - **1.0 worker tier** — all business logic. Local mirror tables live in `db_common`
31
32
  (`NYCDOETickets`, `NYCDOELocations`, `AdvanceShippingNotice*`).
33
+ - **`worker2` (2.0) — monitoring only.** DOE's *only* 2.0 code is the OneUptime liveness probe
34
+ `Monitor/Nycdoe/ServiceNowEndpointHealth` (`worker2/Worker/Monitor/Nycdoe.php`, TRUE-80587).
35
+ This client remains **1.0 by default** — `worker2` is in `apps:` solely so that monitor loads.
36
+ It is **not** the start of a 2.0 migration and there is no `Client_Nycdoe` 2.0 tenant; do not
37
+ read it as a platform move.
32
38
  - **TOGa Desk:** DOE is client id **16** (`db_togadesk`) — repair orders, managed service
33
39
  orders, and the "#received / #ordered" Receiving Summary UI.
34
40
  - **NetSuite:** Sales Orders, PO reconciliation, item receipts, Item Fulfillments, invoices
@@ -5,6 +5,7 @@
5
5
  | [Prudential: Dell ASN units PRE/POST interceptor (legacy key + flat tracking)](features/dell-asn-units-interceptor.md) | 2.0 | After the tracking-number bridge migration, the ASN unit route was renamed (`advance-shipping-notice-units` → `advance-shipping-notice-item-units`), so the inhe | _underscore/Model/Prudential/AdvanceShippingNotice.php, dbchanges2/Client_Prudential/2026-06-10 - AsnUnitsInterceptor.sql |
6
6
  | [Prudential: Dell LCH IOP transmissions (LCHRequestV2)](features/dell-lch-iop-transmissions.md) | 1.0 | Outbound order transmissions from the 1.0 worker tier to Dell's Lifecycle Hub (LCH) ITSM integration. | library/app/api/delllch.php, library/app/apitransaction.php, worker/crons/toga2/prudential/transmissions_to_dell_usa.php, worker/crons/toga2/prudential/transmissions_to_dell_ireland.php, worker/crons/toga2/prudential/transmissions_to_dell_india.php, worker/crons/toga2/prudential/generate_sales_and_purchase_orders_from_service_requests.php |
7
7
  | [Prudential: Device information import + unit→contact linking (import_device_information.php)](features/device-information-import-and-contact-linking.md) | 1.0 | Prudential's **device-sync** cron (`worker/crons/toga2/prudential/import_device_information.php`) pulls device/asset records (from ServiceNow / the Dell CMDB fe | worker/crons/toga2/prudential/import_device_information.php, worker/crons/toga2/prudential/backfill_unit_contacts.php, dbchanges2/Client_Prudential/2026-07-07 - Contact Dedup Merge.sql |
8
+ | [Prudential: OneUptime order-pipeline backlog monitors (Monitor/Prudential/*)](features/oneuptime-order-pipeline-monitors.md) | 2.0 | Three worker2 OneUptime push monitors (TRUE-80587) that watch the Prudential/Dell order pipeline for **stalls**, which were previously invisible — a stuck pipel | worker2/Worker/Monitor/Prudential.php, worker2/Config/production.ini, dbchanges2/Core/2026-08-07a - Prudential AIG ServiceNow OneUptime monitor crons.sql, dbchanges2/Client_Prudential/2026-04-08 - Insert Customers.sql |
8
9
  | [Prudential: Service Request Regional Address Validation](features/service-request-address-validation.md) | 2.0 | The `prePost` interceptor on `_Model_Prudential_ServiceRequest` validates `deliverToAddress` fields differently depending on which Prudential regional customer | _underscore/Model/Prudential/ServiceRequest.php, _underscore/Test/Prudential/ServiceRequestTest.php |
9
10
  | [Prudential: Service Request rejection alert email](features/service-request-rejection-alert-email.md) | 2.0 | When a Prudential ServiceNow→TOGa service-request submission (`POST /v2/service-requests`) is **rejected by validation**, TOGa now sends a real-time internal al | worker2/Worker/Client/Prudential/reports/ReqRejectionEmail.php, _underscore/Model/Prudential/ServiceRequest.php |
10
11
  | [Prudential Order Shipped Email — transmit_ordershipped_updates_prudential.php](features/transmit-ordershipped-email.md) | 1.0 | Cron script that transmits "Order Shipped" updates to ServiceNow (RITM) and sends a shipped notification email to the end user. | worker/crons/toga2/prudential/transmit_ordershipped_updates_prudential.php, worker/crons/toga2/prudential/transmit_closecomplete_updates_prudential.php, worker/crons/toga2/prudential/transmit_rejected_cancelled_updates_prudential.php, worker/crons/toga2/prudential/generate_sales_and_purchase_orders_from_service_requests.php, worker/crons/toga2/prudential_beta/generate_sales_and_purchase_orders_from_service_requests.php, worker/crons/notifications/reports/prudential_exception_report.php |
@@ -0,0 +1,108 @@
1
+ ---
2
+ title: "Prudential: OneUptime order-pipeline backlog monitors (Monitor/Prudential/*)"
3
+ framework: "2.0"
4
+ repo: worker2
5
+ project: Worker
6
+ client: prudential
7
+ type: client-feature
8
+ status: active
9
+ updated: 2026-08-17
10
+ owners: ["mhammontree"]
11
+ files:
12
+ - worker2/Worker/Monitor/Prudential.php
13
+ - worker2/Config/production.ini
14
+ - dbchanges2/Core/2026-08-07a - Prudential AIG ServiceNow OneUptime monitor crons.sql
15
+ - dbchanges2/Client_Prudential/2026-04-08 - Insert Customers.sql
16
+ related:
17
+ - ../../../2.0/apps/worker2/features/oneuptime-worker2-monitoring.md
18
+ - ../profile.md
19
+ - ./transmit-ordershipped-email.md
20
+ - ./dell-lch-iop-transmissions.md
21
+ - ../../../2.0/apps/worker2/features/service-request-sales-order-generation.md
22
+ ---
23
+
24
+ ## Summary
25
+
26
+ Three worker2 OneUptime push monitors (TRUE-80587) that watch the Prudential/Dell order pipeline
27
+ for **stalls**, which were previously invisible — a stuck pipeline produced no error, no email,
28
+ and no alert. They implement the shared pattern in
29
+ [OneUptime push-metric monitors for 2.0 workers](../../../2.0/apps/worker2/features/oneuptime-worker2-monitoring.md);
30
+ read that first for the payload contract, criteria set, and provisioning runbook.
31
+
32
+ - `Monitor/Prudential/SalesOrderGenerationBacklog` — Service Requests not yet turned into Sales Orders.
33
+ - `Monitor/Prudential/DellPurchaseOrderTransmissionBacklog` — POs not yet transmitted to Dell.
34
+ - `Monitor/Prudential/OrderShippedUpdateBacklog` — order-shipped updates not yet transmitted.
35
+
36
+ ## Key files / entry points
37
+
38
+ - `worker2/Worker/Monitor/Prudential.php` — `_Worker_Monitor_Prudential`; one `public static`
39
+ action method per monitor, each self-contained (in-method `ALL_CAPS` locals + `$push` closure).
40
+ - `dbchanges2/Core/2026-08-07a - Prudential AIG ServiceNow OneUptime monitor crons.sql` — the
41
+ `Core.CronJobs` rows (shipped `isActive = 0`, `parameters = '{"alertThreshold":N}'`).
42
+
43
+ ## How it works
44
+
45
+ ### The monitors read a 2.0 DB but watch 1.0 crons
46
+
47
+ Prudential's order-pipeline **business logic runs in the 1.0 worker tier** against the 2.0
48
+ `Client_Prudential` database. The monitors live in worker2 and only **READ** that database — the
49
+ same split as the Compass monitors. Thresholds are therefore calibrated against the **1.0 cron
50
+ cadences**, not worker2's own `*/5` schedule:
51
+
52
+ | 1.0 cron | Cadence | Throughput |
53
+ |---|---|---|
54
+ | `generate_sales_and_purchase_orders` | every 3 min | 1 request per run (≈20/hour) |
55
+ | `transmissions_to_dell_{usa,ireland,india}` | every 5 min | one region each |
56
+ | `transmit_ordershipped_updates_prudential` | every 2 min | — |
57
+
58
+ ### `BACKLOG_CUTOFF_DATE` — excluding permanently-stranded rows
59
+
60
+ `BACKLOG_CUTOFF_DATE = 2026-08-07`. Rows before it are excluded because they will **never** drain:
61
+
62
+ - ~38 Service Requests orphaned by the **2026-07-17 Dell regional-split cutover**.
63
+ - Two-phase order-shipped stranding — `transmit_ordershipped_updates_prudential` stamps the
64
+ transmitted column in Phase 1, so a Phase-2 crash strands that PO's email forever (see the
65
+ [profile gotchas](../profile.md) and [transmit-ordershipped-email](./transmit-ordershipped-email.md)).
66
+
67
+ Counting them would pin the monitor HIGH on its very first run, which trains people to ignore it.
68
+
69
+ ### Diagnostic fields that deliberately do NOT drive the alarm
70
+
71
+ `DellPurchaseOrderTransmissionBacklog` also reports:
72
+
73
+ - `strandedCount` — `ServiceRequests.customerId IS NULL`.
74
+ - `unmappedCustomerCount` — customer is not one of the three regional customer uuids.
75
+
76
+ Both are informational only. The three regional transmission crons **INNER JOIN `Customers` and
77
+ filter to a single region uuid each**, so these rows are invisible to *every* transmitter — they
78
+ are a data-integrity problem, not a transmission backlog, and alarming on them would misroute the
79
+ response.
80
+
81
+ **The monitor `LEFT JOIN`s `Customers` on purpose** so it can see rows the crons cannot. Changing
82
+ it to an `INNER JOIN` would silently blind the monitor — the DocBlock on the method says so, and
83
+ that docblock is load-bearing; do not "tidy" it away.
84
+
85
+ ## Reference data (verified 2026-08-17)
86
+
87
+ - The USA / Ireland / India regional customer uuids are seeded in
88
+ `dbchanges2/Client_Prudential/2026-04-08 - Insert Customers.sql` — **not** `2024-01-01.sql`, which
89
+ an in-code comment incorrectly claims.
90
+ - `Client_Prudential.Vendors.id = 1` is Dell.
91
+
92
+ ## Gotchas / known issues
93
+
94
+ - Thresholds are DB-tunable (`Core.CronJobs.parameters` → `{"alertThreshold":N}`) — retune there,
95
+ never by editing PHP. The JSON key must match the method's parameter name exactly.
96
+ - The crons ship `isActive = 0`; the monitors do nothing until activated, and each guards on an
97
+ empty push URL. Do not add OneUptime absence criteria until a push has landed.
98
+
99
+ ## Change history
100
+ - 2026-08-17 — TRUE-80587: built the three backlog monitors; set `BACKLOG_CUTOFF_DATE` to exclude
101
+ the ~38 Dell regional-split orphans and the two-phase order-shipped strandings; made
102
+ `strandedCount` / `unmappedCustomerCount` non-alarming diagnostics behind a deliberate
103
+ `LEFT JOIN Customers`; calibrated thresholds against the 1.0 cron cadences; corrected the
104
+ regional-customer-uuid seed-file reference to `2026-04-08 - Insert Customers.sql`. (mhammontree)
105
+
106
+ ## Related docs
107
+ - [OneUptime push-metric monitors for 2.0 workers](../../../2.0/apps/worker2/features/oneuptime-worker2-monitoring.md) — the shared pattern, payload contract, and runbook.
108
+ - [Prudential Financial profile](../profile.md)
@@ -15,9 +15,10 @@ client: prudential
15
15
  type: profile
16
16
  status: active
17
17
  updated: 2026-08-17
18
- owners: ["jcardinal", "rgirish", "bala"]
18
+ owners: ["jcardinal", "rgirish", "bala", "mhammontree"]
19
19
  files: []
20
20
  related:
21
+ - features/oneuptime-order-pipeline-monitors.md
21
22
  - features/dell-asn-units-interceptor.md
22
23
  - features/service-request-address-validation.md
23
24
  - features/service-request-rejection-alert-email.md
@@ -81,7 +82,14 @@ order-status transmissions.
81
82
  (E_ALL→ErrorException), so `!== false` guards do not work — use the `safeFetchPdf()` pattern. See
82
83
  the exception report and the transmit-ordershipped-email feature doc.
83
84
 
85
+ ## Monitoring
86
+ - `Monitor/Prudential/{SalesOrderGenerationBacklog,DellPurchaseOrderTransmissionBacklog,OrderShippedUpdateBacklog}`
87
+ (worker2 → OneUptime) watch the order pipeline for stalls. Thresholds are DB-tunable and the
88
+ monitors deliberately ignore pre-2026-08-07 permanently-stranded rows. See
89
+ features/oneuptime-order-pipeline-monitors.md.
90
+
84
91
  ## Related docs
92
+ - OneUptime order-pipeline backlog monitors (worker2).
85
93
  - Dell ASN units interceptor.
86
94
  - Dell LCH IOP transmissions (LCHRequestV2, outbound 1.0).
87
95
  - 2.0 _underscore: Tracking-Number Bridge Migration.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "toga-ai",
3
- "version": "1.0.597",
3
+ "version": "1.0.598",
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",