toga-ai 1.0.1207 → 1.0.1209

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,8 +6,8 @@ project: Worker
6
6
  client: shared
7
7
  type: feature
8
8
  status: active
9
- updated: 2026-10-15
10
- owners: [dfranks, jcardinal]
9
+ updated: 2026-10-29
10
+ owners: [dfranks, jcardinal, mhammontree]
11
11
  files: []
12
12
  related:
13
13
  - ../../../../standalone/apps/claude/workflows/clickup-ticket-workflow.md
@@ -35,9 +35,11 @@ How ClickUp surfaces branches/PRs/commits in a ticket's GitHub tab and the branc
35
35
  2. GitHub Settings → "Connect Repositories to Spaces": add each repo AND link it to the **Development** space. A repo not linked to that space never links its PRs.
36
36
  3. Repos added 2026-10-08: worker2, dbchanges2, toga25-desk, toga25-iq, toga25-blox, toga25-supply, toga2-commerce, toga2-supply, toga2-template, toga-blox-npm, saml, forward, apiproxy (`test` and `claude` skipped). A PR titled with the ticket id then linked automatically (verified).
37
37
  - **Settings:** leave "auto-generated branch name" as `:taskId:`. Do not turn on "Update Task Status from GitHub" — worker2 owns status automation.
38
+ - **A shared branch links its PRs to EVERY ticket it names.** One branch carrying several tickets (e.g. `TRUE-82776` for 82776/82778/82779) shows its PRs on all of them, even tickets whose work is not in it. Before closing a PR because "the ticket shows a PR", check the PR's head branch (2026-10-29, TRUE-82779).
38
39
 
39
40
  ## Change history
40
41
 
42
+ - 2026-10-29 — Gotcha: a shared branch's PRs show on every ticket it names; check the head branch (TRUE-82779). (mhammontree)
41
43
  - 2026-10-15 — PR base now follows the ticket's project (Desk/IQ → `toga25`); links to git-workflow.md instead of restating. (jcardinal)
42
44
  - 2026-10-08 — Added reconnect fix, Development-space repo linking, and settings to leave alone. (jcardinal)
43
45
  - 2026-10-08 — `/work-ticket` and `/plan-ticket` retired; `/finish` opens the PRs. PR title = ticket id first; base = repo default branch; git-workflow now uses bare ticket-id branches. (jcardinal)
