toga-ai 1.0.310 → 1.0.312
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,7 +23,7 @@
|
|
|
23
23
|
| [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 |
|
|
24
24
|
| [Talos (TOGa IQ) Meeting-Notes Integration & Token Auto-Refresh (consumer)](features/talos-meeting-notes-integration.md) | How a **dev tool / agent consumes Talos (TOGa IQ)** to query the team meeting-notes corpus programmatically. | .claude/skills/plan-ticket/scripts/talos.js |
|
|
25
25
|
| [Talos Pricing Automation (worker2 Cron — AWS Actuals, Calibration, Monthly Report)](features/talos-pricing-automation.md) | The worker2 half of the **Talos Pricing Platform** (see the talos `pricing-cogs-model` and tools `talos-pricing-ui` docs for the other halves). | worker2/Worker/Talos/Pricing.php, worker2/Database/TalosPricingCrons.sql |
|
|
26
|
-
| [Talos Transcript Ingestion Pipeline (worker2 → AWS Bedrock KBs)](features/talos-transcript-ingestion.md) | `_Worker_Team_Transcripts` runs a fully automated, cron-driven pipeline that ingests raw Teams transcripts into the **Talos / TOGa IQ** AWS Bedrock knowledge ba | worker2/Worker/Team/Transcripts.php, worker2/bin/sync-knowledge-bases.php, worker2/Config/production.ini, dbchanges2/Team/2026-06-30a, dbchanges2/Team/2026-06-30b, dbchanges2/Team/2026-06-30c, dbchanges2/Team/2026-06-30d, dbchanges2/Team/2026-06-30e, dbchanges2/Core/2026-06-30a, dbchanges2/Core/2026-07-02a, dbchanges2/Team/2026-07-02a, dbchanges2/Team/2026-07-08a, dbchanges2/Team/2026-07-09a |
|
|
26
|
+
| [Talos Transcript Ingestion Pipeline (worker2 → AWS Bedrock KBs)](features/talos-transcript-ingestion.md) | `_Worker_Team_Transcripts` runs a fully automated, cron-driven pipeline that ingests raw Teams transcripts into the **Talos / TOGa IQ** AWS Bedrock knowledge ba | worker2/Worker/Team/Transcripts.php, worker2/bin/sync-knowledge-bases.php, worker2/Config/production.ini, worker2/Database/TeamsTranscriptExports.sql, dbchanges2/Team/2026-06-30a, dbchanges2/Team/2026-06-30b, dbchanges2/Team/2026-06-30c, dbchanges2/Team/2026-06-30d, dbchanges2/Team/2026-06-30e, dbchanges2/Core/2026-06-30a, dbchanges2/Core/2026-07-02a, dbchanges2/Team/2026-07-02a, dbchanges2/Team/2026-07-08a, dbchanges2/Team/2026-07-09a, dbchanges2/Team/2026-07-10a |
|
|
27
27
|
| [Team Sprint Management & Reporting](features/team-sprint-management.md) | `_Worker_Team_Sprint` (file `Worker/Team/Sprint.php`) is the engine behind TOGA's internal **development-sprint process and reporting**. | worker2/Worker/Team/Sprint.php |
|
|
28
28
|
| [Teams Meeting Transcript Export](features/teams-transcript-export.md) | > **SUPERSEDED (2026-07-09) — the S3-staging model below is history.** `Export` is now a thin > **GRAPH-DIRECT** cron poller: it no longer archives raw VTT to ` | worker2/Worker/Team/Transcripts.php, worker2/Config/production.ini, worker2/Database/TeamsTranscriptExports.sql, dbchanges2/Core/2026-06-18a - Teams Transcript Export schedule.sql |
|
|
29
29
|
| [VAPI Webhook Handler (worker2 — AI-BDR end-of-call processing)](features/vapi-webhook-handler.md) | `_Worker_Vapi` ([worker2/Worker/Vapi.php](worker2/Worker/Vapi.php)) is the **PHP side of the AI-BDR call loop** — the webhook that receives VAPI's end-of-call r | worker2/Worker/Vapi.php, worker2/Worker/Ai/Bdr/Vapi.php |
|
|
@@ -6,12 +6,13 @@ project: Worker
|
|
|
6
6
|
client: shared
|
|
7
7
|
type: feature
|
|
8
8
|
status: active
|
|
9
|
-
updated: 2026-07-
|
|
9
|
+
updated: 2026-07-10
|
|
10
10
|
owners: [jcardinal]
|
|
11
11
|
files:
|
|
12
12
|
- worker2/Worker/Team/Transcripts.php
|
|
13
13
|
- worker2/bin/sync-knowledge-bases.php
|
|
14
14
|
- worker2/Config/production.ini
|
|
15
|
+
- worker2/Database/TeamsTranscriptExports.sql
|
|
15
16
|
- dbchanges2/Team/2026-06-30a
|
|
16
17
|
- dbchanges2/Team/2026-06-30b
|
|
17
18
|
- dbchanges2/Team/2026-06-30c
|
|
@@ -22,6 +23,7 @@ files:
|
|
|
22
23
|
- dbchanges2/Team/2026-07-02a
|
|
23
24
|
- dbchanges2/Team/2026-07-08a
|
|
24
25
|
- dbchanges2/Team/2026-07-09a
|
|
26
|
+
- dbchanges2/Team/2026-07-10a
|
|
25
27
|
related:
|
|
26
28
|
- ./teams-transcript-export.md
|
|
27
29
|
- ./notification-email-template.md
|
|
@@ -65,20 +67,48 @@ JSON that PHP renders to an inline-styled HTML recap email, enqueued to the orga
|
|
|
65
67
|
## How it works
|
|
66
68
|
|
|
67
69
|
### `Export(int $lookbackDays, ?int $limit = null, array $cc = [], array $bcc = [])` (cron)
|
|
68
|
-
Thin poller
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
70
|
+
Thin poller with a **quiet-period stability gate** — it does **not** enqueue a transcript on
|
|
71
|
+
first sight (that processed still-in-progress meetings and emailed half-meeting recaps). The old
|
|
72
|
+
`organizers` parameter was **REMOVED** — organizers are always resolved from config now.
|
|
73
|
+
|
|
74
|
+
**Why a gate is needed:** the Graph `callTranscript` object carries **no "meeting ended"
|
|
75
|
+
signal** — only `id, meetingId, createdDateTime, transcriptContentUrl, meetingOrganizer` (no
|
|
76
|
+
`endDateTime`, no `duration`, no `status`, verified against Graph v1.0 `onlineMeeting:
|
|
77
|
+
getAllTranscripts`). Teams keeps **appending to the VTT while the meeting is live**.
|
|
78
|
+
`onlineMeeting.endDateTime` is only the **scheduled** end and for recurring meetings is the
|
|
79
|
+
series anchor (same unreliability that already rules out `startDateTime` for the meeting date).
|
|
80
|
+
So the only reliable end proxy is **that the transcript content stopped growing**.
|
|
81
|
+
|
|
82
|
+
**Observe-until-stable**, per organizer, per transcript:
|
|
83
|
+
1. Skip immediately if already handed off (`dtEnqueued` set) — **no re-download**.
|
|
84
|
+
2. Otherwise download the VTT **only to measure `strlen` (byte size)**.
|
|
85
|
+
3. First sighting → `recordObservation()` (INSERT, `dtEnqueued` NULL) and **defer**.
|
|
86
|
+
4. Size grew since last poll → still live → `updateObservationSize()` (resets the
|
|
87
|
+
`dtLastChanged` anchor) and **defer**.
|
|
88
|
+
5. Size unchanged for `>= TRANSCRIPT_STABLE_QUIET_SECONDS` (const = **1200s / 20 min**) →
|
|
89
|
+
meeting ended → `markEnqueued()` (stamps `dtEnqueued`, commit) then enqueue one
|
|
90
|
+
`Team/Transcripts/Process` job. At the hourly Export cron cadence this is effectively
|
|
91
|
+
"second poll with unchanged size ⇒ process," with the 20-min floor guarding against
|
|
92
|
+
unusually frequent polls.
|
|
75
93
|
|
|
76
94
|
- `$limit` caps enqueues per run (mainly for testing; e.g. `1` = a single meeting).
|
|
77
95
|
- `cc` / `bcc` thread through each `Process` job into the recap email; validated in
|
|
78
96
|
`enqueueRecapEmail` (deduped, organizer excluded). **Fallback:** if the organizer email
|
|
79
97
|
can't be resolved but `cc` is present, the recap sends **TO** the cc addresses.
|
|
80
|
-
|
|
81
|
-
|
|
98
|
+
|
|
99
|
+
**Watermark (`resolveWatermark`) — must not skip past an open observation.** The per-organizer
|
|
100
|
+
Graph window start takes `MIN(dtCreated)` over **open** observations (`dtEnqueued IS NULL`),
|
|
101
|
+
falling back to `MAX(dtCreated)` over handed-off rows, minus the 1-min overlap. Without this a
|
|
102
|
+
long meeting whose `createdDateTime` predates a newer already-handed-off meeting falls outside
|
|
103
|
+
the Graph date filter and gets stuck "observing" forever.
|
|
104
|
+
|
|
105
|
+
**Private helpers (Transcripts.php):** `loadObservation` (`useQueryCache(false)`; returns
|
|
106
|
+
`id,sizeBytes,dtLastChanged,dtEnqueued,meetingSubject,organizerEmail`), `recordObservation`
|
|
107
|
+
(replaces `recordExport`; synchronous + `_Database::transactionCommit(DB_TEAM)`),
|
|
108
|
+
`updateObservationSize`, `markEnqueued`, `meetingFor` (memoized `getMeeting` wrapper),
|
|
109
|
+
`secondsSince` (parses a MySQL UTC datetime with `' UTC'` suffix → elapsed seconds).
|
|
110
|
+
`alreadyExported()` and `recordExport()` were **removed** (superseded by the observation
|
|
111
|
+
helpers).
|
|
82
112
|
|
|
83
113
|
### `Process(int $transcriptId, string $graphUserId, string $meetingId, string $organizerEmail, string $subject, string $meetingStartIso, array $cc = [], array $bcc = [])`
|
|
84
114
|
1. Download the VTT **straight from Graph** (no S3 round trip).
|
|
@@ -128,12 +158,27 @@ spacing (Bedrock allows only one in-flight ingestion per KB, so parallel syncs r
|
|
|
128
158
|
- **`Team.TranscriptPromptTemplate`** — `name, bodyText, instruction, model, temperature, …`.
|
|
129
159
|
- **`Team.KnowledgeBases`** — `slug, name, isGeneral, bedrockKbId, isActive`; auto-kept-current
|
|
130
160
|
from Bedrock by the daily `SyncKnowledgeBases`.
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
-
|
|
135
|
-
`transcriptIdentifier`
|
|
136
|
-
|
|
161
|
+
**The two tables are BOTH live and NOT redundant** — different owners, different jobs:
|
|
162
|
+
|
|
163
|
+
- **`Team.TranscriptExports`** — **discovery/observation ledger** owned by `Export()`: drives
|
|
164
|
+
the per-organizer watermark, transcript dedupe, and the quiet-period observation. Keyed on
|
|
165
|
+
`transcriptIdentifier` (widened to `VARCHAR(768)`, dbchanges2 `Team/2026-07-09a`).
|
|
166
|
+
Quiet-period columns (dbchanges2 `Team/2026-07-10a`): `sizeBytes` **repurposed** as the
|
|
167
|
+
last-observed raw VTT byte count; `dtLastChanged` (the quiet-period anchor); `dtEnqueued`
|
|
168
|
+
(**NULL = still observing; non-NULL = handed off**, the new done-marker). Dead column
|
|
169
|
+
`s3Key` **dropped** (leftover from the removed S3-staging scheme, no code refs).
|
|
170
|
+
- **`Team.TranscriptProcessing`** — **pipeline state machine** owned by `Process()`: `status`
|
|
171
|
+
ENUM (`pending/cleaned/uploaded/archived/synced/failed`), KB result keys
|
|
172
|
+
(`kbSlug/approvedKey/archiveKey/aiModel/failureReason`), and its own
|
|
173
|
+
`transcriptIdentifier`-keyed idempotency (also `VARCHAR(768)`). Dead column `sourceKey`
|
|
174
|
+
(and its unique index) **dropped** (removed S3-staging leftover).
|
|
175
|
+
|
|
176
|
+
**Dedupe key rule:** recurring meetings **reuse the same `meetingIdentifier`** but get a
|
|
177
|
+
**new `transcriptIdentifier` per occurrence**, so dedupe MUST stay keyed on
|
|
178
|
+
`transcriptIdentifier`, never `meetingIdentifier`.
|
|
179
|
+
|
|
180
|
+
**Migration ordering (`Team/2026-07-10a`):** ADD columns → backfill
|
|
181
|
+
`dtEnqueued=dtExported` on existing rows → DROP `s3Key` → DROP INDEX + `sourceKey`.
|
|
137
182
|
|
|
138
183
|
## Configuration
|
|
139
184
|
|
|
@@ -146,6 +191,20 @@ part of the ingestion loop** (raw reads removed). Credential values live only in
|
|
|
146
191
|
|
|
147
192
|
## Gotchas / known issues
|
|
148
193
|
|
|
194
|
+
- **DEPLOY ORDER: apply migration `2026-07-10a` BEFORE deploying the new worker2 code.** The
|
|
195
|
+
code reads/writes `dtEnqueued`/`dtLastChanged`; deploying code first errors on every poll.
|
|
196
|
+
The migration also backfills every existing row as handed-off (`dtEnqueued=dtExported`) so
|
|
197
|
+
no historical or legacy-duplicate row is re-observed or re-processed.
|
|
198
|
+
- **Root cause of the earlier "same meeting processed multiple times" duplicate rows.**
|
|
199
|
+
Diagnosed from prod `Team.TranscriptExports` rows 573/577 (same "Craftex Open Items"
|
|
200
|
+
meeting): row 573's `transcriptIdentifier` was truncated to exactly 255 chars and is the
|
|
201
|
+
byte-for-byte prefix of row 577's full 304-char id. These were pre-`2026-07-09a` truncated
|
|
202
|
+
rows; the removed `alreadyExported()` compared the full 304-char id and never matched the
|
|
203
|
+
truncated stored value, so the meeting re-enqueued. The `2026-07-09a` widen fixed it going
|
|
204
|
+
forward but left stale truncated rows that could never match. Across the table 42
|
|
205
|
+
`meetingIdentifier`s had duplicates (lengths 56/255/304) — a mix of truncation artifacts and
|
|
206
|
+
**legitimate recurring occurrences** (recurring reuses `meetingIdentifier`, new
|
|
207
|
+
`transcriptIdentifier` each time). `2026-07-10a`'s backfill marks all historical rows inert.
|
|
149
208
|
- **Idempotency: 304-char Graph ids truncated at `VARCHAR(255)` (root cause of endless
|
|
150
209
|
reprocessing).** Graph `callTranscript` ids are ~304 chars; `transcriptIdentifier` columns
|
|
151
210
|
were `VARCHAR(255)`, so ids were silently truncated. `alreadyExported()` compared the full
|
|
@@ -183,6 +242,20 @@ part of the ingestion loop** (raw reads removed). Credential values live only in
|
|
|
183
242
|
|
|
184
243
|
## Change history
|
|
185
244
|
|
|
245
|
+
- 2026-07-10 — **Added the quiet-period stability gate to `Export`** so in-progress meetings
|
|
246
|
+
are no longer processed (was emailing half-meeting recaps). `callTranscript` has no
|
|
247
|
+
end-of-meeting signal, so `Export` now observes each transcript's VTT byte size and only
|
|
248
|
+
hands off (enqueues `Process`) once size is unchanged for `TRANSCRIPT_STABLE_QUIET_SECONDS`
|
|
249
|
+
(1200s). New `TranscriptExports` columns `dtLastChanged`/`dtEnqueued` + repurposed
|
|
250
|
+
`sizeBytes`; new helpers (`recordObservation`, `updateObservationSize`, `markEnqueued`,
|
|
251
|
+
`loadObservation`, `meetingFor`, `secondsSince`); removed `alreadyExported`/`recordExport`.
|
|
252
|
+
`resolveWatermark` now floors at `MIN(dtCreated)` of open observations so a long meeting
|
|
253
|
+
can't fall out of the Graph window. Confirmed both `Team` tables are non-redundant
|
|
254
|
+
(Exports = discovery/observation, Processing = pipeline state machine); dropped dead columns
|
|
255
|
+
`TranscriptExports.s3Key` and `TranscriptProcessing.sourceKey`. Diagnosed the prior
|
|
256
|
+
duplicate-row root cause (pre-`2026-07-09a` truncated ids never matched; dedupe must stay
|
|
257
|
+
keyed on `transcriptIdentifier`, never `meetingIdentifier`). **Apply `2026-07-10a` before
|
|
258
|
+
deploying the code.** dbchanges2: `Team/2026-07-10a`. (jcardinal)
|
|
186
259
|
- 2026-07-09 — **Re-architected to GRAPH-DIRECT.** `Export` is now a thin cron poller
|
|
187
260
|
(removed the `organizers` param; added `$limit`, `cc`/`bcc`); `Scan` and
|
|
188
261
|
`bin/reprocess-transcripts.php` deleted; the `toga-private` staging bucket dropped entirely.
|
|
@@ -6,6 +6,6 @@
|
|
|
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, worker/crons/toga2/prudential/transmissions_to_dell.php, worker/crons/toga2/prudential_beta/transmissions_to_dell_india.php, worker/crons/toga2/prudential_beta/transmissions_to_dell_ireland.php, worker/crons/toga2/prudential_beta/transmissions_to_dell_usa.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
8
|
| [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 |
|
|
9
|
-
| [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/generate_sales_and_purchase_orders_from_service_requests.php, worker/crons/toga2/prudential_beta/generate_sales_and_purchase_orders_from_service_requests.php |
|
|
9
|
+
| [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 |
|
|
10
10
|
| [Prudential Financial](profile.md) | 2.0 | Prudential is a TOGA client whose device-fulfillment flow is driven by **Dell** via the Dell API (`Client_Prudential.Apis.id = 2`). | |
|
|
11
11
|
| [Prudential: Dell ASN failed POST backfill replay](workflows/dell-asn-backfill-replay.md) | 2.0 | When Dell ASN POSTs fail in bulk (e.g. | |
|
|
@@ -6,13 +6,15 @@ project: Worker
|
|
|
6
6
|
client: prudential
|
|
7
7
|
type: client-feature
|
|
8
8
|
status: active
|
|
9
|
-
updated: 2026-07-
|
|
9
|
+
updated: 2026-07-10
|
|
10
10
|
owners: ["rgirish"]
|
|
11
11
|
files:
|
|
12
12
|
- worker/crons/toga2/prudential/transmit_ordershipped_updates_prudential.php
|
|
13
13
|
- worker/crons/toga2/prudential/transmit_closecomplete_updates_prudential.php
|
|
14
|
+
- worker/crons/toga2/prudential/transmit_rejected_cancelled_updates_prudential.php
|
|
14
15
|
- worker/crons/toga2/prudential/generate_sales_and_purchase_orders_from_service_requests.php
|
|
15
16
|
- worker/crons/toga2/prudential_beta/generate_sales_and_purchase_orders_from_service_requests.php
|
|
17
|
+
- worker/crons/notifications/reports/prudential_exception_report.php
|
|
16
18
|
related:
|
|
17
19
|
- ../profile.md
|
|
18
20
|
---
|
|
@@ -47,11 +49,37 @@ Iterates the same POs collected in Phase 1. For each:
|
|
|
47
49
|
- `Client_Prudential.PurchaseOrders.c_dtEmailSentOrderShipped` — stamped after email send
|
|
48
50
|
- Both fields are checked by `prudential_exception_report.php` to detect discrepancies
|
|
49
51
|
|
|
52
|
+
**⚠ `c_dtTransmittedOrderClosedUpdateToPrudential` is an OVERLOADED column (discovered 2026-07-10).**
|
|
53
|
+
It is written by TWO different crons for THREE distinct terminal outcomes:
|
|
54
|
+
- **Delivered / Closed-Complete** — stamped by `transmit_closecomplete_updates_prudential.php`,
|
|
55
|
+
gated on an ASN with a `DELIVERED` tracking number, and **pairs with a customer email**
|
|
56
|
+
(`c_dtEmailSentOrderClosed`). *Only this path owes an email.*
|
|
57
|
+
- **Rejected / Cancelled** — stamped by `transmit_rejected_cancelled_updates_prudential.php`,
|
|
58
|
+
with **no ASN join**; it only pushes a ServiceNow "rejected"/"cancelled" RITM update and
|
|
59
|
+
**sends no customer email**.
|
|
60
|
+
|
|
61
|
+
So a no-ASN Rejected/Cancelled PO shows a "delivered" transmit stamp with `c_dtEmailSentOrderClosed`
|
|
62
|
+
NULL forever — that is correct, not a failure. Any query keyed on this column alone will
|
|
63
|
+
conflate the two. Example: REQ4448998 (PO 26544) was a Rejected order with no ASN/tracking and
|
|
64
|
+
was a false positive in the "Order Delivered Email Not Sent" report.
|
|
65
|
+
|
|
50
66
|
## Exception report link
|
|
51
67
|
`worker/crons/notifications/reports/prudential_exception_report.php` flags any PO where
|
|
52
68
|
`c_dtTransmittedOrderShippedUpdateToPrudential IS NOT NULL` AND `c_dtEmailSentOrderShipped IS NULL`
|
|
53
69
|
as "Order Shipped Email Not Sent". This appears daily in the Prudential REQ Processing Report email.
|
|
54
70
|
|
|
71
|
+
**Order-Delivered discrepancy query — DELIVERED gate required (fixed 2026-07-10, ~line 463-474).**
|
|
72
|
+
The Order-Delivered discrepancy query originally filtered only on
|
|
73
|
+
`c_dtTransmittedOrderClosedUpdateToPrudential IS NOT NULL AND c_dtEmailSentOrderClosed IS NULL`
|
|
74
|
+
with no shipment/stage filter. Because that column is overloaded (see Data model), every
|
|
75
|
+
Rejected/Cancelled Dell PO was flagged as an "Order Delivered Email Not Sent" failure even though
|
|
76
|
+
it never owed a delivery email. Fix: add
|
|
77
|
+
`AND EXISTS (AdvanceShippingNotices → ASNItems → ASNItemUnits → ASNItemUnits_TrackingNumbers →
|
|
78
|
+
TrackingNumbers WHERE purchaseOrderId = PurchaseOrders.id AND TrackingNumbers.status = 'DELIVERED')`
|
|
79
|
+
— this mirrors the closecomplete cron's own Phase-1 email precondition, so the report only flags
|
|
80
|
+
orders that genuinely qualified for a delivery email. Over 2026-07-01..07-09 this dropped 6 false
|
|
81
|
+
positives (25 → 19 flagged) while retaining all 19 genuine discrepancies.
|
|
82
|
+
|
|
55
83
|
## Sibling scripts sharing this pattern
|
|
56
84
|
The same two-phase (transmit-then-email) shape and the same PDF-fetch fragility exist in three
|
|
57
85
|
sibling crons, all fixed the same way this session (2026-07-06):
|
|
@@ -65,6 +93,21 @@ sibling crons, all fixed the same way this session (2026-07-06):
|
|
|
65
93
|
|
|
66
94
|
## Gotchas / known issues
|
|
67
95
|
|
|
96
|
+
**ROOT CAUSE (third regression) — `App_Database::escapeString()` does not exist → fatal on the 404 auto-create path (fixed 2026-07-10):**
|
|
97
|
+
`createAssetAndRetryPut()` in **both** `transmit_ordershipped` and `transmit_closecomplete`
|
|
98
|
+
(~line 158) called `App_Database::escapeString($serialNumber)`. **That method does not exist** on
|
|
99
|
+
`App_Database` — only `sqlEscape`, `sqlProtect`, and `mysqlRealEscapeString` do. Under the 1.0
|
|
100
|
+
framework's `error_reporting(E_ALL)`, calling a non-existent static method is a **fatal `Error`
|
|
101
|
+
that kills the cron mid-run**. It fires whenever an asset PUT to ServiceNow returns 404
|
|
102
|
+
(`isAssetNotFoundError`) — routine for Breakfix/Reclaim/Refresh returned/replacement assets not yet
|
|
103
|
+
in the Prudential CMDB. The fatal occurs in Phase 1 **after** the transmit flag is stamped but
|
|
104
|
+
**before** the email phase, so the email is permanently stranded (email flag left NULL). This was
|
|
105
|
+
the specific unaddressed trigger of the month-long email-stranding — distinct from, but riding on
|
|
106
|
+
top of, the two-phase design flaw below. **Fix: `escapeString` → `sqlEscape` in both files**
|
|
107
|
+
(`sqlEscape` is already used correctly elsewhere in the same files, e.g. ordershipped line 1386).
|
|
108
|
+
This was flagged as a "latent error" in the 2026-07-06 pre-existing-issues note; it turned out to
|
|
109
|
+
be the live trigger.
|
|
110
|
+
|
|
68
111
|
**ROOT CAUSE — `file_get_contents` in a 1.0 cron THROWS, it does not return false (discovered 2026-07-06):**
|
|
69
112
|
The 1.0 framework bootstrap sets `error_reporting(E_ALL)` and `App_Error::handleError`
|
|
70
113
|
(`library/app/error.php` ~line 348) converts **every** PHP warning into an `ErrorException`.
|
|
@@ -110,8 +153,8 @@ URL was still live in 3 of the sibling files and was corrected this session.
|
|
|
110
153
|
**Pre-existing issues surfaced this session (NOT fixed — awareness only):**
|
|
111
154
|
- SQL-injection risk: unescaped `$reqNumber` concatenated into a `LIKE '%...%'` query in the
|
|
112
155
|
Phase-2 body of both `transmit_ordershipped` and `transmit_closecomplete`.
|
|
113
|
-
-
|
|
114
|
-
|
|
156
|
+
- (RESOLVED 2026-07-10) `App_Database::escapeString()` at ~line 158 in **both** crons — see the
|
|
157
|
+
ROOT CAUSE gotcha above; this "latent" error was the actual fatal trigger, now fixed to `sqlEscape`.
|
|
115
158
|
- Separate inbound issue: Dell SR re-POST hitting `ServiceRequests.number` UNIQUE constraint →
|
|
116
159
|
API 400 "Duplicate entry SR129013" (REQ4444748). Not part of the email fix.
|
|
117
160
|
|
|
@@ -132,5 +175,20 @@ stamp (crash hits the next iteration). With multiple New-type POs, a crash on PO
|
|
|
132
175
|
all subsequent POs in the same run.
|
|
133
176
|
|
|
134
177
|
## Change history
|
|
178
|
+
- 2026-07-10 — Third regression of the "Order Shipped / Order Delivered Email Not Sent" symptom.
|
|
179
|
+
True trigger: `createAssetAndRetryPut()` called the nonexistent `App_Database::escapeString()`
|
|
180
|
+
(~line 158 in `transmit_ordershipped` + `transmit_closecomplete`) — a fatal `Error` under E_ALL
|
|
181
|
+
that fired on the ServiceNow 404 asset auto-create path (routine for Breakfix/Reclaim/Refresh),
|
|
182
|
+
killing Phase 1 after the transmit stamp but before the email. Fixed `escapeString` -> `sqlEscape`
|
|
183
|
+
in both files (verified by 3 adversarial subagents; php -l clean). Recovered by nulling the
|
|
184
|
+
transmit flags for 9 stranded shipped POs + 1 closed PO (re-processing is idempotent) — the 9
|
|
185
|
+
owed shipped emails were confirmed sent 2026-07-10 09:34-09:35.
|
|
186
|
+
Also: discovered `c_dtTransmittedOrderClosedUpdateToPrudential` is overloaded across
|
|
187
|
+
Delivered vs Rejected/Cancelled (only Delivered owes an email), and fixed the
|
|
188
|
+
`prudential_exception_report.php` Order-Delivered discrepancy query to require an EXISTS on a
|
|
189
|
+
DELIVERED tracking number (drops Rejected/Cancelled false positives; REQ4448998/PO 26544 was one).
|
|
190
|
+
The structural two-phase stamp-before-email flaw remains **open** — candidate durable fix: stamp
|
|
191
|
+
the email flag only after a successful send and/or wrap each PO's email phase in try/catch. Not
|
|
192
|
+
done this session (scoped to the specific trigger) (rgirish)
|
|
135
193
|
- 2026-07-06 — Found the real root cause: `file_get_contents` throws (E_ALL→ErrorException) so the `!== false` guards (incl. the 2026-06-18 one) were never effective. Added `safeFetchPdf()` (try/catch ErrorException → false) + independent per-PDF guards; email now always sends even if a PDF fails. Extended the same fix to `transmit_closecomplete` and both `generate_sales_and_purchase_orders_from_service_requests` scripts, and finished the stale Mac PDF URL fix in the 3 files the 2026-06-18 change missed. Documented the permanent two-phase stranding flaw and the date-bounded-recovery requirement; scope kept to fix-forward (no auto self-heal) by developer decision (rgirish)
|
|
136
194
|
- 2026-06-18 — Fixed 403 crash: corrected Mac PDF URL (`_15.6_v4` → `PrudentialMacSetupGuide.pdf`) and added a `!== false` guard on all S3 fetches in New branch. NOTE (2026-07-06): this guard was ineffective — the exception is thrown inside `file_get_contents`, not returned (rgirish)
|
package/package.json
CHANGED