toga-ai 1.0.943 → 1.0.945

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: Library
6
6
  client: shared
7
7
  type: feature
8
8
  status: active
9
- updated: 2026-09-16
9
+ updated: 2026-10-05
10
10
  owners: ["bala"]
11
11
  files:
12
12
  - library/app/email.php
@@ -88,7 +88,8 @@ Call `App_Framework_Worker::initialize()` (or the app's equivalent) for config/r
88
88
  2. Read your sample data with a **single SELECT** — no models that `save()`, no api2 calls.
89
89
  3. Build the mail exactly as the real code does (same template class, same subject, same S3 fetches) so you test the real body, not a paraphrase.
90
90
  4. Set the three switches above.
91
- 5. Print, before sending, which attachments resolved and their byte sizes — a missing PDF is otherwise invisible (see `email-queue-attachments.md`: PHPMailer 5 sends anyway).
91
+ 5. To read **prod** data from a local run: call `App_Registry::setOkToPerformOnReadCluster(true)` and force the prod DB links by name (e.g. `db_prod_<client>`, `db_prod_logs_<client>`), because client crons pick `db_dev_*` links under `inDevMode`. Stop before any `UPDATE` the real cron does after sending.
92
+ 6. Print, before sending, which attachments resolved and their byte sizes — a missing PDF is otherwise invisible (see `email-queue-attachments.md`: PHPMailer 5 sends anyway).
92
93
 
93
94
  Run it with the framework repos on the include path:
94
95
  ```
@@ -17,7 +17,7 @@
17
17
  | [Prudential: Send Shipments for the Day report (daily cron)](features/send-shipments-for-the-day.md) | Daily 9:00 PM Prudential/Dell cron emailing an Excel of every device shipped today (tracking, serial, XID, FedEx status, vendor); open for the ASN join chain an |
18
18
  | [Staples 856 ASN Outbound (NetSuite fulfillments → SFTP, tracked in StaplesAdvanceShipNotices)](features/staples-asn-856-outbound.md) | Hourly cron sending Staples 856 ASNs for shipped NetSuite item fulfillments, one row per fulfillment in `Common.StaplesAdvanceShipNotices`; open for the queue/r |
19
19
  | [Staples cXML Order Import (SFTP → NetSuite)](features/staples-cxml-order-import.md) | Hourly cron importing Staples cXML POs from SFTP into NetSuite Sales Orders (writes 855 ack back); open for the per-file flow, saved-search-8412 Location resolu |
20
- | [UMA Student Roster Sync (SFTP → TOGaDeskSupport customers) + Ivanti Ticket Sync](features/uma-student-sync.md) | Daily SFTP full-roster file loads UMA students into TOGaDeskSupport.customers (clientId 10); open for the file format, insert/deactivate rules, how the Ivanti t |
20
+ | [UMA Student Roster Sync (SFTP → TOGaDeskSupport customers) + Ivanti Ticket Sync](features/uma-student-sync.md) | Daily SFTP full-roster file loads UMA students into TOGaDeskSupport.customers (clientId 10); plus the once-a-day Ivanti ticket sync (escalations reach UMA only |
21
21
  | [Walmart Daily Sales Report (send_toga_walmart_report.php)](features/walmart-daily-sales-report.md) | The two Walmart sales workbooks (summary + details) built from `TOGA_Walmart` (in-store) and `TOGA_WalmartEComm` (.COM); open for the pseudo-store 1472 channel |
22
22
  | [Diagnosing frozen 1.0 worker cron check-ins (Sentry "missed" flood)](workflows/diagnosing-frozen-cron-checkins.md) | Ordered path to diagnose frozen 1.0 worker cron timestamps + a Sentry `worker1` "missed" flood; open when check-ins stall and you suspect a wedged lock. |
23
23
  | [isFulfillable Multi-Client Backfill (all togasupply clients)](workflows/isfulfillable-multi-client-backfill.md) | One-time backfill catching up `Items.isFulfillable` on existing items across all 17 togasupply clients; open when running/re-running it or deciding whether rema |
@@ -6,7 +6,7 @@ project: Worker
6
6
  client: uma
7
7
  type: client-feature
8
8
  status: active
9
- updated: 2026-09-28
9
+ updated: 2026-10-05
10
10
  owners: ["mhammontree"]
11
11
  files:
12
12
  - worker/crons/togadesk/sync_uma_customers.php
@@ -17,7 +17,7 @@ related:
17
17
  - 1.0/apps/worker/architecture.md
18
18
  ---
19
19
 
20
- Daily SFTP full-roster file loads UMA students into TOGaDeskSupport.customers (clientId 10); open for the file format, insert/deactivate rules, how the Ivanti ticket sync depends on it, health-check queries, and the PII/hardcoded-credential traps.
20
+ Daily SFTP full-roster file loads UMA students into TOGaDeskSupport.customers (clientId 10); plus the once-a-day Ivanti ticket sync (escalations reach UMA only when it runs); open for the file format, insert/deactivate rules, escalation timing, health-check queries, and the PII/hardcoded-credential traps.
21
21
 
22
22
  ## Summary
23
23
 
@@ -35,7 +35,15 @@ UMA students reach our system through a **daily SFTP file**, not an API. `togade
35
35
  - existing → **untouched** (name/email/phone are never updated);
36
36
  - existing but absent from the file → `isActive = 0`. The file is the **full roster**.
37
37
 
38
- **Ticket sync (`sync_uma_tickets.php`):** uses `App_Api_UMA` (`library/app/api/uma.php`, extends `App_Api_Ivanti`, endpoint `umaservicecenter.saasit.com`) to pull new student incidents and push TOGaDesk tickets. A ticket is created only when the incident email matches a student's `emailPersonal`/`emailWork`.
38
+ **Ticket sync (`sync_uma_tickets.php`):** uses `App_Api_UMA` (`library/app/api/uma.php`, extends `App_Api_Ivanti`, endpoint `umaservicecenter.saasit.com`). Each run, in order:
39
+ 1. Pull new Ivanti incidents → TOGaDesk tickets. Created only when the incident email matches a student's `emailPersonal`/`emailWork`.
40
+ 2. Close TOGaDesk tickets whose incident is resolved in Ivanti.
41
+ 3. **Escalate ("send new tickets back to uma"):** clientId 10 tickets with `referenceId IS NULL AND dtSynced IS NULL` → `App_Api_UMA::postNewIncident()`; Ivanti RecId stored in `tickets.referenceId`.
42
+ 4. Push Closed TOGaDesk tickets back to Ivanti as Resolved.
43
+
44
+ Nothing is sent at escalation time — an escalated ticket waits for the next run.
45
+
46
+ **Ticket sync schedule** (`schedules/cron.worker.togadesk.json`, server time = Central): **once per day** — Mon–Thu `0 15 * * 1-4`, Fri `30 11 * * 5`, Sat `0 17 * * 6`, Mon `0 1 * * 1`. No Sunday run (~32 h gap Sat 17:00 → Mon 01:00). Runs take ~2 min. Fix if asked: restore an hourly (or tighter) range.
39
47
 
40
48
  ## Health check (read-only prod, env `legacy`)
41
49
 
@@ -43,6 +51,7 @@ UMA students reach our system through a **daily SFTP file**, not an API. `togade
43
51
  - **Runs:** `Common.CronJobExecutions` (~120M rows, **no index** on `job`/`dtCheckIn`). Bound by id range (~750k rows/day, e.g. `id > MAX(id) - N`) and match `job = '/var/www/html/crons/togadesk/sync_uma_customers.php'`.
44
52
  - `dtCheckOut` set = finished cleanly; a thrown error leaves it NULL.
45
53
  - ~12-13 s = file processed; ~2 s = SFTP root was empty.
54
+ - **Ticket sync runs:** same table, `job = '/var/www/html/crons/togadesk/sync_uma_tickets.php'`. Escalation send time = run time; check Ivanti created timestamps, not `dtSynced`.
46
55
  - **Normal pattern:** one file/day. Occasional misses or a late file arriving with the next day's — harmless, since only the newest (full roster) is parsed.
47
56
 
48
57
  ## Gotchas
@@ -54,6 +63,9 @@ UMA students reach our system through a **daily SFTP file**, not an API. `togade
54
63
  - **PII.** Files hold DOB and SSN last 4. When investigating, pull only dates/file names from FileLog, never `fileData`.
55
64
  - **Hardcoded credentials — treat as leaked, rotate.** SFTP login is hardcoded in `sync_uma_customers.php`; the Ivanti API key is hardcoded in `library/app/api/uma.php`. Do not copy them anywhere.
56
65
  - Does not call `App_Database::closeConnections()`.
66
+ - **Escalations batch once a day.** Cron entry names still say windows like "9:00–21:00" — misleading; the cron expressions are single hours. Cause: `d53e1ab35` (TRUE-61196, 2025-03-10, UTC→CST) replaced hourly ranges (`0 3-15 * * 2-5`, `0 0-15 * * 6`, `0 0-17 * * 0`, `0 1-15 * * 1`) with single hours; later TZ shifts `9732b7b50` (TRUE-74692) and `9f16ad1de` (TRUE-77365) kept single hours.
67
+ - **`tickets.dtSynced` is not the send time.** On push the code sets it to the ticket's created timestamp; some rows show the run time instead.
57
68
 
58
69
  ## Change history
59
70
  - 2026-09-28 — Documented (TRUE-82352 investigation). Open incident: no UMA file since 2026-09-20; runs 9-21..9-27 clean with empty SFTP; togadesk worker instance changed that day but SFTP login still works from the new box — likely UMA stopped uploading, awaiting UMA confirmation. No wrong deactivations; new students and their tickets missing since 9-20. (mhammontree)
71
+ - 2026-10-05 — TRUE-82504 escalation-batch investigation: escalations reach UMA's Ivanti once a day (prod: tickets created 9/29 20:17 → 9/30 08:21 all sent 9/30 15:01–15:02 CT; repeated 10/1). Findings delivered, no fix requested. (mhammontree)
@@ -7,7 +7,7 @@
7
7
  | [Prudential: Device information import + unit→contact linking (import_device_information.php)](features/device-information-import-and-contact-linking.md) | 1.0 | How the device-sync cron links a Unit to its employee Contact, the "null null" duplicate-contact incident, and the dedup+backfill remediation; open before touch |
8
8
  | [Prudential: ItemClasses device-tier data is dead (and how that was proved)](features/itemclasses-device-tier-data.md) | 2.0 | Prudential `ItemClasses` device-tier data is dead/partly-wrong and was cleared 2026-09-11; also the reusable 4-source method for proving any field is dead. |
9
9
  | [Prudential: OneUptime order-pipeline backlog monitors (Monitor/Prudential/*)](features/oneuptime-order-pipeline-monitors.md) | 2.0 | Three worker2 OneUptime push monitors watching the Prudential/Dell order pipeline for stalls; open before touching Prudential monitors, their thresholds, or the |
10
- | [Prudential: order email PDF attachments (which guide goes on which email)](features/order-email-pdf-attachments.md) | 1.0 | Which S3 PDF guide attaches to which Prudential order email by service-request type/stage, and how to change an attachment safely; open before editing any Prude |
10
+ | [Prudential: order email PDF attachments (which guide goes on which email)](features/order-email-pdf-attachments.md) | 1.0 | Which S3 PDF guide attaches to which Prudential order email by service-request type/stage, every hardcoded spot to change when a guide is swapped, and how to te |
11
11
  | [Prudential: Service Request Regional Address Validation](features/service-request-address-validation.md) | 2.0 | The prePost interceptor that validates a Prudential service request's deliverToAddress per region (USA/India/Ireland) and rejects a blank customer uuid; open fo |
12
12
  | [Prudential: refused REQ alert (401/403 on POST /v2/service-requests)](features/service-request-auth-rejection-alert.md) | 1.0 | The 15-min cron that alerts on refused (401/403) inbound Prudential REQs, where each code is logged, and how to tell "we refused it" from "they never sent it"; |
13
13
  | [Prudential: Service Request bundle lookup scoped to the customer's catalog (wrong Dell India provision profile)](features/service-request-bundle-catalog-resolution.md) | 2.0 | Why India REQs link to USA bundles (bundle numbers repeat across catalogs) and the prePost fix that resolves the bundle uuid inside the customer's catalog; open |
@@ -6,19 +6,21 @@ project: Worker
6
6
  client: prudential
7
7
  type: client-feature
8
8
  status: active
9
- updated: 2026-09-16
9
+ updated: 2026-10-05
10
10
  owners: ["bala"]
11
11
  files:
12
12
  - worker/crons/toga2/prudential/generate_sales_and_purchase_orders_from_service_requests.php
13
13
  - worker/crons/toga2/prudential/transmissions_to_netsuite.php
14
14
  - worker/crons/toga2/prudential/transmit_ordershipped_updates_prudential.php
15
15
  - worker/crons/toga2/prudential/transmit_closecomplete_updates_prudential.php
16
+ - worker/crons/toga2/prudential_beta/generate_sales_and_purchase_orders_from_service_requests.php
17
+ - store/common/prudential/home.php
16
18
  related:
17
19
  - transmit-ordershipped-email.md
18
20
  - ../profile.md
19
21
  - ../../../1.0/apps/library/features/email-test-mode-and-write-free-sends.md
20
22
  ---
21
- Which S3 PDF guide attaches to which Prudential order email by service-request type/stage, and how to change an attachment safely; open before editing any Prudential order-email attachment.
23
+ Which S3 PDF guide attaches to which Prudential order email by service-request type/stage, every hardcoded spot to change when a guide is swapped, and how to test against prod safely; open before editing any Prudential order-email attachment or guide link.
22
24
 
23
25
  ## Summary
24
26
  Every customer-facing Prudential order email carries S3-hosted PDF guides, and **which** guide is attached is decided by the service request type. The rule was inconsistent between stages (same request type got different documents at order-created vs shipped vs delivered), and a new hire was being sent the *return* instructions instead of a setup guide. As of 2026-08-27 one rule applies at **every** stage:
@@ -42,7 +44,7 @@ Reported by the client (Gary Yerman) against the new-hire order-created email; t
42
44
  Load-bearing part is the **Return** column: no non-New type may go out without the return guide, and New must never carry it.
43
45
 
44
46
  ### Which cron sends which email
45
- All four are `active: 1` in `worker/schedules/cron.worker.sync.json`:
47
+ Schedules in `worker/schedules/cron.worker.sync.json`. All `active: 1` except `transmissions_to_netsuite.php` (`active: 0` since 2026-07-17, see `transmit-ordershipped-email.md`):
46
48
  | Stage | Cron | Branches on |
47
49
  |---|---|---|
48
50
  | Order created | `generate_sales_and_purchase_orders_from_service_requests.php` | `serviceRequestTypeId` (`1`, `5`, `3`, `else`) |
@@ -53,14 +55,29 @@ All four are `active: 1` in `worker/schedules/cron.worker.sync.json`:
53
55
  **Two different crons send the shipped email** (`transmissions_to_netsuite.php` and `transmit_ordershipped_updates_prudential.php` — both also stamp `c_dtTransmittedOrderShippedUpdateToPrudential`). Any attachment change to the shipped email must be made in **both**, or they drift apart. The order-created cron switches on the numeric `serviceRequestTypeId`, while shipped and delivered crons switch on the type **name string** read from `serviceRequest.serviceRequestType.name` in the TOGa API response. Names match the enum exactly (`New`, `Reclaim`), so the string comparisons are safe — but the two styles mean a newly added request type lands in the `else` branch everywhere and is never matched by name.
54
56
 
55
57
  ### The PDF assets
56
- All three live under `https://external-client-files.s3.us-west-2.amazonaws.com/prudential/` and all returned HTTP 200 (verified 2026-08-27). Two are **renamed on attach**:
58
+ All three live under `https://external-client-files.s3.us-west-2.amazonaws.com/prudential/`. Two are **renamed on attach**:
57
59
  | S3 object | Attached to the email as | Size |
58
60
  |---|---|---|
59
- | `EquipMe-Win11-v1.pdf` | `Getting_Started_Guide_Windows11.pdf` | 773,591 B |
61
+ | `EquipMe-Win11-Sep_2026.pdf` (since 2026-10-05; replaced `EquipMe-Win11-v1.pdf`) | `Getting_Started_Guide_Windows11.pdf` | 2,735,276 B |
60
62
  | `2026_Aug_PrudentialMacSetupGuide_ZCC.pdf` | `PrudentialMacSetupGuide.pdf` | 7,543,154 B |
61
63
  | `Prudential_Instruction-2Page-V1.pdf` | (unchanged) | 100,224 B |
62
64
 
63
- The Mac guide dominates: a new-hire email is roughly **11 MB** once base64-encoded. Grepping for the attached-as filename will not find the S3 object — search the S3 basename.
65
+ A new-hire email (Win11 + Mac) is roughly **14 MB** once base64-encoded. Grepping for the attached-as filename will not find the S3 object, search the S3 basename.
66
+
67
+ **The attached name never changes, so it does not tell you the version.** The email shows `Getting_Started_Guide_Windows11.pdf` and the cron caches to `$cacheFolder/Getting_Started_Guide_Windows11.pdf` whatever URL was fetched. Check the byte size instead (Sep 2026 = 2,735,276; old v1 = 773,591). The old `EquipMe-Win11-v1.pdf` and the S3 object `Getting_Started_Guide_Windows11.pdf` are byte-identical (both the old guide).
68
+
69
+ ### Swapping the Windows 11 guide: every hardcoded spot
70
+ There is **no setting or table** for the guide URL. It is a literal in each file below, and all must change together:
71
+ | File | Where |
72
+ |---|---|
73
+ | `worker/crons/toga2/prudential/generate_sales_and_purchase_orders_from_service_requests.php` | `$pdfUrl2` in the order-created branches New (1), Refresh (3), and `else` (Breakfix/Swap/Label). Reclaim (5) attaches only `Prudential_Instruction-2Page-V1.pdf`. |
74
+ | `worker/crons/toga2/prudential/transmit_ordershipped_updates_prudential.php` | Shipped (Dell, vendorId 1): new-hire block, plus the shared block inside `if ($reqType != 'Reclaim')`. |
75
+ | `worker/crons/toga2/prudential/transmit_closecomplete_updates_prudential.php` | Delivered: same two places, same Reclaim guard. |
76
+ | `worker/crons/toga2/prudential/transmissions_to_netsuite.php` | Shipped for Agilant-shipped POs (vendorId 2). Cron is `active: 0`, and prod has 0 vendor-2 POs with `c_dtEmailSentOrderShipped`, so it never fires today. Change it anyway. |
77
+ | `worker/crons/toga2/prudential_beta/generate_sales_and_purchase_orders_from_service_requests.php` | Beta copy of order-created. |
78
+ | `store/common/prudential/home.php` (~L244-245) | Equip Me home page "Document" announcement: `$link` ("Click here") and `$win10Link` ("click here") both point at the guide. The Mac link (`$macLink`) is separate. |
79
+
80
+ Last swap 2026-10-05 (to the Sep 2026 build guide): New, Refresh, Breakfix/Swap/Label emails and the Equip Me page; Reclaim stays excluded.
64
81
 
65
82
  ### The verbiage has to match what actually attached
66
83
  Every attach is guarded by `if ($pdfGuideX !== false)`, but until 2026-08-31 the `$attachmentText` paragraph that *describes* the attachments was unconditional. A failed S3 fetch produced an email whose copy pointed the recipient at guides not on the message — and it always said "guides" plural even when a single guide attached. The rule now, in every branch touched: **derive the sentence from the guards, not from the intent.**
@@ -94,23 +111,18 @@ Only the new-hire and Breakfix/Swap/Label order-created templates carry the (com
94
111
  2. Each PDF gets its **own** `if ($pdfX !== false)` guard around the `file_put_contents` + `addAttachment` pair — never a joint `&&`. One failed PDF must drop only itself; the email always sends.
95
112
  3. **Put the return-guide fetch *before* the `if ($reqType != 'Reclaim')` guard** in the shipped and delivered crons. Everything inside that guard is skipped for Reclaim — and Reclaim is precisely the type that most needs the return instructions. That guard is how Reclaim ended up receiving zero attachments.
96
113
 
97
- ### Testing these emails without writing anything
98
- **There is no committed test harness — do not go looking for one.** Three throwaway scripts (`test_new_hire_email.php`, `test_prudential_order_emails.php`, `test_prudential_email_templates.php`) were written under `crons/toga2/prudential/` on 2026-08-31 to mail every stage/type combination with the real templates and real S3 PDFs. They were **never committed and are no longer in the working tree**. What is durable is the technique.
99
-
100
- A harness that mails a real, byte-identical copy while writing **nothing** to the database needs all of this:
101
- - `setQueueForSending(false)`, `sendEmailEvenIfOutboundEmailIsDisabled = true`, and `setIsLogEnabled(false)` on the `App_Email` object. All three — the first two keep it out of `Common.EmailsQueued`, the third keeps it out of `Logs.Email`. Full detail in [App_Email Side Effects & Test Mode](../../../1.0/apps/library/features/email-test-mode-and-write-free-sends.md).
102
- - `App_Framework_Worker::initialize()` **only**. Do **not** call `App_Framework::cronInitialization()` — it INSERTs a `CronJobExecutions` row. Set `set_time_limit()` and `date_default_timezone_set()` yourself instead.
103
- - One read-only SELECT for the sample data (below), rather than the cron's api2 fetches.
104
- - Print each PDF's resolved/missing state and byte size **before** sending; PHPMailer 5 will send the message anyway if an attachment is unreadable.
105
-
106
- **Run it on the worker server if anyone other than you needs to receive it.** Local and beta configs have `internal.test_mode = 1`, which discards the recipient list and sends only to `email.dev_email_to`. `config.worker.ini` on the worker server has `test_mode = 0`.
107
-
108
- Read-only query for a real sample request: one row per request type is enough. Join `ServiceRequests` to `Addresses` on `deliverToAddressId`, `States` on `Addresses.stateId`, `Contacts` on `requestedForContactId`, and **LEFT JOIN** `SalesOrders` on `SalesOrders.serviceRequestId` (left, because an unprocessed request has no SO yet). Select `ServiceRequests.c_reqNumber AS reqNumber`, `Contacts.firstName`, the address parts, `States.code`, and `SalesOrders.number` — the exact set the shipped/delivered templates interpolate. All six request types have live rows in prod `Client_Prudential` (verified 2026-08-31); **Swap has only 2 rows**, the rest have thousands, so filter narrowly for Swap or you will miss it.
114
+ ### Testing these emails against prod data without writing anything
115
+ No committed harness. Build a throwaway `test_<x>_emails.php` next to the cron, use it, delete it. The generic recipe (the three `App_Email` switches, skip `cronInitialization()`, reading prod from a local run) is in [App_Email Side Effects & Test Mode](../../../1.0/apps/library/features/email-test-mode-and-write-free-sends.md). Prudential specifics:
116
+ - Copy the cron's email block **verbatim** (template, subject, S3 fetches) and force `$emailAddresses = [$testEmailRecipient]`.
117
+ - Force links `db_prod_prudential` / `db_prod_logs_prudential` (the crons pick `db_dev_*` under `inDevMode`).
118
+ - Sample = the latest prod record per `serviceRequestTypeId` that was **already emailed** at that stage: `SalesOrders.c_dtEmailSent` (created), `PurchaseOrders.c_dtEmailSentOrderShipped` (shipped), `c_dtEmailSentOrderClosed` (delivered) `IS NOT NULL`. Swap has very few rows in prod, so filter narrowly.
119
+ - Stop before the cron's `UPDATE` of those date columns.
120
+ - Print each PDF's byte size before sending (that is how you tell guide versions apart, see above).
109
121
 
110
122
  ## Gotchas
111
123
  - **`transmit_closecomplete_updates_prudential.php` still over-promises (~line 447).** Its delivered email hardcodes "Guides for Windows 11 and MacBook devices are provided" with no reference to whether either PDF actually attached — the exact bug fixed everywhere else on 2026-08-31. Left alone deliberately to keep PR #1707's diff to files already being changed. **Worth a follow-up ticket.**
112
124
  - **`transmissions_to_netsuite.php` also sends *delivered* emails — with no attachments at all.** It carries its own "New hire equipment has been delivered" / "Your equipment has been delivered" blocks (~L1765 and ~L2149) that make **zero** `addAttachment()` calls, while `transmit_closecomplete_updates_prudential.php` sends the **same two subjects** with the guides attached. **Whether both fire for the same order was not established** — if they do, the recipient gets a duplicate delivered email, one attachment-less. Check this before changing either.
113
- - **`prudential_beta/` does not match prod.** `crons/toga2/prudential_beta/generate_sales_and_purchase_orders_from_service_requests.php` is `active: 1` on the same schedule but points at the beta DB and still references the retired `Prudential_Return_Instructions.pdf` (and the old `PrudentialMacSetupGuide.pdf`). **A beta test will not reproduce prod attachment behavior.** Left as-is deliberately; the developer is aware.
125
+ - **`prudential_beta/` does not match prod.** `crons/toga2/prudential_beta/generate_sales_and_purchase_orders_from_service_requests.php` is `active: 1` on the same schedule but points at the beta DB and still references the retired `Prudential_Return_Instructions.pdf` (and the old `PrudentialMacSetupGuide.pdf`). **A beta test will not reproduce prod attachment behavior.** Only the Win11 guide URL is kept in sync with prod (2026-10-05); the rest is left as-is on purpose.
114
126
  - **Breakfix / Swap / Label get no Mac guide on the order-created email** (they do get it on shipped and delivered). Known, and left alone on purpose — not a bug to "fix" unasked.
115
127
  - The delivered cron (`transmit_closecomplete_updates_prudential.php`) already followed the rule and was deliberately **not** touched.
116
128
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "toga-ai",
3
- "version": "1.0.943",
3
+ "version": "1.0.945",
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",