toga-ai 1.0.1127 → 1.0.1129

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.
@@ -34,7 +34,7 @@
34
34
  | [NetSuite SuiteQL/REST Shim — Field Semantics](features/netsuite-suiteql-rest-shim.md) | Non-obvious NetSuite SuiteQL/REST field semantics behind `App_Api_Netsuite_Rest` shims (signs, `tl.id`==line, `iscogs`, ShipItem cost, `createdFrom`, shim-shape |
35
35
  | [NetSuite Sync Alert Monitor (App_SystemMonitor_NetSuiteIntegration)](features/netsuite-sync-alert-monitor.md) | 1.0 monitor `App_SystemMonitor_NetSuiteIntegration` — flags stale per-client NetSuite sync checkpoints (>48h) to ClickUp; open when a sync-stale alert is missin |
36
36
  | [Unit serial details from NetSuite (date received, wipe, lock, notes, return tracking) — opt-in per client](features/netsuite-unit-serial-details-sync.md) | NetSuite inventory-number (serial) fields synced onto `Units` behind `IS_ENABLED_NETSUITE_UNIT_SERIAL_DETAILS`, plus the new-unit condition constant; open befor |
37
- | [Running library (1.0) on PHP 8.4 / 8.5 — deprecations, guards, what is left](features/php-8-5-runtime-compatibility.md) | How library (1.0) handles PHP 8.4/8.5 deprecations (logged, not fatal), the PHP 7.2-safe guard patterns, and what is still open; open before moving any 1.0 app |
37
+ | [Running library (1.0) on PHP 8.4 / 8.5 — deprecations, guards, what is left](features/php-8-5-runtime-compatibility.md) | How library (1.0) handles PHP 8.4/8.5 deprecations (logged, not fatal), the PHP 7.2-safe guard patterns, IMAP without ext-imap (php-imap2), and what is still op |
38
38
  | [Startech PC Matic B2B Sync (library)](features/startech-pcmaticb2b-sync.md) | PC Matic B2B bidirectional ticket sync between TOGaDesk 1.0 and TOGA 2.0 (`App_Api_Toga2`); open for ticket-type maps, StarTech client onboarding, the per-clien |
39
39
  | [App_Api_Toga2 — TOGa2 API Client & 1.0↔2.0 Sync Bridge](features/toga2-api-client-and-bridge.md) | `App_Api_Toga2` — the 1.0-side client for the TOGa2/api2 REST API + cross-framework sync routines (`send`/`authenticate`, options DSL, `syncWithToga`/`syncWithT |
40
40
  | [Transfer order ↔ PO link reconcile (both directions, header + line, from NetSuite)](features/transfer-order-purchase-order-link-reconcile.md) | `App_Api_Toga2::reconcileTransferOrderPurchaseOrderLinks()` makes one TO's PO links (upstream + downstream, header + line) match NetSuite; open before touching |
@@ -9,6 +9,12 @@ status: active
9
9
  updated: 2026-10-23
10
10
  owners: ["rgirish", "jcardinal", "bala"]
11
11
  files:
12
+ - library/php-imap2/bootstrap.php
13
+ - library/php-imap2/src/Polyfill.php
14
+ - library/php-imap2/src/HeaderInfo.php
15
+ - library/php-imap2/src/Functions.php
16
+ - library/php-imap2/src/Roundcube/MessageHeader.php
17
+ - library/php-imap2/TOGA-PATCHES.md
12
18
  - library/app/error.php
13
19
  - library/app/date.php
14
20
  - library/app/database.php
@@ -34,9 +40,11 @@ related:
34
40
  - ../../api/workflows/deploy-to-elastic-beanstalk-al2023.md
35
41
  - ../../togaview/workflows/deploy-to-elastic-beanstalk-al2023.md
36
42
  - ../../togadesk/workflows/deploy-to-elastic-beanstalk-al2023.md
43
+ - ../../togadesk/features/email-to-ticket-intake.md
44
+ - ../../worker1.5/architecture.md
37
45
  ---
38
46
 
39
- How library (1.0) handles PHP 8.4/8.5 deprecations (logged, not fatal), the PHP 7.2-safe guard patterns, and what is still open; open before moving any 1.0 app to PHP 8.4+ or touching `App_Error::handleError`.
47
+ How library (1.0) handles PHP 8.4/8.5 deprecations (logged, not fatal), the PHP 7.2-safe guard patterns, IMAP without ext-imap (php-imap2), and what is still open; open before moving any 1.0 app to PHP 8.4+, touching `App_Error::handleError`, or editing `php-imap2/`.
40
48
 
41
49
  ## Summary
42
50
 
@@ -68,6 +76,17 @@ Null guards (TRUE-83054, commit `04a2293c`, landed directly on `_production`)
68
76
 
69
77
  **Main 8.x break classes in 1.0 app code** (found moving `api`): `null` into internal string functions (`trim`, `base64_decode`, `stripos` — deprecated 8.1) → cast `(string)`; reading keys of an empty `fetchRow` result (warning since 8.0) → default the array — an app with hundreds of these can keep 7.2 behavior with a narrow error-handler shim for exactly "array offset on false/null" (togadesk, [Desk deploy § PHP 8 shims](../../togadesk/workflows/deploy-to-elastic-beanstalk-al2023.md#php-8-shims-in-desk); does not cover crons); NULL DB dates into `App_Date` → guard first. Fix in library when the root cause is in a library helper.
70
78
 
79
+ ### IMAP without ext-imap (php-imap2)
80
+
81
+ ext-imap is gone from PHP 8.4+, so on 8.5 hosts (worker1.5) every `imap2_*` call runs on library's vendored `php-imap2/` (v0.1.10, 2022 — treat as unmaintained; decided TRUE-83148: patch it in library, not a wrapper, not PECL imap, not re-vendor — upstream Polyfill is stubs too). Fixed in `4a2c363f` (TRUE-83148, on `_production` 2026-10-23):
82
+ - `App_Framework::registerPhpImap2()` now loads `src/Polyfill.php` (missing → `Class "Javanile\Imap2\Polyfill" not found` from `imap2_headerinfo`).
83
+ - Polyfill has plain-PHP versions (no zbateson, 7.2 safe) of rfc822 write/parse address, mime header decode (returns objects; charset `default` for plain text, no conversion — like native), base64/binary/qprint/8bit, utf7 (ISO-8859-1 like native).
84
+ - `imap2_8bit` checks `IMAP2_RETROFIT_MODE`, not `function_exists('imap_8bit')` (bootstrap defines that itself → endless recursion).
85
+ - `HeaderInfo` builds to/from/cc/reply_to/sender straight from the envelope; drops entries with empty mailbox or host (group markers like `undisclosed-recipients:;`).
86
+ - `MessageHeader` properties declared (8.2 dynamic property).
87
+
88
+ `library/php-imap2/TOGA-PATCHES.md` lists every patch — re-apply after any re-vendor.
89
+
71
90
  **Find new ones:** `php -l -d display_errors=1` with an 8.4+ binary per file (compile-time). Runtime-only ones (`curl_close`, `setAccessible`, `utf8_*`) need a grep. On a live 8.5 app, read `error_log` — each deprecation is logged once per call site.
72
91
 
73
92
  ## Gotchas
@@ -80,8 +99,13 @@ Null guards (TRUE-83054, commit `04a2293c`, landed directly on `_production`)
80
99
  - **Do not "repair" the removed `App_Edi::cleanText` block.** A working version would replace every character 0-127 (all letters and digits) with spaces and blank every EDI file.
81
100
  - Local dev on PHP 8.2 does not show 8.4/8.5 deprecations — test with an 8.5 binary.
82
101
  - **PHP 8 string-to-number comparison can turn a loop infinite.** `api` subscription: a non-numeric quantity compared to an int looped forever on 8.x. Cast `(int)` before the loop.
102
+ - **OAuth IMAP (`imap2_open(..., OP_XOAUTH2)`) always uses the Roundcube path — even on PHP 7.2 with ext-imap.** So `HeaderInfo::fromMessage` runs on the 7.2 worker too. Gate every php-imap2 behavior change on `!IMAP2_RETROFIT_MODE` (= `function_exists('imap_open')` at bootstrap load) so 7.2 stays byte-identical.
103
+ - **php-imap2 gaps still open (no-ext-imap only):** group end writes `grp: ` not `grp: ;`; `mutf7ToUtf8` / `utf8ToMutf7` / `utf8` return input; `mailCompose` returns `false`; `rfc822ParseHeaders` still needs zbateson (not on importer paths). `imap_binary` line width (60, CRLF) not compared to real ext-imap. 7.2 safety checked by reading only.
104
+ - **Needs `mbstring` + `iconv`** on the 8.5 host for the Polyfill decode paths — check `php -m`.
105
+ - **A library fix reaches worker1.5 only after a worker1.5 redeploy** ([worker1.5 architecture](../../worker1.5/architecture.md)).
83
106
 
84
107
  ## Change history
108
+ - 2026-10-23 — php-imap2 works without ext-imap: Polyfill loaded + filled in; OAuth/Roundcube gotcha; open gaps (TRUE-83148, `4a2c363f`) (jcardinal)
85
109
  - 2026-10-23 — `instance-data` does not resolve on AL2023 (empty instance id, log noise); deprecations-continue behavior confirmed in the worker1.5 cron log (TRUE-83107). (bala)
86
110
  - 2026-10-23 — `case 'x';` 8.5 deprecation fixed in vendored php-imap2 (TRUE-83116). (bala)
87
111
  - 2026-10-22 — Dynamic-property fixes; app-level offset-on-false shim pattern from the Desk move (TRUE-83055, TRUE-83053) (rgirish)
@@ -6,7 +6,7 @@ project: TOGa Desk
6
6
  client: shared
7
7
  type: feature
8
8
  status: active
9
- updated: 2026-10-19
9
+ updated: 2026-10-23
10
10
  owners: ["jcardinal", "dfranks", "mhammontree"]
11
11
  files:
12
12
  - crons/tickets.php
@@ -21,6 +21,8 @@ related:
21
21
  - 1.0/apps/togadesk/features/s3fs-asifiles-file-storage.md
22
22
  - 1.0/apps/library/features/app-cloud-aws-credentials.md
23
23
  - 1.0/apps/worker/architecture.md
24
+ - 1.0/apps/worker1.5/architecture.md
25
+ - 1.0/apps/library/features/php-8-5-runtime-compatibility.md
24
26
  ---
25
27
 
26
28
  TOGa Desk email→ticket intake via IMAP poller (`crons/tickets.php`) + postfix pipe (`crons/pipe.php`), both into `Ticket::emailToTicket()`; open for routing/threading, noise suppression, or the sender auto-create of `people` rows.
@@ -72,6 +74,7 @@ Ruled out as user-creation sources (do not re-chase):
72
74
 
73
75
  ## Gotchas
74
76
  - **`$processName` guard:** `tickets.php` throws if executed directly instead of via an env wrapper — and `ENVIRONMENT`/`$env` must be set or it loads the wrong config.
77
+ - **On worker1.5 (PHP 8.5, no ext-imap) intake depends on library's php-imap2 patches.** Before `4a2c363f` the prod cron died at `desk/vendor/classes/class.imap.php:228` (`imap2_headerinfo` → `Class "Javanile\Imap2\Polyfill" not found`). There the cron is `php /var/www/html/crons/../ontrack/crons/tickets_prod.php`; a library fix reaches it only after a worker1.5 redeploy. See [PHP 8.5 § IMAP without ext-imap](../../library/features/php-8-5-runtime-compatibility.md#imap-without-ext-imap-php-imap2) (TRUE-83148).
75
78
  - **A togadesk merge does NOT update the cron.** The cron runs on the 1.0 **worker** (`/var/www/html/ontrack`, cloned from togadesk at worker deploy — NOT `/var/www/ontrack`). The `Worker` CodePipeline sources only `worker` + `library`; a togadesk merge runs `TOGaDesk-Production` (web envs) only. After any `crons/` change, **release the `Worker` pipeline by hand**. TRUE-82935: PR #625 merged 10-16 16:15, reached the cron only 10-19 13:10. See [worker architecture](../../worker/architecture.md).
76
79
  - **Never load a second composer autoloader in the cron as a fallback** (e.g. root `vendor/autoload.php` for the AWS SDK) — CTO decision TRUE-82935. Keep the cron off `App_Cloud`; any `App_Cloud` call there throws `Error` ([App_Cloud](../../library/features/app-cloud-aws-credentials.md)).
77
80
  - **Attachment outage 2026-10-12 → 10-19 (TRUE-82935).** 10-12 15:00 → 10-16 15:00: `files` rows written, S3 object missing (`copyFileToS3` threw). 10-16 15:16 → 10-19 13:10: PR #624 deleted the row on failure → no attachments at all. First good run 10-19 13:20 (files 615609-615611). Attachments lost 2026-10-12 15:00 → 2026-10-19 13:10 were **NOT recovered** (developer decision, TRUE-82935); the original emails remain in IMAP "Trash" if ever needed.
@@ -84,6 +87,7 @@ Ruled out as user-creation sources (do not re-chase):
84
87
  - **`adminid` clobber on reply routing:** after `emailToTicket()` sets `$data['adminid']` from the FROM address (~line 1407), an asset lookup (~line 1437-1442) overwrites it with the replying user's asset's assigned admin. On a matched-ticket reply this makes a user's email look like a staff reply in `addReply()` (`isAdminReply=true`), so `Awaiting User` etc. never transition. The asset-derived adminid is for NEW-ticket auto-assign in `add()`, not reply classification. See the ticket-lifecycle doc.
85
88
 
86
89
  ## Change history
90
+ - 2026-10-23 — worker1.5 / PHP 8.5 gotcha: intake needs library php-imap2 patches (TRUE-83148) (jcardinal)
87
91
  - 2026-10-19 — Attachments saved straight into `desk/uploads` (s3fs), no `App_Cloud`; lost attachments not recovered; worker-pipeline deploy gotcha; corrected live `class.imap.php` copy; wider blocked-file pattern (TRUE-82935) (jcardinal)
88
92
  - 2026-09-25 — renamed product reference from "TogaIQ" to "Talos" (rebrand; TOGa IQ is now the separate `toga25-iq` app) (jcardinal)
89
93
 
@@ -23,6 +23,7 @@ related:
23
23
  - ../../standards/backend-php.md
24
24
  - workflows/diagnosing-crons-not-running.md
25
25
  - features/committed-secrets-and-log-exposure.md
26
+ - ../library/features/php-8-5-runtime-compatibility.md
26
27
  ---
27
28
 
28
29
  The single-server 1.0 cron tier that replaced worker's 7-role fleet; open for how the crontab is built from schedules/*.json, the overlap check, and what the cron list page is (display only).
@@ -55,6 +56,7 @@ Schedule files: `cron.alpha/beta/demo/hotfix/sprint/stage/ui/worker.json`. Only
55
56
  No CloudWatch log group: every job's output is `/var/log/worker-cron.log` on the box; reach it with SSM Session Manager. `Common.CronJobExecutions.instanceId` is `''` for this server. Crons stalled or many 1.0 backlogs at once: [Diagnosing worker1.5 crons that stopped running](workflows/diagnosing-crons-not-running.md).
56
57
 
57
58
  ## Gotchas
59
+ - **IMAP crons depend on library's php-imap2 patches.** No ext-imap on PHP 8.5; php-imap2 comes from the cloned `library`, not composer. Desk, Quad and ~20 Compass / Compass Canada IMAP crons crashed until library `4a2c363f` (see [PHP 8.5 § IMAP without ext-imap](../library/features/php-8-5-runtime-compatibility.md#imap-without-ext-imap-php-imap2)). togadesk is cloned as `ontrack` → `/var/www/html/ontrack`; the prod Desk importer is `php /var/www/html/crons/../ontrack/crons/tickets_prod.php` (TRUE-83148).
58
60
  - **Do not read files that are not in this repo on a 1.0 page.** The page read `schedules/cron.worker.catalog.json` (old worker only). The warning became `exit` and the page died about 474 times an hour (9,387 total), probably from a health check on `/`. Fixed in TRUE-83107 (c903b20).
59
61
  - **`App_Misc::getFileList` uses `readdir` and does not sort.** Any loop that keeps state per file must set that state on every file. The old loop set `$jsonContents` only for a non-empty category, so `cron.worker.json` reused the previous file's rows, or warned (and exited) if it came first.
60
62
  - **Cron log noise you can ignore:** `PHP Deprecated: ...` lines (the job continues) and `getaddrinfo for instance-data failed` warnings (see [PHP 8.5 compatibility § Gotchas](../library/features/php-8-5-runtime-compatibility.md)). Two open faults show up in the log: the OptimumDesk sync prints a credential row into its exception ([Error capture 1.0 § Gotchas](../library/features/error-capture-1-0.md)), and empty-From emails fail at SES every run ([Email queue § Gotchas](../library/features/email-queue-attachments.md)).
@@ -7,7 +7,7 @@ _Auto-generated by `knowledge.js index`. Do not hand-edit._
7
7
  - **library** (Library) _(framework core)_ — 41 doc(s) → [1.0/apps/library/INDEX.md](1.0/apps/library/INDEX.md)
8
8
  - **worker** (Worker) — 43 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) — 4 doc(s) → [1.0/apps/worker1.5/INDEX.md](1.0/apps/worker1.5/INDEX.md)
10
+ - **worker1.5** (Worker 1.5) — 5 doc(s) → [1.0/apps/worker1.5/INDEX.md](1.0/apps/worker1.5/INDEX.md)
11
11
  - **togadesk** (TOGa Desk) — 18 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)
@@ -5,8 +5,8 @@ project: _Underscore
5
5
  client: compass-canada
6
6
  type: client-feature
7
7
  status: active
8
- updated: 2026-09-16
9
- owners: ["bala"]
8
+ updated: 2026-10-23
9
+ owners: ["bala", "jcardinal"]
10
10
  files:
11
11
  - api2/Component/Api/Cxml/Cxml.php
12
12
  - worker/crons/toga2/compasscanada/workflow/3_import_grand_and_toy_advance_shipping_notices.php
@@ -68,8 +68,8 @@ The importer's only file-level gate is the **column count of the first line, whi
68
68
 
69
69
  ### Reading the mailbox without touching it (`check_grand_and_toy_inbox.php`)
70
70
  `worker/crons/toga2/compasscanada/workflow/check_grand_and_toy_inbox.php` is a **read-only inspector** for `compasscanada.status@togatech.com`. Opens `INBOX`, `ARCHIVE` and `Junk Email` with `OP_XOAUTH2 | OP_READONLY` and **never moves, deletes, imports or emails anything**, so it is safe to run in production. Per attachment it reports: byte size; the first line's column count against the 24-column contract; the first 3 lines each with their own column count (a stray title row is the usual culprit); and a **row-level dry run** mirroring the importer's per-row guards (importable / no tracking / no PO / no part number / scientific-notation tracking / electronic delivery). `--save=<folder>` dumps each attachment to disk. It reads the Azure OAuth values **out of the importer file at runtime** rather than duplicating the client secret. Two 1.0 IMAP gotchas encoded in it:
71
- - **`imap2_headerinfo()` fails on this setup** — it needs `ZBateson\MailMimeParser`, not installed. Read raw headers with **`imap2_fetchheader()`** and parse `Date` / `From` / `Subject` with a regex instead.
72
- - **`App_Framework::registerPhpImap2()` does not load `php-imap2/src/Polyfill.php`**, so loading that file does not give you `imap2_headerinfo()` either.
71
+ - **`imap2_headerinfo()` failed on the old 7.2 worker** — it needed `ZBateson\MailMimeParser`, not installed. Read raw headers with **`imap2_fetchheader()`** and parse `Date` / `From` / `Subject` with a regex instead. On worker1.5 (PHP 8.5, no ext-imap) it works since library `4a2c363f` (TRUE-83148); the 7.2 path is unchanged on purpose.
72
+ - Since `4a2c363f`, `App_Framework::registerPhpImap2()` loads `php-imap2/src/Polyfill.php`. Same fix unbroke the Compass / Compass Canada IMAP crons on worker1.5. See [PHP 8.5 § IMAP without ext-imap](../../../1.0/apps/library/features/php-8-5-runtime-compatibility.md#imap-without-ext-imap-php-imap2).
73
73
 
74
74
  ### In-transit email is owned by the cron, not api2
75
75
  The importer sends the Compass Canada in-transit email itself and stamps `TrackingNumbers.c_dtInTransitEmailSent`, guarded by `if (!empty($salesOrderNumber) && $isNewTrackingNumber)`. So the email fires only when the cron creates a brand-new `TrackingNumbers` row. Two consequences: (1) never delete a `TrackingNumbers` row during cleanup — a replay recreates it as new and sends a duplicate in-transit email; keep the row and its `c_dtInTransitEmailSent` stamp; (2) posting an ASN by hand through api2 emails nobody, useful for a silent data repair. Downstream, `worker/crons/toga2/compasscanada/update_salesorder_status_from_g&t.php` selects candidates via `AdvanceShippingNoticeItemUnits` + `AdvanceShippingNoticeItemUnits_TrackingNumbers`, so an ASN with **zero items** can never reach it — no partial in-transit or delivered email either. See [Compass Partial In-Transit & Delivered Emails](../../../1.0/apps/worker/features/compass-partial-in-transit-delivered-emails.md).
@@ -4,5 +4,6 @@
4
4
  |-----|-----------|---------|
5
5
  | [ODP Monthly Talos Call Transcript Report (worker2 cron)](features/odp-call-transcript-monthly-report.md) | 2.0 | Monthly xlsx of ODP Talos voice-agent calls, built from S3 session reports and emailed; open before any ODP call reporting work. |
6
6
  | [Office Depot Phone Tech Support](features/odp-tech-support.md) | standalone | Office Depot's phone agent guides store associates through Tier-1 support and records Desk tickets; open for its client-specific prompt, tools, tuning and deplo |
7
+ | [Office Depot — OptimumDesk SKU Sync (RetailServices supplier 14, POST /api/skus)](features/optimumdesk-sku-sync.md) | 1.0 | Office Depot store SKUs (400122/400123/400124) pushed to OptimumDesk by the worker1.5 RetailServices sync; open for the /api/skus payload rules, the 400122 work |
7
8
  | [TOGaRefresh 2026 — linkBuilderRefresh URL Routing (ODP)](features/togarefresh2026-linkbuilder-routing.md) | 1.0 | In TOGaRefresh 2026, service-builder buttons must route via `App_Model_ServiceRequest::linkBuilderRefresh()` (`serviceselection`), not legacy `linkBuilder()` (` |
8
9
  | [Office Depot](profile.md) | standalone | Office Depot uses TOGa Voice for inbound store-associate tech support and Desk ticketing; open for client scope and links to its voice configuration. |
@@ -0,0 +1,40 @@
1
+ ---
2
+ title: Office Depot — OptimumDesk SKU Sync (RetailServices supplier 14, POST /api/skus)
3
+ framework: "1.0"
4
+ repo: worker1.5
5
+ project: Worker 1.5
6
+ client: office-depot
7
+ type: client-feature
8
+ status: active
9
+ updated: 2026-10-23
10
+ owners: [snaredla]
11
+ files:
12
+ - worker1.5/crons/sync/retailservices/sync_retail_services_client_service.php
13
+ - toga/app/togarefresh2026/refurbishments/action.php
14
+ related:
15
+ - clients/office-depot/profile.md
16
+ - clients/office-depot/features/togarefresh2026-linkbuilder-routing.md
17
+ - 1.0/apps/togaview/features/retail-commerce-lifecycle.md
18
+ - 1.0/apps/worker1.5/architecture.md
19
+ ---
20
+
21
+ Office Depot store SKUs (400122/400123/400124) pushed to OptimumDesk by the worker1.5 RetailServices sync; open for the /api/skus payload rules, the 400122 workorder/businessUnit requirement, or stuck TOSYNC rows.
22
+
23
+ ## Summary
24
+
25
+ `sync_retail_services_client_service.php` sends Office Depot service rows to OptimumDesk (`scuses.optimumdesk.com`, `POST /api/skus`). Only one `RetailServices.ClientSuppliers` row uses `api='SKUS'` (id 14, businessUnit 11110), so this branch affects only Office Depot SKUs 400122/400123/400124.
26
+
27
+ ## How it works
28
+
29
+ - Payload always sends `customer.customerId`. All store refurbs use the store customer "Store Officedepot" = OptimumDesk customerId 1626767.
30
+ - SKU 400122 ("Certified Open Box PC", created by togarefresh2026 `refurbishments/action.php`) opens a PC wipe ticket at OptimumDesk. It **requires** `workorder` and `businessUnit` in the payload. The sync adds both only when 400122 is in the order (TRUE-80576).
31
+ - `workorder` = the TOGa- number; for a non-TOGa ref, ref + 1000000000.
32
+ - `ClientAccounts.clientRefAccountId` `TOGa-<n>` = TOGA_ODP `ServiceRequests.serviceRequestNumber` `<n>`.
33
+ - Success reply carries `data.workorder_id`. Outbound calls are logged in `Logs.API` (legacy cluster).
34
+ - Login is the integration user stored on the `RetailServices.ClientSuppliers` row (never copy it into docs).
35
+
36
+ ## Gotchas
37
+
38
+ - Without `workorder`/`businessUnit`, 400122 fails with HTTP 401 "Sku details not found or has been removed" — misleading; it is not auth and not the customer. Broken by commit bb70017cd (TRUE-76320, 2026-01-28, old worker repo) which trimmed the payload to customerId. 400124 works without them, so do not trim per-SKU fields again without testing 400122.
39
+ - Failed rows stay `syncStatus = TOSYNC` and retry every cron run — 15 stuck orders produced 24,666 occurrences of one Tools error issue. A growing error count usually means a few stuck rows, not many failures.
40
+ - To check the customer, `GET /customer/{id}` with the same login works.
@@ -11,11 +11,12 @@ apps:
11
11
  - dbchanges2
12
12
  - forward
13
13
  - store
14
+ - worker1.5
14
15
  project: TOGa Voice
15
16
  client: office-depot
16
17
  type: profile
17
18
  status: active
18
- updated: 2026-10-22
19
+ updated: 2026-10-23
19
20
  owners: [akhokhani, mhammontree, jcardinal, snaredla, rgirish]
20
21
  files: []
21
22
  related:
@@ -42,6 +43,7 @@ Office Depot is the production support client of the standalone Python/LiveKit T
42
43
  - [Ticket API](../../standalone/apps/voice-to-voice/features/ticket-api-integration.md) owns signing and client/department routing.
43
44
  - [Post-call Lambda](../../standalone/apps/voice-to-voice/features/post-call-lambda.md) owns recordings/transcript attachments and its dated outage evidence.
44
45
  - [TOGa Desk](../../1.0/apps/togadesk/architecture.md) is the ticket system of record.
46
+ - [OptimumDesk SKU sync](features/optimumdesk-sku-sync.md) — worker1.5 RetailServices push of store SKUs 400122/400123/400124.
45
47
 
46
48
  ## Gotchas
47
49
 
@@ -6,8 +6,8 @@ project: Worker
6
6
  client: quad
7
7
  type: client-feature
8
8
  status: active
9
- updated: 2026-09-16
10
- owners: ["ajean"]
9
+ updated: 2026-10-23
10
+ owners: ["ajean", "jcardinal"]
11
11
  files:
12
12
  - worker/crons/toga2/quad/import_po_and_asn.php
13
13
  related:
@@ -65,6 +65,7 @@ Rule: verify each key against `SHOW COLUMNS` on the tenant table before assuming
65
65
  - **Reporting branches must use variables that exist in their own scope.** The ASN item-fulfillment failure branch keyed its result on `$purchaseOrder->number` — the loop variable of `processPurchaseOrdersFile()`, undefined in `processAdvanceShippingNoticeFile()`. A soft-fail path that crashes re-jams the mailbox. It now uses `$purchaseOrderNumber`, the enclosing `foreach` key.
66
66
  - **Deploy lag: merging to `_production` does not ship the worker.** Three separate PRs sat merged-but-not-deployed to the `agilant-worker` EB environment; one sat overnight while the mailbox re-failed every 3 minutes and the issue climbed from 20 to 354 occurrences — repeatedly looked like "the fix failed." **Verification:** compare the reported trace line against current source — a trace pointing at a line that is now a comment (or no longer exists) proves a stale binary, not a bad fix.
67
67
  - **`fgetcsv($handle)` is called without `$escape` throughout `worker`.** Fine on the `agilant-worker` runtime (PHP 7.2.13) but deprecated on 8.5 — and 1.0 promotes deprecations, so this breaks hard if `worker` follows `worker2` onto 8.5.
68
+ - **On worker1.5 (PHP 8.5, no ext-imap; copy at `worker1.5/crons/toga2/quad/class.imap.php`) the importer depends on library's php-imap2 patches** (`4a2c363f`, TRUE-83148) — before them `imap2_headerinfo` died with `Class "Javanile\Imap2\Polyfill" not found`. See [PHP 8.5 § IMAP without ext-imap](../../../1.0/apps/library/features/php-8-5-runtime-compatibility.md#imap-without-ext-imap-php-imap2).
68
69
  - **Where the untruncated api2 envelope lives.** The error-console message is `varchar(255)` and severs a `print_r`-dumped api2 envelope right where `status`, `error.code` and `messages[]` begin. The full request/response is in the client-scoped `Logs_Quad.Api`; the original attachment content is in `Logs.Event.context` (7-day purge). See [Error reporting — Issue/Event](../../../2.0/apps/_underscore/features/error-reporting-issue-event.md).
69
70
 
70
71
  ### Open issues
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "toga-ai",
3
- "version": "1.0.1127",
3
+ "version": "1.0.1129",
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",