toga-ai 1.0.716 → 1.0.718
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.
|
@@ -6,7 +6,7 @@ project: API
|
|
|
6
6
|
client: shared
|
|
7
7
|
type: architecture
|
|
8
8
|
status: active
|
|
9
|
-
updated: 2026-
|
|
9
|
+
updated: 2026-09-01
|
|
10
10
|
owners: [jcardinal, bala, mhammontree, dfranks]
|
|
11
11
|
files:
|
|
12
12
|
- api2/Controller/Index.php
|
|
@@ -315,6 +315,52 @@ worker2 EB health monitor now applies a **persistence-delay gate** (a non-5xx `D
|
|
|
315
315
|
must persist ≥ `nonFivexxPersistMinutes`, default 5, before it pages), which suppresses these flaps.
|
|
316
316
|
See [worker2 EB health monitor](../worker2/features/elastic-beanstalk-health-monitor.md).
|
|
317
317
|
|
|
318
|
+
**A sixth cause — a 4xx BURST flips the env to `Degraded`/`Severe` while the ALB stays fully
|
|
319
|
+
healthy, because EB Enhanced Health counts application/ELB 4xx toward environment health.**
|
|
320
|
+
Distinct from every cause above: it is not a deploy (cause #4), not worker starvation (cause #3),
|
|
321
|
+
and not idle flappiness (cause #5) — it is EB reacting to *client* 4xx responses that are working
|
|
322
|
+
as designed. EB Enhanced Health's `ConfigDocument` (namespace
|
|
323
|
+
`aws:elasticbeanstalk:healthreporting:system`) ships with two rules **Enabled by default**:
|
|
324
|
+
`Rules.Environment.Application.ApplicationRequests4xx` and `Rules.Environment.ELB.ELBRequests4xx`.
|
|
325
|
+
So a short burst of client 4xx (e.g. a client polling with an expired bearer token) flips the env
|
|
326
|
+
to `Degraded`/`Severe` even though the ALB never marked a target unhealthy. Verified live (AWS CLI
|
|
327
|
+
`describe-events`/`describe-configuration-settings` + CloudWatch + prod `Logs.Api`, acct
|
|
328
|
+
`654654170868`):
|
|
329
|
+
- `api-production-1` (us-east-1) flip 2026-09-01 01:39–01:44 UTC, no cause cited ("TargetGroups
|
|
330
|
+
reduced health state"), **not** during a deploy. Throughout: `HealthyHostCount=2`,
|
|
331
|
+
`UnHealthyHostCount=0`, **0** target 5xx, **0** ELB 5xx, **0** connection errors — the ALB never
|
|
332
|
+
deregistered a target.
|
|
333
|
+
- Trigger burst ~5 min earlier at 01:34 UTC: `RequestCount` 738/min, ~560 of them 4xx (~77%),
|
|
334
|
+
`TargetResponseTime` max 5.2 s. Prod `Logs.Api` for the window: **398 × HTTP 401 on
|
|
335
|
+
`GET /v2/users/me`** from `sourceIp 34.232.23.158` (the Compass/Veyer feed) on `api1.togahub.com`,
|
|
336
|
+
split across both instances (`i-0fa76fc1365cdf583`, `i-068e89c284eb26133`) — a client polling with
|
|
337
|
+
an expired/invalid bearer token.
|
|
338
|
+
- `api-production-3` (eu-west-1) 2026-09-01 01:06 UTC stated it plainly: "Environment health
|
|
339
|
+
transitioned Ok→Severe: 80.7% of requests erroring with HTTP 4xx" — same root cause.
|
|
340
|
+
- Other causes ruled out: `/health` confirmed served as a **static Apache file** (response carries
|
|
341
|
+
`Last-Modified`/`ETag`/`Accept-Ranges`, body `OK`, `Content-Length 3`, no PHP) → cause #3 not in
|
|
342
|
+
play; `DeploymentPolicy=Rolling`, `BatchSize=1` Fixed confirmed → cause #4 not in play. This is
|
|
343
|
+
why the prior four fixes (culminating in Rolling/batch-1, which only addressed cause #4) never
|
|
344
|
+
stopped these flips — they are unrelated to deploys.
|
|
345
|
+
|
|
346
|
+
**Rule:** an EB "% 4xx" flag, or a no-cause "TargetGroup reduced health" flip, where
|
|
347
|
+
`HealthyHostCount` never drops and target/ELB **5xx = 0**, is EB Enhanced Health reacting to a 4xx
|
|
348
|
+
burst — **not** a backend failure. Check the ALB 4xx metric and `Logs.Api` for a client 4xx flood
|
|
349
|
+
before chasing app bugs.
|
|
350
|
+
|
|
351
|
+
**Fix shipped 2026-09-01 (config-only, all three prod envs).** Both 4xx rules were **disabled** on
|
|
352
|
+
`api-production-1/2/3` by updating the `ConfigDocument` option (namespace
|
|
353
|
+
`aws:elasticbeanstalk:healthreporting:system`) via `aws elasticbeanstalk update-environment
|
|
354
|
+
--option-settings`, setting `ApplicationRequests4xx.Enabled=false` and
|
|
355
|
+
`ELBRequests4xx.Enabled=false` while preserving every other rule and the `CloudWatchMetrics` block
|
|
356
|
+
(**5xx and latency rules stay ON, so real backend failures still page**). It is a config-only update
|
|
357
|
+
(~1–2 min "Updating", no instance replacement) and is **reversible** by flipping the flags back to
|
|
358
|
+
`true`. Verified after apply: all three Ready/Green/Ok with both 4xx rules `Enabled=false`. This is
|
|
359
|
+
the **server-side complement** to the worker2 monitor's non-5xx persistence-delay gate
|
|
360
|
+
(`nonFivexxPersistMinutes`, cause #5) — one de-sensitizes the alert, this stops EB from generating
|
|
361
|
+
the flip at all. (The dev ran the three `update-environment` commands; the harness auto-mode
|
|
362
|
+
classifier correctly blocks the assistant from mutating prod infra or self-granting that permission.)
|
|
363
|
+
|
|
318
364
|
**Triage aid — break an EB "% requests failing with HTTP 5xx" flag into ELB 502/503/504 to find
|
|
319
365
|
the cause:** **503** = no healthy backend (a deploy with no capacity); **504** = target timeout
|
|
320
366
|
(worker saturation); **502** = connection dropped mid-response (an `AllAtOnce` deploy killing
|
|
@@ -400,6 +446,20 @@ they are the known sharp edges. Do not re-discover these from scratch.
|
|
|
400
446
|
single-caller branches in `V2.php` as unverified until exercised directly.
|
|
401
447
|
|
|
402
448
|
## Change history
|
|
449
|
+
- 2026-09-01 — Recorded a **sixth EB-`Degraded` cause — a client 4xx BURST**: EB Enhanced Health
|
|
450
|
+
counts application/ELB 4xx toward env health via two default-Enabled `ConfigDocument` rules
|
|
451
|
+
(`ApplicationRequests4xx`, `ELBRequests4xx`, namespace
|
|
452
|
+
`aws:elasticbeanstalk:healthreporting:system`), so a short 4xx spike flips the env to
|
|
453
|
+
`Degraded`/`Severe` while the ALB stays fully healthy (`HealthyHostCount`=2, 0 target/ELB 5xx).
|
|
454
|
+
Verified on `api-production-1` (2026-09-01 01:39 UTC flip, not a deploy) from a 01:34 UTC burst of
|
|
455
|
+
398×HTTP 401 on `GET /v2/users/me` from the Compass/Veyer feed (`34.232.23.158`) polling with an
|
|
456
|
+
expired token; `api-production-3` stated it as "80.7% of requests erroring with HTTP 4xx". Ruled
|
|
457
|
+
out causes #3 (static-file `/health` confirmed) and #4 (Rolling/batch-1 confirmed) — which is why
|
|
458
|
+
the prior deploy-focused fixes never stopped it. Triage rule: no-cause/%-4xx flip + steady
|
|
459
|
+
`HealthyHostCount` + 0 5xx = a 4xx burst, check the ALB 4xx metric and `Logs.Api` first. Fixed
|
|
460
|
+
config-only on all three prod envs — disabled both 4xx rules via `update-environment
|
|
461
|
+
--option-settings` (5xx/latency rules kept ON), reversible; the server-side complement to the
|
|
462
|
+
worker2 monitor's non-5xx persistence-delay gate. (jcardinal)
|
|
403
463
|
- 2026-08-29 — Recorded a **fifth EB-`Degraded` cause**: a brief self-healing "no-cause reduced
|
|
404
464
|
health" flip on a low-traffic *idle* 2-instance env (e.g. `api-production-3`, eu-west-1) is EB
|
|
405
465
|
Enhanced Health flappiness (a single missed ALB `/health` check), distinct from the busy-env
|
|
@@ -6,7 +6,7 @@ project: Worker
|
|
|
6
6
|
client: shared
|
|
7
7
|
type: feature
|
|
8
8
|
status: active
|
|
9
|
-
updated: 2026-08-
|
|
9
|
+
updated: 2026-08-31
|
|
10
10
|
owners: ["jcardinal", "mhammontree", "bala"]
|
|
11
11
|
files:
|
|
12
12
|
- worker2/Worker/Monitor/Compass.php
|
|
@@ -65,9 +65,24 @@ Each measures one Compass/Office Depot integration metric and pushes a decided a
|
|
|
65
65
|
- **OfficeDepotEdiImportQueue** (S3) — counts files >10 min old in
|
|
66
66
|
`s3://agilant-as2/OfficeDepot/`, excluding `OUTBOX/`, `SENT/` and directory placeholders.
|
|
67
67
|
- **CompassSalesOrderTransmission** (DB) — approved SOs (both approval stages) with
|
|
68
|
-
`c_dtTransmittedToCompass` NULL (not yet transmitted to MITS).
|
|
68
|
+
`c_dtTransmittedToCompass` NULL (not yet transmitted to MITS). Alarms on **either**
|
|
69
|
+
`pendingCount > 10` **OR** `oldestPendingAgeMinutes > 180` (3h), so a single order stuck too
|
|
70
|
+
long pages even under the count threshold — this catches the 1.0 cron's
|
|
71
|
+
[poison-order starvation](../../../clients/compass-usa/features/mits-sales-order-transmission-alerting.md).
|
|
72
|
+
**Age is measured from the FINAL (stage-2) approval decision** (`ApprovalDecisions` stage 2
|
|
73
|
+
`dtDecision` where `isApproved = 1` — not stage 1, not order date; that is the "eligible to
|
|
74
|
+
transmit" clock). The query **excludes canceled orders** (`salesOrderStageId IS NULL OR
|
|
75
|
+
<> 8`), mirroring the transmit cron's WHERE, so an order canceled after approval (e.g.
|
|
76
|
+
SA136567, now stage 8) neither counts nor false-alarms forever — this also fixed a
|
|
77
|
+
pre-existing count mismatch vs. the cron. Config: `$ALARM_AGE_MINUTES = 180`; body echoes
|
|
78
|
+
`oldestPendingAgeMinutes` + `alarmAgeMinutesThreshold`.
|
|
69
79
|
- **CompassIncomingPurchaseOrders** (DB) — `SA%` SOs transmitted to MITS >4h ago (on/after
|
|
70
|
-
the 2026-04-30 cutoff) with no PO returned.
|
|
80
|
+
the 2026-04-30 cutoff) with no PO returned. (This supersedes the 1.0
|
|
81
|
+
`App_SystemMonitor_Compass` "orders returned from MITS" sub-check, which is a **latent
|
|
82
|
+
no-op**: it selects `so`/`number`/etc but then reads `$row['pendingCount']`, a column the
|
|
83
|
+
query never returns, so `$pendingCount` is always null and that sub-check silently always
|
|
84
|
+
reports OK. Verified 2026-08-31 in `library/app/systemmonitor/compass.php` ~lines 111-140,
|
|
85
|
+
TRUE-78712.)
|
|
71
86
|
- **CompassPurchaseOrderTransmissionsToVendors** (DB) — unsubmitted POs for monitored
|
|
72
87
|
vendors (Office Depot, Strategic Systems, Compass, Presidio) due today or earlier.
|
|
73
88
|
- **EmailedAsnImport** (mailbox) — counts inbox messages in the emailed-ASN mailbox via
|
|
@@ -415,6 +430,18 @@ check for another client.
|
|
|
415
430
|
monitors must pass the region (see [Cloud S3 helpers](../../_underscore/features/cloud-s3-helpers.md)).
|
|
416
431
|
|
|
417
432
|
## Change history
|
|
433
|
+
- 2026-08-31 — Added **age-based detection** to `CompassSalesOrderTransmission()`: it now alarms
|
|
434
|
+
on `pendingCount > 10` **OR** `oldestPendingAgeMinutes > 180` (new `$ALARM_AGE_MINUTES = 180`),
|
|
435
|
+
so one order stuck too long pages even under the count threshold — catching the 1.0 transmit
|
|
436
|
+
cron's poison-order starvation. Age is measured from the **stage-2** (final) approval decision,
|
|
437
|
+
not stage 1 or order date. The query now also **excludes canceled orders** (`salesOrderStageId
|
|
438
|
+
IS NULL OR <> 8`), mirroring the transmit cron's WHERE, fixing a pre-existing count mismatch and
|
|
439
|
+
a canceled order (SA136567) false-alarming forever. New body fields `oldestPendingAgeMinutes` +
|
|
440
|
+
`alarmAgeMinutesThreshold`; no OneUptime criteria change needed — the age alarm rides the
|
|
441
|
+
existing `"alarm":"HIGH"` token ("dumb reporter, smart monitor"). Also recorded that the 1.0
|
|
442
|
+
`App_SystemMonitor_Compass` "orders returned from MITS" sub-check is a **latent no-op** (reads
|
|
443
|
+
`$row['pendingCount']`, a column its query never selects — always reports OK; TRUE-78712),
|
|
444
|
+
superseded by `CompassIncomingPurchaseOrders`. worker2-only, php -l passes, not deployed. (jcardinal)
|
|
418
445
|
- 2026-08-28 — Made `EmailedAsnImport()` resilient to a transient IMAP TLS handshake timeout
|
|
419
446
|
that was flipping the monitor Offline and paging even though it self-recovered next run:
|
|
420
447
|
wrapped token+connect+count in an **in-run retry loop** (`MAX_CONNECT_ATTEMPTS = 3`,
|
|
@@ -6,7 +6,7 @@ client: compass-usa
|
|
|
6
6
|
type: client-feature
|
|
7
7
|
status: active
|
|
8
8
|
updated: 2026-08-31
|
|
9
|
-
owners: ["dfranks", "bala"]
|
|
9
|
+
owners: ["dfranks", "bala", "jcardinal"]
|
|
10
10
|
files:
|
|
11
11
|
- worker/crons/toga2/compass/workflow/1_transmit_compass_sales_orders_to_mits.php
|
|
12
12
|
- worker/crons/toga2/compasscanada/workflow/1_transmit_compassCanada_sales_orders_to_mits.php
|
|
@@ -100,6 +100,44 @@ Configuration is **never retroactive**: `shouldNotify()` fires on the first occu
|
|
|
100
100
|
escalation, or when the backed-off reminder interval elapses, and returns false for a window with
|
|
101
101
|
zero events. The first email lands on the **next live rejection**, not on existing history.
|
|
102
102
|
|
|
103
|
+
## Why the backlog happens — poison-order starvation (root cause)
|
|
104
|
+
|
|
105
|
+
The alert almost never fires on **volume**. It fires because one bad order **aborts the whole
|
|
106
|
+
run** and every un-processed order behind it starves. Diagnosed 2026-08-31 against a live
|
|
107
|
+
backlog (0 new, several >24h, one ~20 days old).
|
|
108
|
+
|
|
109
|
+
- The cron loops approved-but-untransmitted orders and `throw`s on any MITS rejection
|
|
110
|
+
(`App_Exception_Business` on a non-200, plain `Exception` on "No Items Found"). A throw
|
|
111
|
+
**ends the entire run**, so every order not yet reached that run stays untransmitted.
|
|
112
|
+
- The driver query uses **`ORDER BY RAND()`**, so *which* orders get processed before the bad
|
|
113
|
+
one is random each run. A stuck "poison" order therefore blocks a **different** set of good
|
|
114
|
+
orders every run — good orders can wait days behind one bad one.
|
|
115
|
+
- Throughput ceiling: the cron sleeps **30s per order** (86400s time limit), scheduled every
|
|
116
|
+
3 min but overlap-guarded, so it is effectively one long-running instance — ~110 orders/hr.
|
|
117
|
+
|
|
118
|
+
**Two rejection signatures seen in prod** (Logs.Issue id 24, issueKey
|
|
119
|
+
`COMPASS_SALES_ORDER_MITS_TRANSMIT_REJECTED`, ~2329 occurrences since 2026-08-03):
|
|
120
|
+
|
|
121
|
+
1. **HTTP 400 `The AssetType field is required`** — a line item's catalog Item has
|
|
122
|
+
`assetTypeId = NULL`, so the payload `assetType` is null and MITS rejects the **whole
|
|
123
|
+
order**. This was the active blocker (e.g. item `4U555A-S`; ~10 distinct NULL-assetType
|
|
124
|
+
items blocked 9 of 11 pending orders).
|
|
125
|
+
2. **HTTP 403 `SalesOrder:Exists`** — MITS already created the order on a prior attempt, but
|
|
126
|
+
our side never recorded `c_dtTransmittedToCompass`, so the cron **retries it forever**.
|
|
127
|
+
|
|
128
|
+
**Fix the data:** assign the correct `assetTypeId` to the affected `Client_Compass.Items`
|
|
129
|
+
(business owns the mapping), and check whether the NetSuite item sync owns `assetType` — if it
|
|
130
|
+
does, it will revert the fix unless the source is corrected too.
|
|
131
|
+
|
|
132
|
+
**Recommended code hardening (NOT yet implemented):** wrap each order's transmit in
|
|
133
|
+
`try/catch` so one MITS rejection raises the business issue but **continues to the next
|
|
134
|
+
order** instead of aborting the batch. This removes the starvation entirely.
|
|
135
|
+
|
|
136
|
+
The worker2 OneUptime monitor now catches this starvation directly: `CompassSalesOrderTransmission`
|
|
137
|
+
alarms on `pendingCount > 10` **OR** `oldestAgeMinutes > 180`, so a single order stuck too long
|
|
138
|
+
pages even when the count is under threshold. See
|
|
139
|
+
[OneUptime push-metric monitors for 2.0 workers](../../../2.0/apps/worker2/features/oneuptime-worker2-monitoring.md).
|
|
140
|
+
|
|
103
141
|
## Gotchas / known issues
|
|
104
142
|
|
|
105
143
|
- **Canada is the pre-2026-08-03 design, and it still carries the two problems that migration
|
|
@@ -131,6 +169,14 @@ zero events. The first email lands on the **next live rejection**, not on existi
|
|
|
131
169
|
when this issue next fires. Watch that run rather than assuming it works.
|
|
132
170
|
|
|
133
171
|
## Change history
|
|
172
|
+
- 2026-08-31 — Diagnosed the recurring backlog's **root cause**: not volume but **poison-order
|
|
173
|
+
starvation**. One MITS rejection `throw`s and aborts the whole run, and because the driver
|
|
174
|
+
query uses `ORDER BY RAND()`, a stuck order blocks a random set of good orders every run.
|
|
175
|
+
Recorded the two live rejection signatures (`assetTypeId = NULL` → HTTP 400 "The AssetType
|
|
176
|
+
field is required"; already-created order → HTTP 403 "SalesOrder:Exists" retried forever),
|
|
177
|
+
the data fix (assign `assetTypeId`; check NetSuite item sync ownership), and the recommended
|
|
178
|
+
per-order `try/catch` hardening (not yet implemented). Cross-linked the new worker2 age-based
|
|
179
|
+
monitor. (jcardinal)
|
|
134
180
|
- 2026-08-31 — Added `vburks@togatech.com` alongside `devteam@togatech.com` on **every** in-cron
|
|
135
181
|
`App_Email_Agilant` alert in both MITS transmit crons (USA has one, Canada has three), so the
|
|
136
182
|
business stakeholder is alerted on transmission failures. Broadened this doc to cover **both**
|
package/package.json
CHANGED