@@ -578,6 +578,12 @@ instance: [Staples SFTP folder monitors](../../../clients/staples/features/oneup
578
578
  `loadImportConfigs()` + `connect()` (CronJobs config + Secrets Manager, one login per account) — see
579
579
  [NYCDOE stuck ASN files monitor](../../../clients/nycdoe/features/oneuptime-stuck-asn-files-monitor.md).
580
580
 
581
+ **Private-key login** (first in worker2, [NYCDOE EDI 850 queue monitor](../../../clients/nycdoe/features/oneuptime-edi-850-sftp-queue-monitor.md)):
582
+ `$key = PublicKeyLoader::load(_Component_Secrets::resolve($secretRef)); $sftp->login($username, $key);`
583
+ (`phpseclib3\Crypt\PublicKeyLoader`). Store the secret as the **raw PEM in Secrets Manager's Plaintext
584
+ tab** (BEGIN/END lines and line breaks kept) — the default key/value JSON (`{"":""}`) fails every run.
585
+ `_Worker_Sftp_Import::connect()` still has PRIVATE_KEY as a TODO; copy this.
586
+
581
587
  Two runtime facts about the installed phpseclib3 (v3.0.49) that bite silently — verified at runtime
582
588
  against the live SFTP:
583
589
 
@@ -693,6 +699,7 @@ production deploy.**
693
699
  - [OneUptime 1.0 worker uptime monitoring](../../../1.0/apps/worker/features/oneuptime-worker-uptime-monitoring.md) — the 1.0 push-heartbeat predecessor
694
700
 
695
701
  ## Change history
702
+ - 2026-10-29 — SFTP: first private-key login (raw PEM secret) via NYCDOE `EdiPurchaseOrderSftpQueue` (TRUE-82779). (mhammontree)
696
703
  - 2026-10-29 — Gotcha: never run a monitor from local worker2, the hardcoded push URL feeds local numbers into the prod monitor; `CompassSalesOrderTransmission` triage now in the MITS alerting doc (TRUE-83407). (bala)
697
704
  - 2026-10-29 — Every OneUptime push retries once (`setAutoRetry`, 1 retry, 2s; `PUSH_RETRY_*` consts on every monitor class); worker1.5 heartbeat too (TRUE-83398). (bala)
698
705
  - 2026-10-29 — Gotcha: "stopped reporting" incidents from failed OneUptime pushes (`OneUptime push FAILED.` + `alarm=OK` in `WorkerJobs.output`) and how to tell them from a real outage (TRUE-83380). (bala)
@@ -5,9 +5,9 @@ _Auto-generated by `knowledge.js index`. Do not hand-edit._
5
5
  ## 1.0 framework
6
6
 
7
7
  - **library** (Library) _(framework core)_ — 41 doc(s) → [1.0/apps/library/INDEX.md](1.0/apps/library/INDEX.md)
8
- - **worker** (Worker) — 42 doc(s) → [1.0/apps/worker/INDEX.md](1.0/apps/worker/INDEX.md)
8
+ - **worker** (Worker) — 41 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
- - **worker1.5** (Worker 1.5) — 9 doc(s) → [1.0/apps/worker1.5/INDEX.md](1.0/apps/worker1.5/INDEX.md)
10
+ - **worker1.5** (Worker 1.5) — 10 doc(s) → [1.0/apps/worker1.5/INDEX.md](1.0/apps/worker1.5/INDEX.md)
11
11
  - **togadesk** (TOGa Desk) — 19 doc(s) → [1.0/apps/togadesk/INDEX.md](1.0/apps/togadesk/INDEX.md)
12
12
  - **togaview** (TOGa View) — 9 doc(s) → [1.0/apps/togaview/INDEX.md](1.0/apps/togaview/INDEX.md)
13
13
  - **webhook** (Webhook) — 1 doc(s) → [1.0/apps/webhook/INDEX.md](1.0/apps/webhook/INDEX.md)
@@ -23,7 +23,7 @@ _Auto-generated by `knowledge.js index`. Do not hand-edit._
23
23
  ## 2.0 framework
24
24
 
25
25
  - **_underscore** (_Underscore) _(framework core)_ — 119 doc(s) → [2.0/apps/_underscore/INDEX.md](2.0/apps/_underscore/INDEX.md)
26
- - **worker2** (Worker) — 102 doc(s) → [2.0/apps/worker2/INDEX.md](2.0/apps/worker2/INDEX.md)
26
+ - **worker2** (Worker) — 103 doc(s) → [2.0/apps/worker2/INDEX.md](2.0/apps/worker2/INDEX.md)
27
27
  - **api2** (API) — 29 doc(s) → [2.0/apps/api2/INDEX.md](2.0/apps/api2/INDEX.md)
28
28
  - **dbchanges2** (Database Changes) _(framework core)_ — 24 doc(s) → [2.0/apps/dbchanges2/INDEX.md](2.0/apps/dbchanges2/INDEX.md)
29
29
  - **toga2-supply** (TOGa Supply) — 9 doc(s) → [2.0/apps/toga2-supply/INDEX.md](2.0/apps/toga2-supply/INDEX.md)
@@ -5,6 +5,7 @@
5
5
  | [NYCDOE 2.0 ASN SFTP Ingestion + Parsing (Sftp/Import → Sftp/Parse → Case/ServiceRequest)](features/asn-sftp-ingestion-2-0.md) | 2.0 | 2.0 rewrite of DOE's vendor-SFTP ASN intake (TRUE-80519 Phases 1–2): per-manufacturer fetch to `Files`, header-validated parse into one Case per customer PO + o |
6
6
  | [NYCDOE EDI 850 Receive & 997 Acknowledgement](features/edi-850-receive-and-997-ack.md) | 1.0 | DOE's EDI 850-in → NetSuite SO + inline 997-out ack; open for the 997 build, its "always accept" stub, or a DOE ack complaint. |
7
7
  | [NYCDOE Ticket Hold-Status Sync (ServiceNow ⇄ TOGaDesk)](features/hold-status-sync.md) | 1.0 | Bidirectional DOE ticket hold-status sync (ServiceNow ⇄ TOGaDesk), the four defects that let holds silently revert or never re-send, the held→complete close clo |
8
+ | [NYCDOE: EDI 850 SFTP queue monitor (Monitor/Nycdoe/EdiPurchaseOrderSftpQueue)](features/oneuptime-edi-850-sftp-queue-monitor.md) | 2.0 | worker2 OneUptime monitor that alarms when a DOE EDI 850 sits 19h+ in `/outbound` on DOE's SFTP (= the 1.0 receiver failed, no SO, no 997); open when it fires o |
8
9
  | [NYCDOE: ServiceNow update-rejection monitor (Monitor/Nycdoe/ServiceNowUpdateRejections)](features/oneuptime-servicenow-update-rejections-monitor.md) | 2.0 | worker2 OneUptime monitor that alarms when ServiceNow refuses NYC DOE ticket updates (HTTP 200 with an empty body) in the 1.0 `Logs.API` log; open when it fires |
9
10
  | [NYCDOE: stuck ASN files monitor (Monitor/Nycdoe/StuckAsnFiles)](features/oneuptime-stuck-asn-files-monitor.md) | 2.0 | worker2 OneUptime monitor that alarms when any file sits 24h+ on a NYC DOE vendor ASN SFTP folder; open when it fires, when a DOE ASN file is stuck, or when cha |
10
11
  | [NYCDOE ServiceNow / ASN Integration](features/servicenow-integration.md) | 1.0 | Full DOE ServiceNow/ASN integration map — ticket mirror, ASN → NetSuite SO/TOGa Desk pipeline, outbound status/POD, EDI; open for any DOE cron/pipeline work or |
@@ -6,14 +6,16 @@ project: Worker
6
6
  client: nycdoe
7
7
  type: client-feature
8
8
  status: active
9
- updated: 2026-09-16
9
+ updated: 2026-10-29
10
10
  owners: [mhammontree]
11
11
  files:
12
12
  - worker/crons/sync/nycdoe/receive_edi_purchase_orders.php
13
+ - worker/crons/sync/nycdoe/send_edi_open_invoices.php
13
14
  - worker/schedules/cron.worker.sync.json
14
15
  - library/app/edi.php
15
16
  related:
16
17
  - servicenow-integration.md
18
+ - oneuptime-edi-850-sftp-queue-monitor.md
17
19
  - ../profile.md
18
20
  - ../../../1.0/apps/worker/architecture.md
19
21
  - ../../../1.0/apps/library/architecture.md
@@ -23,7 +25,11 @@ DOE's EDI 850-in → NetSuite SO + inline 997-out ack; open for the 997 build, i
23
25
 
24
26
  ## Summary
25
27
 
26
- DOE sends purchase orders as **EDI 850** files over their SFTP; TOGA turns each into a NetSuite Sales Order and sends back an **EDI 997** functional acknowledgement. There is **no separate 997 cron** — the 997 is built and uploaded inline at the end of the 850 receiver, so anything that aborts the receiver means DOE never gets an ack. Broader DOE ticket + ASN pipeline: [servicenow-integration.md](servicenow-integration.md).
28
+ DOE sends purchase orders as **EDI 850** files over their SFTP; TOGA turns each into a NetSuite Sales Order and sends back an **EDI 997** functional acknowledgement. There is **no separate 997 cron** — the 997 is built and uploaded inline at the end of the 850 receiver, so anything that aborts the receiver means DOE never gets an ack. Broader DOE ticket + ASN pipeline: [servicenow-integration.md](servicenow-integration.md). A stuck 850 is caught by the 2.0 [EDI 850 SFTP queue monitor](oneuptime-edi-850-sftp-queue-monitor.md) (TRUE-82779).
29
+
30
+ **SFTP folder roles (named from DOE's side — easy to mix up):**
31
+ - `/outbound` = DOE drops 850s here **for us**; the receiver reads it (cron line ~33) and deletes each file after success.
32
+ - `/inbound` = **we** drop 997s and invoices (`send_edi_open_invoices.php`) here for DOE to pick up.
27
33
 
28
34
  ## How it works
29
35
 
@@ -63,6 +69,7 @@ Field mapping (verified against 850 FileLog id 880810):
63
69
  ## Related
64
70
 
65
71
  - [NYCDOE ServiceNow / ASN Integration](servicenow-integration.md)
72
+ - [EDI 850 SFTP queue monitor](oneuptime-edi-850-sftp-queue-monitor.md)
66
73
  - [NYC DOE client profile](../profile.md)
67
74
  - [Worker (1.0) architecture](../../../1.0/apps/worker/architecture.md)
68
75
  - [Library (1.0) architecture](../../../1.0/apps/library/architecture.md)
@@ -0,0 +1,44 @@
1
+ ---
2
+ title: "NYCDOE: EDI 850 SFTP queue monitor (Monitor/Nycdoe/EdiPurchaseOrderSftpQueue)"
3
+ framework: "2.0"
4
+ repo: worker2
5
+ project: Worker
6
+ client: nycdoe
7
+ type: client-feature
8
+ status: active
9
+ updated: 2026-10-29
10
+ owners: ["mhammontree"]
11
+ files:
12
+ - worker2/Worker/Monitor/Nycdoe.php
13
+ - dbchanges2/Core/2026-10-28a - NYCDOE EDI 850 SFTP Queue Monitor CronJob.sql
14
+ related:
15
+ - ./edi-850-receive-and-997-ack.md
16
+ - ./oneuptime-stuck-asn-files-monitor.md
17
+ - ../../../2.0/apps/worker2/features/oneuptime-worker2-monitoring.md
18
+ - ../profile.md
19
+ ---
20
+ worker2 OneUptime monitor that alarms when a DOE EDI 850 sits 19h+ in `/outbound` on DOE's SFTP (= the 1.0 receiver failed, no SO, no 997); open when it fires or when changing the DOE EDI SFTP login.
21
+
22
+ ## Summary
23
+ `Monitor/Nycdoe/EdiPurchaseOrderSftpQueue(string $host, string $username, string $secretRef, string $remoteDirectory, int $port = 22)` (`_Worker_Monitor_Nycdoe`, TRUE-82779). SFTP-backlog monitor; shared pattern in [OneUptime push-metric monitors](../../../2.0/apps/worker2/features/oneuptime-worker2-monitoring.md#sftp-monitoring-phpseclib3). Watches the folder the 1.0 [EDI 850 receiver](edi-850-receive-and-997-ack.md) empties.
24
+
25
+ - `Core.CronJobs` uuid `94f6d0ed-a67b-4862-b844-215d3ccc44cf`, `0 * * * *`, `maxExecutionTime` 300, ships `isActive = 0`. `parameters` `{host: transfer.schools.nyc, port: 22, username, secretRef, remoteDirectory: "/outbound"}` — config lives in the row, not PHP.
26
+ - Key: AWS Secrets Manager `sftp/nycdoe/edi` (the `doedimedu.pem` private key). Login: see [private-key SFTP login](../../../2.0/apps/worker2/features/oneuptime-worker2-monitoring.md#sftp-monitoring-phpseclib3).
27
+ - Push URL = in-method `$ONEUPTIME_URL` local (credential; never record the value).
28
+
29
+ ## How it works
30
+ - **Stale rule** (`findStaleFiles(entries, staleFileMinutes, nowEpoch)`, public pure filter for tests): any plain file, hidden included (only `.`/`..` and directories skipped — same as the 1.0 receiver), mtime >= `STALE_EDI_FILE_MINUTES` (1140 = 19h). Constant at the top of the class.
31
+ - **Why 19h:** receiver runs 9:30 and 15:30 and deletes each 850 after the SO + 997; a file landing just after 15:30 waits ~18h normally.
32
+ - **Retry:** 3 tries, 5s apart, per-step timeout min(15s, remaining / 4), floor 5s, 240s deadline. Login refused is not retried.
33
+ - **Body:** `status` (`reporting` / `error`), `alarm` (`HIGH` / `OK`), `staleFileCount`, `staleFileMinutes`, `staleFiles` (max 20: name, ageHours, oldest first), `checkedAtUtc`. Errors: `secret_load_failed`, `sftp_read_failed` (+ `attempts`). SFTP error text is kept only in the `WorkerJobs` return string, never sent to OneUptime.
34
+ - **OneUptime (set up by Mark):** Incoming Request. Offline (Match Any): body contains `"alarm":"HIGH"` OR `"status":"error"` OR no request in 120 min (add after first push). Online (Match All): `"alarm":"OK"` AND not `"status":"error"`.
35
+
36
+ ## Gotchas
37
+ - **When it fires:** the receiver failed for that file — DOE has no SO and no 997. Debug per [EDI 850 receive](edi-850-receive-and-997-ack.md).
38
+ - **`secret_load_failed` keeps only the exception class** — a key parse error can echo key material.
39
+ - **No SFTP host-key pinning** (same as Staples / `Sftp/Import`; CSO rated Low).
40
+ - **Do not run it locally** — no AWS role, so it pushes `status:"error"` and opens a real incident. Test the filter with `test/@Mark/TRUE-82779/test_find_stale_files.php`.
41
+ - **After deploy:** run the Core SQL, set `isActive = 1`, check the first `WorkerJobs` result.
42
+
43
+ ## Change history
44
+ - 2026-10-29 — Created; built, ships inactive (TRUE-82779). (mhammontree)
@@ -6,7 +6,7 @@ project: Worker
6
6
  client: nycdoe
7
7
  type: client-feature
8
8
  status: active
9
- updated: 2026-10-27
9
+ updated: 2026-10-29
10
10
  owners: ["mhammontree"]
11
11
  files:
12
12
  - worker2/Worker/Monitor/Nycdoe.php
@@ -43,6 +43,7 @@ worker2 OneUptime monitor that alarms when any file sits 24h+ on a NYC DOE vendo
43
43
  ### Decisions
44
44
  - **SFTP listing is the source of truth, not `Client_Nycdoe.Files`.** 2.0 deletes only after a good parse; `not_matched_kept` files have no `Files` row; a `failed` row goes stale when a same-name file later succeeds (seen: `DOE-CDW-ASN-10-20-2026.csv`). 1.0 `Logs.FileLog` logs each file once in gated mode, so it cannot show wait time.
45
45
  - Hourly — "we don't really know when customers plan to upload files" (Mark). Any file name counts (Mark).
46
+ - **TRUE-82778** (`Nycdoe/VendorAsnSftpQueue`, vendor ASN file age) is covered by this monitor — closed as duplicate, nothing built (Mark, 2026-10-27).
46
47
 
47
48
  ## Gotchas
48
49
  - **Known gap:** a same-name re-upload resets mtime, so a file the vendor keeps replacing never alarms.
@@ -51,4 +52,5 @@ worker2 OneUptime monitor that alarms when any file sits 24h+ on a NYC DOE vendo
51
52
  - **Do not run it locally once the real URL is in code** — no AWS role on a dev machine, so `_Component_Secrets::resolve()` fails (instance metadata `169.254.169.254`), pushes `status:"error"`, and opens a real incident. Local `Core_2` also has only 1 of 4 `Sftp/Import/Run` rows. Local test: `test/@Mark/TRUE-82941/run_stuck_asn_monitor.php` (read-only listing with the 1.0 library asnprocessor FTP password).
52
53
 
53
54
  ## Change history
55
+ - 2026-10-29 — TRUE-82778 closed as duplicate of this monitor. (mhammontree)
54
56
  - 2026-10-27 — Created; built, ships inactive. `Sftp/Import` `loadImportConfigs()` / `connect()` made public for reuse (TRUE-82941). (mhammontree)
@@ -14,7 +14,7 @@ project: Worker
14
14
  client: nycdoe
15
15
  type: profile
16
16
  status: active
17
- updated: 2026-10-27
17
+ updated: 2026-10-29
18
18
  owners: [mhammontree, sking, bala, rgirish]
19
19
  files: []
20
20
  related:
@@ -23,6 +23,7 @@ related:
23
23
  - features/asn-sftp-ingestion-2-0.md
24
24
  - features/oneuptime-servicenow-update-rejections-monitor.md
25
25
  - features/oneuptime-stuck-asn-files-monitor.md
26
+ - features/oneuptime-edi-850-sftp-queue-monitor.md
26
27
  ---
27
28
 
28
29
  NYC DOE client profile (1.0 worker tier live; 2.0 ASN rewrite in progress), ServiceNow + EDI; open for DOE's platform scope, endpoints, API clients, and where its ticket/ASN/EDI logic lives.
@@ -32,7 +33,7 @@ NYC DOE — TOGA client whose **live** integration runs in the **1.0 worker tier
32
33
 
33
34
  ## Platforms & data
34
35
  - **1.0 worker tier** — all live business logic. Local mirror tables in `db_common` (`NYCDOETickets`, `NYCDOELocations`, `AdvanceShippingNotice*`).
35
- - **2.0 (`worker2` + `_underscore` + `dbchanges2`, tenant `Client_Nycdoe`)** — (1) the OneUptime liveness probe `Monitor/Nycdoe/ServiceNowEndpointHealth` (`worker2/Worker/Monitor/Nycdoe.php`, TRUE-80587, live) the [ServiceNow update-rejection monitor](features/oneuptime-servicenow-update-rejections-monitor.md) (TRUE-82776, ships inactive), and the [stuck ASN files monitor](features/oneuptime-stuck-asn-files-monitor.md) (TRUE-82941, ships inactive); (2) the ASN rewrite `Sftp/Import` → `Sftp/Parse` → Case/ServiceRequest (Phases 1–2; gated delete live in prod since ~2026-10-11, scheduled crons still off) — see [2.0 ASN SFTP Ingestion](features/asn-sftp-ingestion-2-0.md). Entity map: `Case` = 1.0 managed_service_order, `ServiceRequest` = 1.0 repair_order; DOE does not use `Ticket`. Client models: `_underscore/Model/Nycdoe/`. 2.0 does **not** write to ServiceNow yet — the 1.0 RITM/POD crons keep sending until 2.0 takes over, then 1.0 is turned off.
36
+ - **2.0 (`worker2` + `_underscore` + `dbchanges2`, tenant `Client_Nycdoe`)** — (1) the OneUptime liveness probe `Monitor/Nycdoe/ServiceNowEndpointHealth` (`worker2/Worker/Monitor/Nycdoe.php`, TRUE-80587, live) the [ServiceNow update-rejection monitor](features/oneuptime-servicenow-update-rejections-monitor.md) (TRUE-82776, ships inactive), the [stuck ASN files monitor](features/oneuptime-stuck-asn-files-monitor.md) (TRUE-82941, ships inactive), and the [EDI 850 SFTP queue monitor](features/oneuptime-edi-850-sftp-queue-monitor.md) (TRUE-82779, ships inactive); (2) the ASN rewrite `Sftp/Import` → `Sftp/Parse` → Case/ServiceRequest (Phases 1–2; gated delete live in prod since ~2026-10-11, scheduled crons still off) — see [2.0 ASN SFTP Ingestion](features/asn-sftp-ingestion-2-0.md). Entity map: `Case` = 1.0 managed_service_order, `ServiceRequest` = 1.0 repair_order; DOE does not use `Ticket`. Client models: `_underscore/Model/Nycdoe/`. 2.0 does **not** write to ServiceNow yet — the 1.0 RITM/POD crons keep sending until 2.0 takes over, then 1.0 is turned off.
36
37
  - **TOGa Desk:** DOE is client id **16** (`db_togadesk`) — repair orders, managed service orders, the "#received / #ordered" Receiving Summary UI.
37
38
  - **NetSuite:** Sales Orders, PO reconciliation, item receipts, Item Fulfillments, invoices (SuiteTalk toolkit in `library`).
38
39
  - **`dbchanges` (1.0):** DOE schema + **data-repair** changes ship here (`dbchanges/Core/` targets the legacy/V1 `Core` schema). Stranded-ASN recoveries are data-only repairs — see the [1.0 dbchanges authoring workflow](../../1.0/apps/dbchanges/workflows/authoring-and-shipping-sql-files.md).
@@ -20,7 +20,7 @@ project: _Underscore
20
20
  client: nychh
21
21
  type: profile
22
22
  status: active
23
- updated: 2026-10-22
23
+ updated: 2026-10-29
24
24
  owners: [jcardinal, apeterson, bala, akhokhani, mhammontree, rgirish]
25
25
  files:
26
26
  - dbchanges2/Client_Nychh/2026-10-18a - NewYorkWarehouseLocation.sql
@@ -128,7 +128,7 @@ TOGA 2.0 client on `_underscore`, prod schema `Client_Nychh`. Supply-chain / ful
128
128
  - **PO links are UPSTREAM (`PurchaseOrders_SalesOrders`), not downstream.** The downstream `sales-order-purchase-orders` route returns an empty array for NYCHH by design, and the shared `_purchaseOrders` field (downstream-only) resolves to `null` for this tenant — open gap. See [PO Number direction](./features/po-number-upstream-direction.md).
129
129
 
130
130
  ## Notes
131
- - **1.0 cron "Send NYCHH Sales Orders" sends nothing (2026-10-15, TRUE-82761, parked).** `worker/crons/toga2/nychh/send_sales_order.php` (`*/5`) emails each `SalesOrders` row with `c_dtTransmittedToNYCHH IS NULL` as an xlsx to the warehouse. In prod all 125 rows have it NULL and none has `createdByUserId` (all NetSuite-imported), so its `INNER JOIN Users ON createdByUserId` drops every row. Supply orders are now `TransferOrders`. A monitor on that column would be always HIGH or always OK — don't build one. Open: ask Jelena if the transmission is still used.
131
+ - **1.0 cron "Send NYCHH Sales Orders" sends nothing (2026-10-15, TRUE-82761, parked).** `worker/crons/toga2/nychh/send_sales_order.php` (`*/5`) emails each `SalesOrders` row with `c_dtTransmittedToNYCHH IS NULL` as an xlsx to the warehouse. In prod all 125 rows have it NULL and none has `createdByUserId` (all NetSuite-imported), so its `INNER JOIN Users ON createdByUserId` drops every row. Supply orders are now `TransferOrders`. A monitor on that column would be always HIGH or always OK — don't build one. Open: is the transmission still used? Owner unknown — Jelena has left TOGA Technology (2026-10-29).
132
132
  - **`Items.title` is NULL on most NYCHH rows — `description` is the populated label field.** Confirmed 2026-09-03 on client-sandbox (a `transfer-order-items` line returned `"title": null` with a real `partNumber`). Every screen that labels an item for this tenant needs the `title || description` chain — the Transfer Order record modal and the Create modal's item picker both carry it. The real fix is populating `Items.title`.
133
133
  - **Role ids in `Client_Nychh` (verified 2026-09-01):** `1 = Base`, `2 = Developer`, `3 = API`. Role ids differ per client DB — resolve by **name** in migrations, never hard-code these.
134
134
  - **The Agilant API (`Client_Nychh.Apis` id 1) holds BOTH Base (1) and API (3) via `Apis_Roles` (2026-10-06).** Record ACL = union, so checking only the API role misleads. Base grants full CRUD on 28/312/313 (ItemFulfillments, TransferOrders, TransferOrderItems); API role has no DELETE on 312/313 and no row for 28. Interceptors: record 312 has POST/POST + POST/PUT only (none on DELETE).
@@ -1,19 +1,19 @@
1
1
  ---
2
2
  title: "Prudential: order email PDF attachments (which guide goes on which email)"
3
3
  framework: "1.0"
4
- repo: worker
5
- project: Worker
4
+ repo: worker1.5
5
+ project: Worker 1.5
6
6
  client: prudential
7
7
  type: client-feature
8
8
  status: active
9
- updated: 2026-10-23
9
+ updated: 2026-10-29
10
10
  owners: ["bala"]
11
11
  files:
12
- - worker/crons/toga2/prudential/generate_sales_and_purchase_orders_from_service_requests.php
13
- - worker/crons/toga2/prudential/transmissions_to_netsuite.php
14
- - worker/crons/toga2/prudential/transmit_ordershipped_updates_prudential.php
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
12
+ - worker1.5/crons/toga2/prudential/generate_sales_and_purchase_orders_from_service_requests.php
13
+ - worker1.5/crons/toga2/prudential/transmissions_to_netsuite.php
14
+ - worker1.5/crons/toga2/prudential/transmit_ordershipped_updates_prudential.php
15
+ - worker1.5/crons/toga2/prudential/transmit_closecomplete_updates_prudential.php
16
+ - worker1.5/crons/toga2/prudential_beta/generate_sales_and_purchase_orders_from_service_requests.php
17
17
  - store/common/prudential/home.php
18
18
  related:
19
19
  - transmit-ordershipped-email.md
@@ -61,31 +61,37 @@ All three live under `https://external-client-files.s3.us-west-2.amazonaws.com/p
61
61
  |---|---|---|
62
62
  | `EquipMe-Win11-Sep_2026_1.1.pdf` (since 2026-10-12; history: `v1` → `Sep_2026` → `Sep_2026_1.1`) | `Getting_Started_Guide_Windows11.pdf` | 2,762,485 B |
63
63
  | `2026_Aug_PrudentialMacSetupGuide_ZCC.pdf` | `PrudentialMacSetupGuide.pdf` | 7,543,154 B |
64
- | `Prudential_Instruction-2Page-V1.pdf` | (unchanged) | 100,224 B |
64
+ | `Prudential_DeviceReturnGuide_10-30-2026.pdf` (return guide, since 2026-10-29; was `Prudential_Instruction-2Page-V1.pdf`, 100,224 B) | (unchanged, same name) | 189,076 B |
65
65
 
66
66
  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.
67
67
 
68
68
  **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_1.1 = 2,762,485; Sep_2026 = 2,735,276; 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).
69
69
 
70
70
  ### Swapping the Windows 11 guide: every hardcoded spot
71
- Recurring task: Prudential sends a new EquipMe Windows 11 PDF from time to time (content or phone-number fixes). There is **no setting or table** for the guide URL. It is a literal in **12 `$pdfUrl2` lines across 5 worker files plus 2 store links**, and all must change together:
71
+ Recurring task: Prudential sends a new EquipMe Windows 11 PDF from time to time (content or phone-number fixes). There is **no setting or table** for the guide URL. It is a literal in **12 `$pdfUrl2` lines across 5 worker1.5 files plus 2 store links**, and all must change together:
72
72
  | File | Where |
73
73
  |---|---|
74
- | `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`. |
75
- | `worker/crons/toga2/prudential/transmit_ordershipped_updates_prudential.php` | Shipped (Dell, vendorId 1): new-hire block, plus the shared block inside `if ($reqType != 'Reclaim')`. |
76
- | `worker/crons/toga2/prudential/transmit_closecomplete_updates_prudential.php` | Delivered: same two places, same Reclaim guard. |
77
- | `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. |
78
- | `worker/crons/toga2/prudential_beta/generate_sales_and_purchase_orders_from_service_requests.php` | Beta copy of order-created. |
74
+ | `worker1.5/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 the return guide. |
75
+ | `worker1.5/crons/toga2/prudential/transmit_ordershipped_updates_prudential.php` | Shipped (Dell, vendorId 1): new-hire block, plus the shared block inside `if ($reqType != 'Reclaim')`. |
76
+ | `worker1.5/crons/toga2/prudential/transmit_closecomplete_updates_prudential.php` | Delivered: same two places, same Reclaim guard. |
77
+ | `worker1.5/crons/toga2/prudential/transmissions_to_netsuite.php` | Shipped for Agilant-shipped POs (vendorId 2). Not scheduled on worker1.5, and prod has 0 vendor-2 POs with `c_dtEmailSentOrderShipped`, so it never fires today. Change it anyway. |
78
+ | `worker1.5/crons/toga2/prudential_beta/generate_sales_and_purchase_orders_from_service_requests.php` | Beta copy of order-created. |
79
79
  | `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. |
80
80
 
81
81
  Procedure:
82
82
  1. Get the new PDF onto `s3://external-client-files/prudential/` under a **new** basename (never overwrite: the old name is what is live until deploy).
83
83
  2. `curl -sI <new url>` must return **200**. A new upload can be private: on 2026-10-12 `EquipMe-Win11-Sep_2026_1.1.pdf` returned **403** while the old file returned 200. Fix the object ACL/policy before deploying (no AWS writes from Claude).
84
- 3. Branch from `_production` in **both** `worker` and `store`.
85
- 4. One global find-replace of the old basename in `worker/crons/toga2/prudential*` and `store/common/prudential/home.php` covers every spot. `grep` the old name after: expect 0 hits; new name: 12 in worker, 2 in store.
84
+ 3. Branch from the default branch in **both** `worker1.5` (`_main`) and `store` (`_production`).
85
+ 4. One global find-replace of the old basename in `worker1.5/crons/toga2/prudential*` and `store/common/prudential/home.php` covers every spot. `grep` the old name after: expect 0 hits; new name: 12 in worker1.5, 2 in store.
86
86
  5. Leave the Mac guide (`$pdfUrl4`, `2026_Aug_PrudentialMacSetupGuide_ZCC.pdf`) alone unless it is the one being swapped. Reclaim stays excluded.
87
87
 
88
- Why step 2 matters: the crons fetch through `safeFetchPdf()`, which returns `false` on a 403, so a private file **silently drops the Windows 11 attachment** (and its sentence) from every email; nothing errors. The store link opens an S3 `AccessDenied` page.
88
+ Why step 2 matters: the crons fetch through `safeFetchPdf()`, which returns `false` on a 403, so a private file **silently drops the attachment** (and its sentence) from every email; nothing errors. The store link opens an S3 `AccessDenied` page.
89
+
90
+ ### Swapping the return guide
91
+ Same steps 1, 2 and 4, with these differences:
92
+ - The basename is used **3 times per spot**: `$pdfUrl3`, the `$cacheFolder` path, and the `addAttachment()` name. The customer sees the S3 basename (no rename on attach).
93
+ - **24 hits in 5 worker1.5 files**: order-created 9 (3 branches), shipped 3 + 3 (both shipped crons), delivered 3, beta order-created 6.
94
+ - `store` has no return-guide link (0 hits in `home.php`), so only `worker1.5` changes.
89
95
 
90
96
  ### The verbiage has to match what actually attached
91
97
  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.**
@@ -130,7 +136,7 @@ No committed harness. Build a throwaway `test_<x>_emails.php` next to the cron,
130
136
  ## Gotchas
131
137
  - **`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.**
132
138
  - **`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.
133
- - **`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.
139
+ - **`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 old `PrudentialMacSetupGuide.pdf` (not the `_ZCC` one). **A beta test will not reproduce prod Mac-guide behavior.** The Win11 guide (2026-10-05) and the return guide (2026-10-29) are kept in sync with prod; the Mac guide is left as-is on purpose.
134
140
  - **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.
135
141
  - The delivered cron (`transmit_closecomplete_updates_prudential.php`) already followed the rule and was deliberately **not** touched.
136
142
 
@@ -140,6 +146,7 @@ No committed harness. Build a throwaway `test_<x>_emails.php` next to the cron,
140
146
  - [App_Email Side Effects & Test Mode](../../../1.0/apps/library/features/email-test-mode-and-write-free-sends.md)
141
147
 
142
148
  ## Change history
149
+ - 2026-10-29 — Return guide swapped `Prudential_Instruction-2Page-V1.pdf` → `Prudential_DeviceReturnGuide_10-30-2026.pdf` in all 24 spots, beta included; file paths now worker1.5 (TRUE-83422) (bala)
143
150
  - 2026-10-23 — Schedule now `worker1.5/schedules/cron.worker.json`; linked the new order-email-recipients doc (TRUE-83157) (bala)
144
151
  - 2026-10-12 — Win11 guide swapped `Sep_2026` → `Sep_2026_1.1` (phone number change); added swap procedure + S3 403 check (bala)
145
152
  - 2026-10-05 — Win11 guide swapped `v1` → `Sep_2026` in all 12 cron spots + Equip Me page (bala)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "toga-ai",
3
- "version": "1.0.1207",
3
+ "version": "1.0.1209",
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",