toga-ai 1.0.794 → 1.0.795
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/api2/features/request-logging.md +37 -1
- package/knowledge/INDEX.md +1 -1
- package/knowledge/clients/prudential/INDEX.md +1 -0
- package/knowledge/clients/prudential/features/service-request-auth-rejection-alert.md +156 -0
- package/knowledge/clients/prudential/features/service-request-rejection-alert-email.md +12 -1
- package/knowledge/clients/prudential/profile.md +12 -2
- package/package.json +1 -1
|
@@ -6,7 +6,7 @@ project: API
|
|
|
6
6
|
client: shared
|
|
7
7
|
type: feature
|
|
8
8
|
status: active
|
|
9
|
-
updated: 2026-09-
|
|
9
|
+
updated: 2026-09-10
|
|
10
10
|
owners: ["mhammontree", "dfranks", "bala", "jcardinal"]
|
|
11
11
|
files:
|
|
12
12
|
- api2/Component/Api/V2/V2.php
|
|
@@ -21,6 +21,7 @@ related:
|
|
|
21
21
|
- ./environment-variable-drives-underscore-branch.md
|
|
22
22
|
- ../../_underscore/features/error-reporting-issue-event.md
|
|
23
23
|
- ../../../../clients/aig/features/entitlement-intake.md
|
|
24
|
+
- ../../../../clients/prudential/features/service-request-auth-rejection-alert.md
|
|
24
25
|
- ../../../../clients/compass-usa/features/mits-po-to-so-item-linking.md
|
|
25
26
|
- ../../../../clients/compass-usa/workflows/order-lifecycle-and-data-integrity.md
|
|
26
27
|
---
|
|
@@ -53,6 +54,24 @@ In `api2/Component/Api/V2/V2.php` (~L2168) every request is logged and routed:
|
|
|
53
54
|
- There is also a **config-gated branch** (~L2166): when `_Config::api('log_filepath')` is set,
|
|
54
55
|
the log entry is written to a **file** on the host instead of the DB.
|
|
55
56
|
|
|
57
|
+
### 401 lands in base `Logs`; 403 lands in `Logs_<Client>`
|
|
58
|
+
|
|
59
|
+
The pre-scope/post-scope split above has a sharp, practical edge on the two auth codes:
|
|
60
|
+
|
|
61
|
+
- **401** (`EN-1`..`EN-6`, e.g. an expired token) is decided in the auth chain in `V2.php`
|
|
62
|
+
(~L2088, inside `execute()`) **before routing**, so the client is not resolved and the row can
|
|
63
|
+
only go to the **shared base `Logs`**.
|
|
64
|
+
- **403** happens **after** the client IS known, so that row lands in **`Logs_<Client>`**.
|
|
65
|
+
|
|
66
|
+
Verified on Prudential for 2026-08-31 – 09-01: `Logs_Prudential.Api` held **56** rows for
|
|
67
|
+
`/v2/service-requests`, **all 201**, while every one of the **36 401s** sat in base `Logs`.
|
|
68
|
+
|
|
69
|
+
**A 401 therefore can never reach a model interceptor** — no `prePost`/`postPost` hook can alert on
|
|
70
|
+
it, because routing and the model are never reached. Anything that must react to refused inbound
|
|
71
|
+
traffic has to **scan the base log**, not hook the model. The caller is still identifiable: decode
|
|
72
|
+
the Bearer token in `requestHeaders` and read `id.client.uuid`. See
|
|
73
|
+
[Prudential refused-REQ alert](../../../../clients/prudential/features/service-request-auth-rejection-alert.md).
|
|
74
|
+
|
|
56
75
|
### A rejected request (HTTP 400) is NOT unlogged
|
|
57
76
|
|
|
58
77
|
`api2/Controller/Index.php` (L369-377) handles a non-success response by **committing** the
|
|
@@ -359,6 +378,13 @@ The working pattern, in order:
|
|
|
359
378
|
> a field its response echo omitted (see the [V2 query contract](v2-rest-query-contract.md) PUT-echo
|
|
360
379
|
> gotcha).
|
|
361
380
|
|
|
381
|
+
> **What is actually indexed (checked with `SHOW INDEX`, 2026-09-10):** `dtStamp` **IS** indexed
|
|
382
|
+
> — `Api_dtStamp_IDX` — in **both** the base `Logs` (~3.67M rows) and `Logs_Prudential` (~7.86M
|
|
383
|
+
> rows). `route` and `responseCode` are **NOT** indexed. So a *tight* `dtStamp` window is cheap
|
|
384
|
+
> (a 15-minute slice is trivial), but a **wide** `dtStamp` range combined with a `route` filter
|
|
385
|
+
> still times out — a 2.5-month grouped query aborted at 300 s. Narrow the time window first;
|
|
386
|
+
> the other predicates do no work for you.
|
|
387
|
+
|
|
362
388
|
1. **Narrow with indexed / non-text columns only** — a `dtStamp` range plus `route`,
|
|
363
389
|
`method`, `responseCode`, `hostname`. A
|
|
364
390
|
`GROUP BY hostname, route, method, responseCode` over one day is cheap and is the fastest way
|
|
@@ -400,6 +426,16 @@ re-send.
|
|
|
400
426
|
> replay operationally, not by pasting payloads into the KB.
|
|
401
427
|
|
|
402
428
|
## Change history
|
|
429
|
+
- 2026-09-10 — Sharpened the client-vs-core routing on the auth codes: a **401 always lands in the
|
|
430
|
+
base `Logs`** (decided in the auth chain ~L2088 before routing, client unresolved) while a **403
|
|
431
|
+
lands in `Logs_<Client>`** (client already known) — verified on Prudential 31 Aug–1 Sep, where
|
|
432
|
+
`Logs_Prudential.Api` held 56 rows for `/v2/service-requests` (all 201) and all **36 401s** were
|
|
433
|
+
in base `Logs`. Consequence: **no model interceptor can ever fire for a 401**, so alerting on
|
|
434
|
+
refused inbound traffic must scan the base log; the caller is recoverable by decoding
|
|
435
|
+
`id.client.uuid` from the Bearer token. Also corrected the index picture with `SHOW INDEX`:
|
|
436
|
+
`Logs.Api.dtStamp` **is** indexed (`Api_dtStamp_IDX`) in both base `Logs` (~3.67M rows) and
|
|
437
|
+
`Logs_Prudential` (~7.86M rows), but `route`/`responseCode` are **not** — a tight window is cheap,
|
|
438
|
+
a wide window plus a route filter still aborts at 300 s. No code change. (bala)
|
|
403
439
|
- 2026-09-01 — **Fixed the duplicate-`transactionId` 500** (`Logs.Issue CT`, `TRUE-81056` /
|
|
404
440
|
`db94a26`): the log-save at the end of `execute()` did a **check-then-insert**, which cannot work
|
|
405
441
|
under concurrency because the rival copy's row is still uncommitted and therefore invisible to the
|
package/knowledge/INDEX.md
CHANGED
|
@@ -5,7 +5,7 @@ _Auto-generated by `knowledge.js index`. Do not hand-edit._
|
|
|
5
5
|
## 1.0 framework
|
|
6
6
|
|
|
7
7
|
- **library** (Library) _(framework core)_ — 25 doc(s) → [1.0/apps/library/INDEX.md](1.0/apps/library/INDEX.md)
|
|
8
|
-
- **worker** (Worker) —
|
|
8
|
+
- **worker** (Worker) — 35 doc(s) → [1.0/apps/worker/INDEX.md](1.0/apps/worker/INDEX.md)
|
|
9
9
|
- **dbchanges** (Database Changes) _(framework core)_ — 1 doc(s) → [1.0/apps/dbchanges/INDEX.md](1.0/apps/dbchanges/INDEX.md)
|
|
10
10
|
- **worker1.5** (Worker 1.5) — 0 doc(s) → [1.0/apps/worker1.5/INDEX.md](1.0/apps/worker1.5/INDEX.md)
|
|
11
11
|
- **togadesk** (TOGa Desk) — 13 doc(s) → [1.0/apps/togadesk/INDEX.md](1.0/apps/togadesk/INDEX.md)
|
|
@@ -8,6 +8,7 @@
|
|
|
8
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 |
|
|
9
9
|
| [Prudential: order email PDF attachments (which guide goes on which email)](features/order-email-pdf-attachments.md) | 1.0 | Every customer-facing Prudential order email carries S3-hosted PDF guides, and **which** guide is attached is decided by the service request type. |
|
|
10
10
|
| [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 |
|
|
11
|
+
| [Prudential: refused REQ alert (401/403 on POST /v2/service-requests)](features/service-request-auth-rejection-alert.md) | 1.0 | ServiceNow posts every Prudential REQ to `POST /v2/service-requests`. |
|
|
11
12
|
| [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 |
|
|
12
13
|
| [Prudential: Sales Orders CSV export (TOGa 2.5 Supply)](features/supply-orders-csv-export.md) | 2.0 | The **Export** button on the TOGa 2.5 Supply *Sales Orders* page is **Prudential-only**: it downloads a 22-column CSV (`prudential-orders-<YYYY-MM-DD>.csv`) cov |
|
|
13
14
|
| [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. |
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: "Prudential: refused REQ alert (401/403 on POST /v2/service-requests)"
|
|
3
|
+
framework: "1.0"
|
|
4
|
+
repo: worker
|
|
5
|
+
project: Worker
|
|
6
|
+
client: prudential
|
|
7
|
+
type: client-feature
|
|
8
|
+
status: active
|
|
9
|
+
updated: 2026-09-10
|
|
10
|
+
owners: ["bala"]
|
|
11
|
+
files:
|
|
12
|
+
- worker/crons/notifications/reports/prudential/prudential_auth_rejection_alert.php
|
|
13
|
+
- worker/schedules/cron.worker.notification.json
|
|
14
|
+
- worker/crons/notifications/reports/prudential_exception_report.php
|
|
15
|
+
- worker/crons/notifications/reports/prudential/prudential_api_500_retry.php
|
|
16
|
+
related:
|
|
17
|
+
- service-request-rejection-alert-email.md
|
|
18
|
+
- service-request-address-validation.md
|
|
19
|
+
- ../profile.md
|
|
20
|
+
- ../../../2.0/apps/api2/features/request-logging.md
|
|
21
|
+
- ../../../2.0/apps/api2/features/v2-api-error-codes.md
|
|
22
|
+
---
|
|
23
|
+
|
|
24
|
+
## Summary
|
|
25
|
+
|
|
26
|
+
ServiceNow posts every Prudential REQ to `POST /v2/service-requests`. When we **refuse** that
|
|
27
|
+
request at the door — **401** (login token bad or expired) or **403** (permissions) — no
|
|
28
|
+
ServiceRequest row is created, no interceptor runs, and **no existing report shows it**. The REQ
|
|
29
|
+
simply does not exist on our side and nobody is told. The cron
|
|
30
|
+
`worker/crons/notifications/reports/prudential/prudential_auth_rejection_alert.php` closes that
|
|
31
|
+
gap: every 15 minutes it reads refused inbound REQs straight from the API logs and emails them
|
|
32
|
+
with the full payload, so they can be resubmitted the same hour instead of being found weeks later.
|
|
33
|
+
|
|
34
|
+
This is the **auth-layer** sibling of the 2.0 validation alert
|
|
35
|
+
([service-request-rejection-alert-email.md](service-request-rejection-alert-email.md)), which
|
|
36
|
+
covers 400s. The two never overlap: a 400 reaches the model, a 401 never does.
|
|
37
|
+
|
|
38
|
+
## Why it exists (the 2026-08/09 incident)
|
|
39
|
+
|
|
40
|
+
REQ4492868 was reported missing. It **did** reach us on 2026-08-31 09:45:18 (POST
|
|
41
|
+
`/v2/service-requests`, BREAKFIX, RITM6763537) and came back **401 `EN-4` "The provided token has
|
|
42
|
+
expired"**. ServiceNow (sourceIp `199.91.136.12`) collected a JWT at 07:40:47 that expired at
|
|
43
|
+
08:40:47 (1-hour life) and then kept reusing that same dead token for hours — every refused row
|
|
44
|
+
carries the **identical `exp`**. **36 REQs** were refused this way across 31 Aug and 1 Sep. 14 were
|
|
45
|
+
later resent and created fine (the Reclaims, replayed 1 Sep ~15:00); **22, including REQ4492868,
|
|
46
|
+
were never resent and are still missing.** The whole class of failure was silent for a week.
|
|
47
|
+
|
|
48
|
+
## Why nothing else caught it
|
|
49
|
+
|
|
50
|
+
- `worker/crons/notifications/reports/prudential_exception_report.php` (daily, 9:00 AM) only queries
|
|
51
|
+
response codes **201, 400 and 500**, and only against **`Logs_Prudential`** (`db_prod_logs_prudential`).
|
|
52
|
+
- `prudential_api_500_retry.php` only queries **500**.
|
|
53
|
+
|
|
54
|
+
So **401 and 403 were the two codes nobody was looking at** — and a 401 is not even in the schema
|
|
55
|
+
those reports read. This is also why the new cron cannot double-report: no other job touches these
|
|
56
|
+
codes.
|
|
57
|
+
|
|
58
|
+
## Where a refused request is actually logged (the load-bearing fact)
|
|
59
|
+
|
|
60
|
+
- **401 → the SHARED BASE `Logs` schema** (`db_prod_logs`), **not** `Logs_Prudential`. The
|
|
61
|
+
expired-token check runs in the auth chain in `api2/Component/Api/V2/V2.php` (~L2088, inside
|
|
62
|
+
`execute()`) **before** routing, so the client is not resolved yet and the row cannot be written
|
|
63
|
+
to the client log schema.
|
|
64
|
+
- **403 → `Logs_Prudential`.** By then the client **is** known.
|
|
65
|
+
|
|
66
|
+
Verified for 31 Aug – 1 Sep: `Logs_Prudential.Api` holds **56 rows** for `/v2/service-requests`,
|
|
67
|
+
**all 201**; every one of the **36 401s** is in base `Logs`.
|
|
68
|
+
|
|
69
|
+
**Consequence: a model `prePost` / interceptor can NEVER fire for a 401** — the request never
|
|
70
|
+
reaches routing or the model. That is why this alert had to be a log scan and not a hook. The
|
|
71
|
+
client is still identifiable: decode the expired Bearer token and read `id.client.uuid`.
|
|
72
|
+
|
|
73
|
+
## How it works
|
|
74
|
+
|
|
75
|
+
1. Floor the clock to the quarter hour and read exactly `[end - 15 min, end)`.
|
|
76
|
+
2. Read **401s from base `Logs`** (`db_prod_logs`) and **403s from `Logs_Prudential`**
|
|
77
|
+
(`db_prod_logs_prudential`), both filtered to `direction = 'IN'`, `method = 'POST'`,
|
|
78
|
+
`route = '/v2/service-requests'`.
|
|
79
|
+
3. Decode the Bearer token in `requestHeaders` to confirm the caller is Prudential
|
|
80
|
+
(`App_Api_Toga2::CLIENT_UUID_PRUDENTIAL`). A **missing** token yields no uuid — those rows are
|
|
81
|
+
**still reported**, deliberately, rather than dropped.
|
|
82
|
+
4. Map the `EN-*` code from the response payload to a plain-English sentence
|
|
83
|
+
(`EN-1` no token sent · `EN-2` unreadable · `EN-3` not valid · `EN-4` expired · `EN-5` altered ·
|
|
84
|
+
`EN-6` address not allowed), and classify the row as **Login** (401) or **Permissions** (403).
|
|
85
|
+
5. Email one grouped HTML table — REQ number, RITM number, type, time raised in ServiceNow, time it
|
|
86
|
+
reached us, reason — plus a **CSV attachment whose last column carries the full request
|
|
87
|
+
payload**, so the mail can be forwarded to Prudential as proof of what they sent.
|
|
88
|
+
6. Send **nothing** on a quiet run. Cap of `MAX_ROWS_PER_RUN = 500` so one bad hour cannot produce a
|
|
89
|
+
giant email.
|
|
90
|
+
|
|
91
|
+
Schedule entry in `worker/schedules/cron.worker.notification.json`: `"*/15 * * * *"` →
|
|
92
|
+
`notifications/reports/prudential/prudential_auth_rejection_alert.php` (notification worker role).
|
|
93
|
+
Recipients: `vburks`, `rgirish`, `ADiamond`, `kmaramreddy` @togatech.com.
|
|
94
|
+
|
|
95
|
+
## Design decision — no state table, no state file (clock-aligned window instead)
|
|
96
|
+
|
|
97
|
+
The first design kept a "last processed log id" marker. Both marker forms were rejected:
|
|
98
|
+
|
|
99
|
+
- **A file marker is wrong here.** The worker cache folder is `/var/www/cache`, **local to each
|
|
100
|
+
Elastic Beanstalk instance** and recreated on every deploy (see
|
|
101
|
+
`worker/ebs/setup_export_cache_folders.php`). The same rejections would be emailed once *per
|
|
102
|
+
instance*, and the marker is lost on every deploy.
|
|
103
|
+
- **A DB marker table** was drafted, then rejected as too much machinery for the value.
|
|
104
|
+
|
|
105
|
+
**Final design:** each run floors the clock to the quarter hour and reads `[end - 15 min, end)`.
|
|
106
|
+
Consecutive slices touch but never overlap, so no row can be read twice — **with no state at all.**
|
|
107
|
+
|
|
108
|
+
**Known trade-off, accepted:** a skipped run (deploy, restart) loses that 15-minute slice. Do not
|
|
109
|
+
"fix" this by re-adding a marker without re-reading the two reasons above.
|
|
110
|
+
|
|
111
|
+
## Gotchas
|
|
112
|
+
|
|
113
|
+
- **Filter `method = 'POST'` or you get browser noise.** Unauthenticated **GET**s on this route from
|
|
114
|
+
human sourceIps (`99.179.145.25`, `107.139.5.190`) also log 401, with an **empty `requestPayload`**
|
|
115
|
+
— they show up as REQ "Unknown". Only a POST is a real ServiceNow submission.
|
|
116
|
+
- **`prudential_api_500_retry.php` is broken and has almost certainly never worked.** At lines 57
|
|
117
|
+
and 70 it references a bare constant **`PRUDENTIAL_CLIENT_UUID` that is defined nowhere** in any
|
|
118
|
+
repo (grep returns only its own two uses); it should be `App_Api_Toga2::CLIENT_UUID_PRUDENTIAL`.
|
|
119
|
+
On PHP 8 an undefined constant is a **fatal Error**, so the cron dies every run; on PHP 7 it
|
|
120
|
+
evaluates to the literal string and the client check never matches, so it silently retries
|
|
121
|
+
nothing. It also appears in **no** `schedules/*.json` file, so it may never have been registered.
|
|
122
|
+
**Not fixed as of 2026-09-10 — reported only.**
|
|
123
|
+
- **`Logs.Api.dtStamp` IS indexed** (`Api_dtStamp_IDX`) in both base `Logs` and `Logs_Prudential`,
|
|
124
|
+
which is what makes the 15-minute window read cheap. But `route` and `responseCode` are **not**
|
|
125
|
+
indexed, so a wide `dtStamp` range plus a route filter still times out (a 2.5-month grouped query
|
|
126
|
+
aborted at 300 s). Keep the window tight. See
|
|
127
|
+
[request-logging.md](../../../2.0/apps/api2/features/request-logging.md).
|
|
128
|
+
|
|
129
|
+
## Telling "we refused it" from "they never sent it"
|
|
130
|
+
|
|
131
|
+
Two different failure shapes get reported the same way ("REQ is missing"):
|
|
132
|
+
|
|
133
|
+
- **Refused by us** — there IS an inbound POST row on `/v2/service-requests` with a 401/403 and a
|
|
134
|
+
full `requestPayload`. That is REQ4492868. Prudential must resubmit.
|
|
135
|
+
- **Never sent** — no inbound row at all. That is **REQ4398272**: absent from
|
|
136
|
+
`Client_Prudential.ServiceRequests` under both `c_reqNumber` and `c_ritmNumber`, and absent from
|
|
137
|
+
`Logs_Prudential.Api` (1 May – 15 Jul) and base `Logs.Api` (10–16 May) — no POST, no 401, no 400,
|
|
138
|
+
no 500. By the surrounding REQ numbers it would have been raised 2026-05-12 between 11:23 and
|
|
139
|
+
12:53 (REQ4398185 landed 11:23, REQ4398357 landed 12:53). Base `Logs` retention reaches back to
|
|
140
|
+
Aug 2025, so the window is genuinely covered. The problem is on the ServiceNow side.
|
|
141
|
+
|
|
142
|
+
Always check **both** schemas before answering — a 401 will not be in `Logs_Prudential`.
|
|
143
|
+
|
|
144
|
+
## Change history
|
|
145
|
+
- 2026-09-10 — Created. New 15-minute cron
|
|
146
|
+
`notifications/reports/prudential/prudential_auth_rejection_alert.php` alerting on Prudential REQs
|
|
147
|
+
refused with **401 (base `Logs`) or 403 (`Logs_Prudential`)**, with an HTML table plus a
|
|
148
|
+
full-payload CSV for forwarding to Prudential. Found after REQ4492868 was reported missing: it was
|
|
149
|
+
refused 2026-08-31 09:45:18 with `EN-4` (expired token), one of **36** refused across 31 Aug–1 Sep
|
|
150
|
+
from a JWT ServiceNow collected at 07:40:47 and reused past its 08:40:47 expiry — **22 still
|
|
151
|
+
missing**. Recorded why nothing caught it (the daily exception report reads only 201/400/500 and
|
|
152
|
+
only `Logs_Prudential`), why a `prePost` hook cannot work for a 401, the **no-state clock-aligned
|
|
153
|
+
window** decision (the `/var/www/cache` marker is per-instance and dies on deploy), the
|
|
154
|
+
**POST-only** filter that excludes browser 401 noise, that `dtStamp` is indexed while
|
|
155
|
+
`route`/`responseCode` are not, and that `prudential_api_500_retry.php` is dead on an undefined
|
|
156
|
+
`PRUDENTIAL_CLIENT_UUID` constant. (bala)
|
|
@@ -6,13 +6,14 @@ project: Worker
|
|
|
6
6
|
client: prudential
|
|
7
7
|
type: client-feature
|
|
8
8
|
status: active
|
|
9
|
-
updated: 2026-
|
|
9
|
+
updated: 2026-09-10
|
|
10
10
|
owners: ["bala"]
|
|
11
11
|
files:
|
|
12
12
|
- worker2/Worker/Client/Prudential/reports/ReqRejectionEmail.php
|
|
13
13
|
- _underscore/Model/Prudential/ServiceRequest.php
|
|
14
14
|
related:
|
|
15
15
|
- service-request-address-validation.md
|
|
16
|
+
- service-request-auth-rejection-alert.md
|
|
16
17
|
- ../../../2.0/apps/_underscore/features/email-send-pipeline.md
|
|
17
18
|
- ../profile.md
|
|
18
19
|
---
|
|
@@ -79,11 +80,21 @@ known residual risk.
|
|
|
79
80
|
validation block completes (e.g. the unguarded `serviceRequestUnits[0]->unit` access — see
|
|
80
81
|
`service-request-address-validation.md`) produces a **500 and no alert**. Fix that guard to
|
|
81
82
|
keep the alert reliable.
|
|
83
|
+
- **This alert only covers 400s — a 401/403 never reaches `prePost` at all.** An expired or bad
|
|
84
|
+
ServiceNow token is refused in the auth chain *before* routing, so the model never runs and no
|
|
85
|
+
interceptor can fire; the row is also logged to the **base `Logs`** schema, not `Logs_Prudential`.
|
|
86
|
+
Those refusals are alerted separately by the 1.0 worker cron in
|
|
87
|
+
[service-request-auth-rejection-alert.md](service-request-auth-rejection-alert.md). Do not extend
|
|
88
|
+
this interceptor to try to catch them.
|
|
82
89
|
- **"Plain text" is transmitted HTML anyway.** `setIsHtml(false)` is honored by `_Email` when it
|
|
83
90
|
queues the row, but the worker2 `Infrastructure/Email/Send` action calls `IsHTML(true)`
|
|
84
91
|
unconditionally, so the message is sent HTML-mode regardless (see `email-send-pipeline.md`).
|
|
85
92
|
|
|
86
93
|
## Change history
|
|
94
|
+
- 2026-09-10 — Scoped: this alert fires only for **validation 400s**. A **401/403** is decided in
|
|
95
|
+
the auth chain before routing, so `prePost` never runs and this email never sends for it — those
|
|
96
|
+
refused REQs are covered by the separate 15-minute worker cron
|
|
97
|
+
`prudential_auth_rejection_alert.php`. No code change. (bala)
|
|
87
98
|
- 2026-07-28 — Created: real-time internal alert on `POST /v2/service-requests` validation
|
|
88
99
|
rejection. `prePost()` now collects all validation errors and enqueues the
|
|
89
100
|
`ReqRejectionEmail/sendRejectionEmail` worker task (REQ# + reason + full payload) before
|
|
@@ -15,7 +15,7 @@ project: _Underscore
|
|
|
15
15
|
client: prudential
|
|
16
16
|
type: profile
|
|
17
17
|
status: active
|
|
18
|
-
updated: 2026-
|
|
18
|
+
updated: 2026-09-10
|
|
19
19
|
owners: ["jcardinal", "rgirish", "bala", "mhammontree"]
|
|
20
20
|
files: []
|
|
21
21
|
related:
|
|
@@ -23,10 +23,13 @@ related:
|
|
|
23
23
|
- features/dell-asn-units-interceptor.md
|
|
24
24
|
- features/service-request-address-validation.md
|
|
25
25
|
- features/service-request-rejection-alert-email.md
|
|
26
|
+
- features/service-request-auth-rejection-alert.md
|
|
26
27
|
- features/dell-lch-iop-transmissions.md
|
|
27
28
|
- features/device-information-import-and-contact-linking.md
|
|
28
29
|
- workflows/http2-alb-workaround.md
|
|
29
|
-
- features/
|
|
30
|
+
- features/service-request-auth-rejection-alert.md — the 15-minute refused-REQ (401/403) alert cron,
|
|
31
|
+
where each code is logged, and how to tell "we refused it" from "they never sent it".
|
|
32
|
+
- features/supply-orders-csv-export.md
|
|
30
33
|
- features/order-email-pdf-attachments.md
|
|
31
34
|
---
|
|
32
35
|
|
|
@@ -89,6 +92,13 @@ The order-created cron branches on this id; the shipped/delivered crons branch o
|
|
|
89
92
|
(E_ALL→ErrorException), so `!== false` guards do not work — use the `safeFetchPdf()` pattern. See
|
|
90
93
|
the exception report and the transmit-ordershipped-email feature doc.
|
|
91
94
|
|
|
95
|
+
- **Inbound REQs we refuse (401/403) are invisible in `Logs_Prudential`.** A 401 is logged to the
|
|
96
|
+
**shared base `Logs`** schema because the client is not resolved yet; only a 403 lands in
|
|
97
|
+
`Logs_Prudential`. The daily `prudential_exception_report.php` reads only 201/400/500 in the
|
|
98
|
+
client schema, so refused REQs went unseen for a week in Aug 2026 (36 refused, 22 never resent).
|
|
99
|
+
The 15-minute `prudential_auth_rejection_alert.php` cron now alerts on them — see
|
|
100
|
+
features/service-request-auth-rejection-alert.md.
|
|
101
|
+
|
|
92
102
|
## Monitoring
|
|
93
103
|
- `Monitor/Prudential/{SalesOrderGenerationBacklog,DellPurchaseOrderTransmissionBacklog,OrderShippedUpdateBacklog}`
|
|
94
104
|
(worker2 → OneUptime) watch the order pipeline for stalls. Thresholds are DB-tunable and the
|
package/package.json
CHANGED