toga-ai 1.0.634 → 1.0.635
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/knowledge/1.0/apps/worker/INDEX.md +1 -1
- package/knowledge/1.0/apps/worker/features/compass-partial-in-transit-delivered-emails.md +19 -1
- package/knowledge/2.0/apps/_underscore/INDEX.md +1 -1
- package/knowledge/2.0/apps/_underscore/features/carrier-shipping-labels.md +31 -2
- package/knowledge/2.0/apps/worker2/INDEX.md +1 -0
- package/knowledge/2.0/apps/worker2/features/tracking-status-refresh.md +208 -0
- package/knowledge/2.0/apps/worker2/workflows/php-runtime-upgrade-dependency-audit.md +29 -2
- package/knowledge/INDEX.md +1 -1
- package/package.json +1 -1
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
| [Worker (1.0 Framework) Architecture](architecture.md) | `worker` is the legacy (**1.0** `App_` framework) **background-job tier**. | worker/index.php, worker/_/app/framework.php, worker/crons/, worker/schedules/, worker/ebs/cron.worker.php, worker/.ebextensions/035_cron.worker.config, worker/crons/infrastructure/execute_dbchanges.php, worker/.ebextensions/030_dbchanges.config |
|
|
6
6
|
| [Compass MA Sales Order Exception Report](features/compass-ma-sales-order-exception-report.md) | A worker cron that emails operations the "Compass Refresh Exception Report" — Compass `MA%` sales orders whose corresponding Office Depot (ODP) sales order has | worker/crons/toga2/compass/workflow/7_generate_ma_sales_order_exception_report.php |
|
|
7
7
|
| [Compass Manager Approval Reminder Emails (1.0 worker crons)](features/compass-manager-approval-reminder-emails.md) | Two 1.0 worker crons nag approvers about sales orders still waiting on a decision — one per Compass tenant. | worker/crons/toga2/compass/compass_email_reminders.php, worker/crons/toga2/compasscanada/compass_email_reminders.php, worker/schedules/cron.worker.sync.json, worker1.5/crons/toga2/compass/compass_email_reminders.php, worker1.5/schedules/cron.worker.json |
|
|
8
|
-
| [Compass Partial In-Transit & Delivered Emails (per package)](features/compass-partial-in-transit-delivered-emails.md) | Compass USA and Compass Canada send a **per-package** in-transit email (and a matching delivered email) instead of one email listing the whole order. | worker/crons/toga2/compass/update_salesorder_status_from_odp.php, worker/crons/toga2/compasscanada/update_salesorder_status_from_odp.php, worker/crons/toga2/compasscanada/workflow/3_update_salesorder_status_from_grand_and_toy.php, worker/crons/toga2/compasscanada/send_delivered_email.php, worker/crons/toga2/compasscanada/workflow/test_partial_in_transit_email.php, worker/crons/toga2/compasscanada/workflow/test_partial_delivered_email.php, library/app/client/compasscanada.php |
|
|
8
|
+
| [Compass Partial In-Transit & Delivered Emails (per package)](features/compass-partial-in-transit-delivered-emails.md) | Compass USA and Compass Canada send a **per-package** in-transit email (and a matching delivered email) instead of one email listing the whole order. | worker/crons/toga2/compass/update_salesorder_status_from_odp.php, worker/crons/toga2/compass/send_delivered_email.php, worker/crons/toga2/compasscanada/update_salesorder_status_from_odp.php, worker/crons/toga2/compasscanada/workflow/3_update_salesorder_status_from_grand_and_toy.php, worker/crons/toga2/compasscanada/send_delivered_email.php, worker/crons/toga2/compasscanada/workflow/test_partial_in_transit_email.php, worker/crons/toga2/compasscanada/workflow/test_partial_delivered_email.php, library/app/client/compasscanada.php |
|
|
9
9
|
| [Elite TOGA 2.0 → TOGaDeskSupport Standalone Attachment Sync](features/elite-togadesk-attachment-sync.md) | `sync_togadesk_elite_attachments.php` is a standalone cron (every 5 minutes) that syncs file attachments from TOGA 2.0 into TOGaDeskSupport for Elite. | worker/crons/toga2/elite/sync_togadesk_elite_attachments.php, worker/crons/toga2/elite/test_sync_togadesk_elite_attachments.php |
|
|
10
10
|
| [Forecast2 ↔ NetSuite Reconciliation & Trueup Tooling](features/forecast2-netsuite-reconciliation.md) | CLI tools to **audit** and **repair** drift between the production `Forecast` DB (core2) and NetSuite. | test/@dave/checker.php, worker2/Component/Forecast/SaleImport/SaleImport.php, test/@dave/looper.php, test/@dave/reconcile_netsuite_totals.php, test/@dave/fixer.php, test/@dave/analyze_netsuite_forecast_diff.php, test/@dave/trueup_sales.php, test/@dave/reconcile_drift_2023plus.php, test/@dave/probe_invoice_gap_2026.php, test/@dave/probe_creditmemo_gap_detail.php, test/@dave/trueup_open_orders.php, test/@dave/loop_trueup_open_orders.php, test/@dave/trueup_opportunities.php, test/@dave/probe_sales_gap_direct.php, test/@dave/probe_missing_oo_timing.php, test/@dave/probe_missing_oo_createdby.php, test/@dave/probe_drift_so_dates.php, test/@dave/probe_profit_invoices.php, test/@dave/probe_profit_gap.php, worker/crons/toga2/forecast2/common_import_sales_from_netsuite.php, worker/crons/toga2/forecast2/periodic_forecast_discrepancy_fix_open_orders.php, worker/crons/toga2/forecast2/import_open_orders.php, worker/schedules/cron.worker.infrastructure.json |
|
|
11
11
|
| [NetSuite Sales Order Sales Rep Sourcing (Staples & ODP EDI orders)](features/netsuite-sales-order-sales-rep-sourcing.md) | How the **sales rep** on a NetSuite Sales Order is determined for the two 1.0 `worker` EDI order-creation integrations (Staples cXML and Compass/ODP EDI). | worker/crons/toga2/compass/workflow/5_create_netsuite_sales_orders_from_office_depot_purchase_orders.php, worker/crons/sync/staples/sync_staples_cxml.php, test/@Mark/NetSuite/TRUE_80451_customer_salesrep_diag.php |
|
|
@@ -6,10 +6,11 @@ project: Worker
|
|
|
6
6
|
client: shared
|
|
7
7
|
type: feature
|
|
8
8
|
status: active
|
|
9
|
-
updated: 2026-08-
|
|
9
|
+
updated: 2026-08-24
|
|
10
10
|
owners: ["bala"]
|
|
11
11
|
files:
|
|
12
12
|
- worker/crons/toga2/compass/update_salesorder_status_from_odp.php
|
|
13
|
+
- worker/crons/toga2/compass/send_delivered_email.php
|
|
13
14
|
- worker/crons/toga2/compasscanada/update_salesorder_status_from_odp.php
|
|
14
15
|
- worker/crons/toga2/compasscanada/workflow/3_update_salesorder_status_from_grand_and_toy.php
|
|
15
16
|
- worker/crons/toga2/compasscanada/send_delivered_email.php
|
|
@@ -133,9 +134,26 @@ the tracking number as emailed (it retries next run).
|
|
|
133
134
|
- **Pick the template from the language of the person actually being emailed.** The manager-approval
|
|
134
135
|
reminder cron (`compass_email_reminders.php`) selected the template from the **requester's**
|
|
135
136
|
language even though the mail goes to the **manager**. Fixed 2026-08-03.
|
|
137
|
+
- **⚠ Conflict with the new 2.0 centralized tracking-status refresh.** The delivered-email cron
|
|
138
|
+
(`worker/crons/toga2/compass/send_delivered_email.php`) polls UPS itself, sends the delivered
|
|
139
|
+
template (uuid `fe961c7c-277b-4a89-be1d-3d318d8c7718`) via api2 `GET /email-templates/sendEmail`,
|
|
140
|
+
and writes `TrackingNumbers.status` with a **direct DB UPDATE** guarded by
|
|
141
|
+
`WHERE status <> 'DELIVERED'`. The new platform-wide
|
|
142
|
+
[tracking-status refresh (worker2)](../../../2.0/apps/worker2/features/tracking-status-refresh.md)
|
|
143
|
+
writes `status` via api2 PUT for **all** clients. If the refresh marks a Compass row `DELIVERED`
|
|
144
|
+
**first**, this cron's guard **skips** it and the delivered email **never sends** — a real conflict
|
|
145
|
+
to resolve before the refresh is enabled for Compass. There is **no `postPut` interceptor** on
|
|
146
|
+
tracking-numbers today; the plan (not built) is to move this email logic into a Compass `postPut`
|
|
147
|
+
interceptor and retire this 1.0 cron.
|
|
136
148
|
|
|
137
149
|
## Change history
|
|
138
150
|
|
|
151
|
+
- 2026-08-24 — Recorded the **conflict with the new 2.0 platform-wide tracking-status refresh**: that
|
|
152
|
+
refresh writes `TrackingNumbers.status` via api2 PUT for all clients, and this cron's delivered
|
|
153
|
+
UPDATE is guarded by `WHERE status <> 'DELIVERED'` — so if the refresh marks a Compass row
|
|
154
|
+
`DELIVERED` first, the delivered email never sends. Noted there is no `postPut` interceptor on
|
|
155
|
+
tracking-numbers and the plan to move this email logic into a Compass `postPut` interceptor and
|
|
156
|
+
retire this cron. Added `send_delivered_email.php` to the file list. (bala)
|
|
139
157
|
- 2026-08-03 — Compass Canada **item rows are now localized**: titles resolved from the
|
|
140
158
|
`ItemTranslations` sidecar (two-column select + PHP fallback, never a mixed-collation SQL
|
|
141
159
|
`COALESCE`) and translated `N/P` / `Qté` labels; language resolution moved onto the shared 1.0
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
| [Assortment Name Translation (AssortmentTranslations sidecar)](features/assortment-name-translation.md) | Serves Assortment (product-grouping) **names** in multiple languages by adding a per-language **sidecar** table `AssortmentTranslations`, reusing the platform's | _underscore/Model/Client/AssortmentTranslation.php, dbchanges2/Client/2026-06-26a - AssortmentTranslations.sql, dbchanges2/Core/2026-06-26a - AssortmentTranslationsRecord.sql, dbchanges2/Client/2026-06-26b - AssortmentTranslationsAcl.sql |
|
|
12
12
|
| [Asynchronous Query Execution (writes-only, via Worker)](features/async-query-execution.md) | `_Query` can run a **write** query asynchronously so a long/slow write does not hold a request-scoped DB connection open long enough to hit **"MySQL server has | _underscore/Query.php, worker2/Worker/Infrastructure/Database.php, worker2/Worker/Team/Transcripts.php |
|
|
13
13
|
| [FIELD_SQL calculated fields — the underscore-prefix + same-name-method contract](features/calculated-sql-fields.md) | A `FIELD_SQL` (calculated) field on a `_Model` is bound by a **two-part contract that `_Model` enforces by throwing at model-construction time**, not by convent | _underscore/Model.php, _underscore/Model/Client/SalesOrder.php, _underscore/Model/Client/ServiceRequest.php, _underscore/Model/Elite/SalesOrder.php, dbchanges2/Client/2026-08-11b - SalesOrderPurchaseOrdersField.sql |
|
|
14
|
-
| [Carrier Shipping Labels (UPS/FedEx) & NetSuite Item Fulfillment](features/carrier-shipping-labels.md) | Backend mechanics behind TOGa Supply's Fulfill & Ship: buying a carrier label (UPS/FedEx), persisting it, and creating the NetSuite Item Fulfillment with tracki | test/@Mark/true-80824-fedex-inflate-test.php, dbchanges2/Client_Growrk/2026-08-10e - GrowrkUpsServiceMethodCodes.sql, _underscore/Model/Client/ItemFulfillment.php, _underscore/Model/Client/TrackingNumber.php, _underscore/Model/Client/ItemFulfillments/TrackingNumber.php, _underscore/Component/Library/LabelPdf/LabelPdf.php, _underscore/Component/Library/Carriers/ShipmentRequest/ShipmentRequest.php, _underscore/Component/Library/Carriers/Ups/Ups.php, _underscore/Component/Library/Carriers/Fedex/Fedex.php, _underscore/Trait/Netsuite/ItemFulfillment.php, _underscore/Trait/Netsuite/SalesOrder.php, _underscore/Component/Library/NetSuite/NetSuite.php, _underscore/Model/Client/TrackingNumber.php, _underscore/Model/Client/ShippingMethod.php, _underscore/Model.php, _underscore/Cloud.php |
|
|
14
|
+
| [Carrier Shipping Labels (UPS/FedEx) & NetSuite Item Fulfillment](features/carrier-shipping-labels.md) | Backend mechanics behind TOGa Supply's Fulfill & Ship: buying a carrier label (UPS/FedEx), persisting it, and creating the NetSuite Item Fulfillment with tracki | test/@Mark/true-80824-fedex-inflate-test.php, dbchanges2/Client_Growrk/2026-08-10e - GrowrkUpsServiceMethodCodes.sql, _underscore/Model/Client/ItemFulfillment.php, _underscore/Model/Client/TrackingNumber.php, _underscore/Model/Client/ItemFulfillments/TrackingNumber.php, _underscore/Component/Library/LabelPdf/LabelPdf.php, _underscore/Component/Library/Carriers/ShipmentRequest/ShipmentRequest.php, _underscore/Component/Library/Carriers/Ups/Ups.php, _underscore/Component/Library/Carriers/Fedex/Fedex.php, _underscore/Component/Library/Carriers/Usps/Usps.php, _underscore/Trait/Netsuite/ItemFulfillment.php, _underscore/Trait/Netsuite/SalesOrder.php, _underscore/Component/Library/NetSuite/NetSuite.php, _underscore/Model/Client/TrackingNumber.php, _underscore/Model/Client/ShippingMethod.php, _underscore/Model.php, _underscore/Cloud.php |
|
|
15
15
|
| [Running 2.0 code from a bare CLI script (bootstrap + transactions)](features/cli-script-bootstrap.md) | A throwaway CLI script (a data check, a backfill dry-run, a render harness) that wants the real 2.0 framework — `_Model`, `_Query`, `_Database` — is **not** the | _underscore/Database.php, _underscore/Environment.php, api2/Initialize.php |
|
|
16
16
|
| [_Cloud S3 helpers (copy / get / delete / list)](features/cloud-s3-helpers.md) | `_Cloud` centralizes AWS SDK S3 usage for the 2.0 stack so the `S3Client` never leaks into workers or app code. | _underscore/Cloud.php |
|
|
17
17
|
| [_Component_*/_Model_* project-namespace registration (autoloader) & backslash-qualify traps](features/component-model-namespace-registration.md) | Every **project-local** `_Component_*` and `_Model_*` class in a 2.0 app **must declare the project namespace** at the top of the file: ```php namespace <NAMESP | _underscore/Loader.php, worker2/_.php, api2/_.php, worker2/Component/Forecast/Db/Db.php, worker2/Component/Forecast/SaleImport/SaleImport.php, worker2/Component/Api/Oneuptime/Oneuptime.php, api2/Component/Api/Netsuite/Netsuite.php, _underscore/Component/Api/Paypal/Paypal.php |
|
|
@@ -6,8 +6,8 @@ project: _Underscore
|
|
|
6
6
|
client: shared
|
|
7
7
|
type: feature
|
|
8
8
|
status: active
|
|
9
|
-
updated: 2026-08-
|
|
10
|
-
owners: [mhammontree, tcox]
|
|
9
|
+
updated: 2026-08-24
|
|
10
|
+
owners: [mhammontree, tcox, bala]
|
|
11
11
|
files:
|
|
12
12
|
- test/@Mark/true-80824-fedex-inflate-test.php
|
|
13
13
|
- dbchanges2/Client_Growrk/2026-08-10e - GrowrkUpsServiceMethodCodes.sql
|
|
@@ -18,6 +18,7 @@ files:
|
|
|
18
18
|
- _underscore/Component/Library/Carriers/ShipmentRequest/ShipmentRequest.php
|
|
19
19
|
- _underscore/Component/Library/Carriers/Ups/Ups.php
|
|
20
20
|
- _underscore/Component/Library/Carriers/Fedex/Fedex.php
|
|
21
|
+
- _underscore/Component/Library/Carriers/Usps/Usps.php
|
|
21
22
|
- _underscore/Trait/Netsuite/ItemFulfillment.php
|
|
22
23
|
- _underscore/Trait/Netsuite/SalesOrder.php
|
|
23
24
|
- _underscore/Component/Library/NetSuite/NetSuite.php
|
|
@@ -275,6 +276,26 @@ including **4 fuzz-discovered `fp:` cases whose only job is to fail if the UTF-8
|
|
|
275
276
|
class for **every** integration, but that is the framework's shared HTTP client and needs its own
|
|
276
277
|
review. **The UPS client has the same latent gap.**
|
|
277
278
|
|
|
279
|
+
## Batch tracking-status polling — `getStatuses()` (implemented 2026-08-24)
|
|
280
|
+
|
|
281
|
+
Alongside the single-shipment `getStatus()`, the carrier libs gained **batch** status lookups to
|
|
282
|
+
support the platform-wide
|
|
283
|
+
[tracking-status refresh](../../worker2/features/tracking-status-refresh.md):
|
|
284
|
+
|
|
285
|
+
- `_Component_Library_Carriers_Fedex::getStatuses(array)` (~**30 numbers/call**) and
|
|
286
|
+
`_Component_Library_Carriers_Usps::getStatuses(array)` (~**10 numbers/call**) reuse the **same
|
|
287
|
+
parsers** as `getStatus()` via extracted private helpers `statusFromFedexTrackResult()` /
|
|
288
|
+
`statusFromUspsDetail()`. Results are keyed by the tracking number the carrier echoes back.
|
|
289
|
+
- **UPS has no batch** — it stays single-call (`getStatus()`); the refresh loops it per number.
|
|
290
|
+
|
|
291
|
+
### ⚠ UPS `getStatus()` gained `$forceNoLogging` (default `false`) — do not flip the shipment callers
|
|
292
|
+
Bulk tracking polls set **`$forceNoLogging = true`** so UPS request logging is suppressed. With prod
|
|
293
|
+
`[database]` set, an unsuppressed poll logs **every** call → log bloat **and** an
|
|
294
|
+
`"Exactly 1 row expected"` throw. `$forceNoLogging` is also **propagated into `getOAuthToken()`**, so
|
|
295
|
+
an OAuth cache-miss during a no-log poll does not leave the token request logged. **Shipment and
|
|
296
|
+
address-validation callers keep the default (logging ON)** — only the bulk tracking poll passes
|
|
297
|
+
`true`.
|
|
298
|
+
|
|
278
299
|
## `FIELD_STORAGE` mechanics (reference)
|
|
279
300
|
|
|
280
301
|
> **⚠ `TrackingNumbers.labelPdfFile` has NO physical column in any `Client_*` schema in any
|
|
@@ -423,6 +444,14 @@ still discards the unsaved in-memory value.
|
|
|
423
444
|
bill) — expect a hard error rather than a silently-wrong bill in those cases.
|
|
424
445
|
|
|
425
446
|
## Change history
|
|
447
|
+
- 2026-08-24 — Added **batch tracking-status polling** to the carrier libs for the platform-wide
|
|
448
|
+
[tracking-status refresh](../../worker2/features/tracking-status-refresh.md):
|
|
449
|
+
`Fedex::getStatuses()` (~30/call) and `Usps::getStatuses()` (~10/call), reusing the single-shipment
|
|
450
|
+
parsers via extracted `statusFromFedexTrackResult()` / `statusFromUspsDetail()` helpers (UPS stays
|
|
451
|
+
single-call, no batch). Gave UPS `getStatus()` a `$forceNoLogging` param (default false, also
|
|
452
|
+
propagated into `getOAuthToken()`) so bulk polls suppress request logging — an unsuppressed poll on
|
|
453
|
+
prod `[database]` bloats logs and throws `"Exactly 1 row expected"`; shipment + address-validation
|
|
454
|
+
callers keep logging on. (bala)
|
|
426
455
|
- 2026-08-11 — Flagged that `TrackingNumbers.labelPdfFile` has **no physical column in any
|
|
427
456
|
`Client_*` schema in any environment** (only the Core `RecordFields` metadata was ever inserted),
|
|
428
457
|
so `GET /tracking-numbers` 500s once a query returns rows — storage fields hydrate lazily per row,
|
|
@@ -44,6 +44,7 @@
|
|
|
44
44
|
| [Talos Transcript Ingestion Pipeline (worker2 → AWS Bedrock KBs)](features/talos-transcript-ingestion.md) | > **DB-DRIVEN AI-MODEL ROUTING (2026-07-29).** Which knowledge base a transcript is cleaned > into is now decided by the **meeting organizer's "home" AI model** | worker2/Worker/Team/Transcripts.php, worker2/bin/sync-knowledge-bases.php, worker2/Config/production.ini, worker2/Database/TeamsTranscriptExports.sql, dbchanges2/Client_True/2026-07-27a - TranscriptAiModelRoutingColumns.sql, dbchanges2/Client_True/2026-07-27b - TranscriptAiModelRoutingData.sql, dbchanges2/Team/2026-07-27a - TranscriptVocabularyAiModelScope.sql, dbchanges2/Team/2026-07-29a - TranscriptVocabularyBackfillAllModels.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, dbchanges2/Team/2026-07-28a - TranscriptProcessingRetryAttempts.sql, dbchanges2/Team/2026-07-28b - TranscriptPromptTemplateConverseModel.sql, dbchanges2/Core/2026-07-28a - TeamsTranscriptRetryCron.sql |
|
|
45
45
|
| [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, _underscore/Model/Team/Sprint.php, dbchanges2/Core/CronJobs (SprintLockScheduled seed) |
|
|
46
46
|
| [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 |
|
|
47
|
+
| [Centralized Tracking-Status Refresh (worker2 Sync cron, all clients)](features/tracking-status-refresh.md) | A single platform-wide cron keeps `TrackingNumbers.status` current for **every provisioned client** until each shipment reaches a terminal state, by polling Fed | worker2/Worker/Sync/TrackingNumbers.php, _underscore/Component/Library/Carriers/Fedex/Fedex.php, _underscore/Component/Library/Carriers/Usps/Usps.php, _underscore/Component/Library/Carriers/Ups/Ups.php, _underscore/Model/Client/TrackingNumber.php, dbchanges2/Client/2026-07-30 - TrackingNumbers_RefreshColumns.sql, dbchanges2/Client/2026-07-30 - Acl_TrackingStatus_Grant.sql, dbchanges2/Client_Tdsynnex/2026-07-30 - Apis_TdsynnexKey.sql, dbchanges2/Core/2026-07-30 - TrackingRefresh_Cron.sql |
|
|
47
48
|
| [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, worker2/Controller/Index.php |
|
|
48
49
|
| [WJE Freshservice Sync (worker2)](features/wje-freshservice-sync.md) | WJE ("WJE IT", helpdesk `wje.freshservice.com`) is a **Freshservice**-based help-desk client whose tickets, contacts, assets, groups, categories, and canned res | worker2/Worker/Wje.php, _underscore/Component/Api/Wje/Wje.php, _underscore/Model/Wje/Ticket.php, _underscore/Model/Wje/TicketNote.php, _underscore/Model/Wje/Contact.php, _underscore/Model/Wje/Unit.php, _underscore/Model/Wje/TicketTeam.php, _underscore/Model/Wje/TicketCategory.php, _underscore/Model/Wje/AssetType.php, _underscore/Model/Wje/PredefinedReply.php, library/app/api/wje.php, worker/crons/toga2/wje/import_supporting_records.php, worker/crons/toga2/wje/sync_togasupply_wje.php, worker/crons/notifications/reports/wje/wje_common.php, library/app/systemmonitor/wje.php, dbchanges2/Client_Wje/2024-10-04 - WjeOnboarding.sql |
|
|
49
50
|
| [PHP Runtime Upgrade on Elastic Beanstalk (worker2 8.3 → 8.5 + PhpSpreadsheet 1.x → 3.x)](workflows/php-runtime-upgrade-dependency-audit.md) | The procedure used to move worker2 from **PHP 8.3 to PHP 8.5** on Elastic Beanstalk, and the dependency work that had to land first. | worker2/composer.json, worker2/composer.lock, worker2/Worker/Team/Sprint.php, worker2/Worker/Client/TowFoundation/ProcessReceipts.php, worker2/Worker/Forecast/Import.php |
|
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Centralized Tracking-Status Refresh (worker2 Sync cron, all clients)
|
|
3
|
+
framework: "2.0"
|
|
4
|
+
repo: worker2
|
|
5
|
+
project: Worker
|
|
6
|
+
client: shared
|
|
7
|
+
type: feature
|
|
8
|
+
status: active
|
|
9
|
+
updated: 2026-08-24
|
|
10
|
+
owners: ["bala"]
|
|
11
|
+
files:
|
|
12
|
+
- worker2/Worker/Sync/TrackingNumbers.php
|
|
13
|
+
- _underscore/Component/Library/Carriers/Fedex/Fedex.php
|
|
14
|
+
- _underscore/Component/Library/Carriers/Usps/Usps.php
|
|
15
|
+
- _underscore/Component/Library/Carriers/Ups/Ups.php
|
|
16
|
+
- _underscore/Model/Client/TrackingNumber.php
|
|
17
|
+
- dbchanges2/Client/2026-07-30 - TrackingNumbers_RefreshColumns.sql
|
|
18
|
+
- dbchanges2/Client/2026-07-30 - Acl_TrackingStatus_Grant.sql
|
|
19
|
+
- dbchanges2/Client_Tdsynnex/2026-07-30 - Apis_TdsynnexKey.sql
|
|
20
|
+
- dbchanges2/Core/2026-07-30 - TrackingRefresh_Cron.sql
|
|
21
|
+
related:
|
|
22
|
+
- ../architecture.md
|
|
23
|
+
- ../workflows/php-runtime-upgrade-dependency-audit.md
|
|
24
|
+
- ../workflows/running-worker2-locally.md
|
|
25
|
+
- ../../_underscore/features/carrier-shipping-labels.md
|
|
26
|
+
- ../../_underscore/features/tracking-number-bridges.md
|
|
27
|
+
- ../../api2/features/api-payload-interceptors.md
|
|
28
|
+
- ../../../1.0/apps/worker/features/compass-partial-in-transit-delivered-emails.md
|
|
29
|
+
---
|
|
30
|
+
|
|
31
|
+
## Summary
|
|
32
|
+
|
|
33
|
+
A single platform-wide cron keeps `TrackingNumbers.status` current for **every provisioned
|
|
34
|
+
client** until each shipment reaches a terminal state, by polling FedEx/USPS/UPS and writing the
|
|
35
|
+
mapped status back through **api2 PUT**. There is **no hardcoded client list** and **no new table**:
|
|
36
|
+
the working set is bounded, carrier load is decoupled from cron frequency by a per-row re-check
|
|
37
|
+
cursor, and concurrency/observability ride the existing per-client `Parameters` key/value table.
|
|
38
|
+
|
|
39
|
+
The abstract worker class is `_Worker_Sync_TrackingNumbers` in
|
|
40
|
+
`worker2/Worker/Sync/TrackingNumbers.php`. The Core cron
|
|
41
|
+
(`Sync/TrackingNumbers/Refresh`, `*/10 * * * *`, `maxExecutionTime 120`) fires a **dispatcher**;
|
|
42
|
+
the dispatcher fans out one child task per client.
|
|
43
|
+
|
|
44
|
+
> **⚠ NOT LIVE — go-live is gated on a platform bug.** The write path is api2 PUT, and
|
|
45
|
+
> **worker2 → api2 calls hard-crash on PHP 8.5** (see the go-live gate below). Until that is
|
|
46
|
+
> resolved on the prod worker box, this feature must not run for real (writes will crash).
|
|
47
|
+
|
|
48
|
+
## How it works
|
|
49
|
+
|
|
50
|
+
### Dispatcher → per-client child (no hardcoded client list)
|
|
51
|
+
- `Refresh(?clientIdentifier = null, isDryRun = true)` is the dispatcher. With **no**
|
|
52
|
+
`clientIdentifier` it **enumerates provisioned clients** (`Clients` JOIN `Databases` JOIN
|
|
53
|
+
`DatabaseHosts` JOIN `Environments` on slug) and `_Worker::runTask`s one
|
|
54
|
+
`Sync/TrackingNumbers/RefreshClient` child per client. With a `clientIdentifier` it runs that one
|
|
55
|
+
client inline.
|
|
56
|
+
- Each `RefreshClient` child: registers the client DB, claims a per-client run (lease, below),
|
|
57
|
+
resolves a write key (capability-based, below), fetches **one bounded page** of due rows, polls
|
|
58
|
+
carriers, writes changed status via api2 PUT, stamps each row's next-check cursor, and
|
|
59
|
+
**self-continues** to the next page if more remain.
|
|
60
|
+
|
|
61
|
+
### Re-check cadence is a per-row cursor (carrier load ≠ cron frequency)
|
|
62
|
+
- `TrackingNumbers.c_dtTrackingCheckNext` is the cursor. A row is selected **only when it is NULL
|
|
63
|
+
or `<= NOW()`**.
|
|
64
|
+
- On a good check the cursor is stamped forward: **+1h** when delivery is imminent
|
|
65
|
+
(`OUT_FOR_DELIVERY` / `AWAITING_PICKUP` / `PICKUP`), else **+6h**, each with a few minutes of
|
|
66
|
+
`RAND()` **jitter** to avoid a carrier stampede.
|
|
67
|
+
- **Terminal statuses (`DELIVERED` / `RETURNED_TO_SENDER`) set the cursor NULL** → never
|
|
68
|
+
re-checked.
|
|
69
|
+
- A **45-day `dtCreated` age cutoff** bounds the working set. Steady-state carrier load is therefore
|
|
70
|
+
`working set / cadence`, **independent of how often the cron fires**.
|
|
71
|
+
|
|
72
|
+
### Batches bounded to finish under the worker request timeout
|
|
73
|
+
- `PAGE_CAP = 50`, `SOFT_TIME_BUDGET_SECONDS = 40`. The budget is checked **between carrier calls
|
|
74
|
+
and between the two batch prefetches**.
|
|
75
|
+
- **⚠ This was a real bug:** an earlier **200s** budget let jobs get **hard-killed at ~75s** (the
|
|
76
|
+
worker's hard request timeout). Keep the soft budget comfortably under ~75s.
|
|
77
|
+
|
|
78
|
+
### Carrier polling
|
|
79
|
+
- **FedEx** and **USPS** are polled with **one batch call each** (`getStatuses`), keyed by the
|
|
80
|
+
tracking number the carrier echoes back. **UPS is single-call only.** Anything a batch call misses
|
|
81
|
+
falls back to a per-number single lookup. (Carrier-library batch support lives in `_underscore` —
|
|
82
|
+
see [carrier-shipping-labels](../../_underscore/features/carrier-shipping-labels.md).)
|
|
83
|
+
- Carrier is resolved from the stored `ShippingCarrier` first (normalized), else from the
|
|
84
|
+
tracking-number pattern. **Unsupported carrier** (DHL / freight) is **parked**; **undetectable**
|
|
85
|
+
carrier is **deferred 3 days**; **malformed** number is **parked**.
|
|
86
|
+
|
|
87
|
+
### Status mapping (coarse carrier status → our 12-status vocabulary)
|
|
88
|
+
- The coarse carrier status is refined by the latest scan-event text via a `match(true)` keyword
|
|
89
|
+
scan.
|
|
90
|
+
- **⚠ Two guards that must not be removed:**
|
|
91
|
+
- Scan text can **never** mark `DELIVERED` unless the carrier's coarse status already agrees —
|
|
92
|
+
stops a misleading scan note from producing a false terminal.
|
|
93
|
+
- `RETURNED_TO_SENDER` only comes from the specific phrase **`return to sender`**.
|
|
94
|
+
|
|
95
|
+
### Concurrency + observability WITHOUT a new table (Parameters convention)
|
|
96
|
+
State lives in each client's existing `Parameters` (key/value) table, mirroring the established
|
|
97
|
+
`NETSUITE_EXECUTION_MODE` / `NETSUITE_LAST_SYNC_DATETIME_*` convention:
|
|
98
|
+
- `TRACKING_REFRESH_EXECUTION_MODE` — a **lease-based claim** holding `RUNNING:<unixStartTime>:<runId>`.
|
|
99
|
+
A claim wins only if idle **or** the lease (`RUN_LEASE_SECONDS = 120`) has expired. **Release is
|
|
100
|
+
owner-scoped** — only the `runId` that owns the lease can free it, so a stale prior run cannot
|
|
101
|
+
clear a newer run's claim. Single row + unique key → deadlock-free.
|
|
102
|
+
- `TRACKING_REFRESH_LAST_SYNC_DATETIME` — the "caught up through" watermark, stamped **only when the
|
|
103
|
+
client's due-set fully drains**.
|
|
104
|
+
- The run body is wrapped in **`try/finally`** so the lease is always released even on a throw.
|
|
105
|
+
|
|
106
|
+
### Write key resolution is CAPABILITY-BASED, not by name
|
|
107
|
+
`findApiKeyAllowedToWriteStatus()` selects the active `Apis` key **whose role has
|
|
108
|
+
`AclRecordPermissions.allowUpdate` on record 62 (tracking-numbers) AND
|
|
109
|
+
`AclFieldPermissions.isWritable` on field 357 (status)**. Keying off the ACL grant itself means the
|
|
110
|
+
key selection can never drift from the permission, and no per-client key renaming is needed. If **no
|
|
111
|
+
capable key exists**, the client is **skipped** and a **fail-loud alert email** is sent **at most
|
|
112
|
+
once per day** (throttled via a `Parameters` key).
|
|
113
|
+
|
|
114
|
+
## Data model — the internal `c_` bookkeeping columns
|
|
115
|
+
|
|
116
|
+
`dbchanges2/Client/2026-07-30 - TrackingNumbers_RefreshColumns.sql` adds five columns to
|
|
117
|
+
`TrackingNumbers` plus a composite sweep index: `c_dtTrackingCheckNext`, `c_dtTrackingChecked`,
|
|
118
|
+
`c_trackingCheckFailures`, `c_dtTrackingParked`, `c_trackingParkReason`. They are declared as model
|
|
119
|
+
fields on `_Model_Client_TrackingNumber` (`_underscore/Model/Client/TrackingNumber.php`).
|
|
120
|
+
|
|
121
|
+
**The full custom-field convention (learned here).** A first-class `c_` custom field needs **four**
|
|
122
|
+
things, not one: (1) the `ALTER TABLE`, (2) a `CustomRecordFields` registration on the record
|
|
123
|
+
(here record 62), (3) an `AclCustomFieldPermissions` grant to the roles that use it (Base + API
|
|
124
|
+
here), and (4) a **model property declaration**. **Internal-only variant used here:** these five are
|
|
125
|
+
registered with **no `Apis_CustomRecordFields` link and no `*CustomRecordFieldSettings`**, so they
|
|
126
|
+
are usable internally but **never surface in the API payload or the UI** — the right pattern for
|
|
127
|
+
private bookkeeping columns.
|
|
128
|
+
|
|
129
|
+
## Migrations
|
|
130
|
+
|
|
131
|
+
- `dbchanges2/Client/2026-07-30 - TrackingNumbers_RefreshColumns.sql` — the five `c_` columns +
|
|
132
|
+
sweep index + `CustomRecordFields` (record 62) + `AclCustomFieldPermissions` (Base + API roles),
|
|
133
|
+
internal-only.
|
|
134
|
+
- `dbchanges2/Client/2026-07-30 - Acl_TrackingStatus_Grant.sql` — gap-fill: grants the API role write
|
|
135
|
+
on tracking-numbers.status (record 62 / field 357) wherever missing, **plus two UPDATEs that HEAL
|
|
136
|
+
clients whose grant rows exist but with the flags off** (`allowUpdate = 0` / `isWritable = 0`) — the
|
|
137
|
+
`NOT EXISTS` inserts skip those rows, so the heal is required (this was the **GroWrk** case).
|
|
138
|
+
- `dbchanges2/Client_Tdsynnex/2026-07-30 - Apis_TdsynnexKey.sql` — creates the integration key for
|
|
139
|
+
Tdsynnex, the only provisioned client with no key. It lives in the **per-client** folder (not a
|
|
140
|
+
`Client/` fan-out) because a `Client/` file cannot reference a specific tenant DB — the dbchanges2
|
|
141
|
+
cluster-isolation rule.
|
|
142
|
+
- `dbchanges2/Core/2026-07-30 - TrackingRefresh_Cron.sql` — registers the dispatcher cron.
|
|
143
|
+
|
|
144
|
+
## Decisions
|
|
145
|
+
|
|
146
|
+
- **Write path = api2 PUT, not direct-DB (deliberate).** Direct-DB was **rejected** so that a future
|
|
147
|
+
**`postPut` interceptor** on tracking-numbers can fire the in-transit / delivered emails on a
|
|
148
|
+
status change. There is no such interceptor today (see the interceptor doc). This is a departure
|
|
149
|
+
from other worker2 crons (e.g. NYCHH asset-tag backfill) that write direct-DB to dodge the PHP-8.5
|
|
150
|
+
crash — here the interceptor requirement wins, which is exactly why the crash below is a hard gate.
|
|
151
|
+
|
|
152
|
+
## Gotchas / known issues
|
|
153
|
+
|
|
154
|
+
### ⚠ GO-LIVE GATE — worker2 → api2 hard-crashes on PHP 8.5
|
|
155
|
+
Writes go through api2 PUT, and a worker2 → api2 call **hard-crashes the request on PHP 8.5
|
|
156
|
+
(OpenSSL 3.5.5): a process-level 500 with no catchable trace, and the shutdown handler never
|
|
157
|
+
fires.** The prod worker box was bumped to PHP 8.5 on **2026-07-29** and this path was **never
|
|
158
|
+
re-verified there**. **api2 itself is fine** — on local PHP 8.1, auth + `PUT /tracking-numbers`
|
|
159
|
+
succeeded and api2 understands the new `c_` columns (GET + PUT returned `isSuccess` with no
|
|
160
|
+
unknown-column error). So the fix is **platform-level (worker TLS/curl on 8.5)**, not code. Until
|
|
161
|
+
resolved, this feature cannot run for real. See the causal
|
|
162
|
+
[PHP 8.3 → 8.5 runtime-upgrade workflow](../workflows/php-runtime-upgrade-dependency-audit.md) and
|
|
163
|
+
the same symptom in [NYCHH asset-tag backfill](nychh-asset-tag-backfill.md).
|
|
164
|
+
|
|
165
|
+
### ⚠ Confirm the worker box's `ENVIRONMENT` before trusting enumeration on beta
|
|
166
|
+
`resolveEnvironment()` — `(substr(_Environment::$name, 0, 4) === 'dev-') ? 'dev' : name` — is
|
|
167
|
+
**byte-identical** to api2's `V2.php` / `Index.php`. On beta the worker box's `ENVIRONMENT` resolves
|
|
168
|
+
to slug **`beta`** or **`sandbox-dev`**, **not `dev`**, and those enumerate a **different/empty**
|
|
169
|
+
client set than `dev`. Confirm the box's actual `ENVIRONMENT` value before trusting the dispatcher's
|
|
170
|
+
client enumeration on beta.
|
|
171
|
+
|
|
172
|
+
### ⚠ Conflict with the 1.0 Compass delivered-email cron
|
|
173
|
+
The in-transit / delivered emails **today** come from a separate **1.0** Compass worker cron
|
|
174
|
+
(`worker/crons/toga2/compass/send_delivered_email.php`) that polls UPS itself, sends via api2
|
|
175
|
+
`GET /email-templates/sendEmail`, and writes `TrackingNumbers.status` via a **direct DB UPDATE**
|
|
176
|
+
guarded by `WHERE status <> 'DELIVERED'`. If this refresh marks a Compass row `DELIVERED` **first**,
|
|
177
|
+
that cron's guard skips it and the delivered email **never sends**. This is a real conflict to
|
|
178
|
+
resolve before enabling the refresh for Compass — see the
|
|
179
|
+
[Compass in-transit/delivered emails doc](../../../1.0/apps/worker/features/compass-partial-in-transit-delivered-emails.md).
|
|
180
|
+
Plan (not built): move the email logic into a Compass `postPut` interceptor and retire the 1.0 cron.
|
|
181
|
+
|
|
182
|
+
## Verification status
|
|
183
|
+
|
|
184
|
+
- **Beta (dev-sandbox):** all **33** provisioned clients resolve a capable write key via the
|
|
185
|
+
capability query (GroWrk healed, Tdsynnex provisioned). Dry-run (`isDryRun = true`) is fully
|
|
186
|
+
read-only (no writes, no api2 PUT, no emails) and is the safe smoke test.
|
|
187
|
+
- **Local:** worker2's worker endpoint is the **ROOT** (`POST http://worker2/`), **not** `/worker`
|
|
188
|
+
— see [running worker2 locally](../workflows/running-worker2-locally.md).
|
|
189
|
+
- **Prod (before go-live):** apply the three client/core migrations, re-run the 33/33 capable-key
|
|
190
|
+
check, and **resolve the PHP-8.5 worker → api2 crash** (the gate).
|
|
191
|
+
|
|
192
|
+
## Change history
|
|
193
|
+
- 2026-08-24 — Built the centralized tracking-status refresh: dispatcher + per-client child
|
|
194
|
+
(`_Worker_Sync_TrackingNumbers`), per-row `c_dtTrackingCheckNext` re-check cursor (+1h imminent /
|
|
195
|
+
+6h else, jitter, terminal → NULL, 45-day cutoff), `PAGE_CAP=50`/`SOFT_TIME_BUDGET_SECONDS=40`
|
|
196
|
+
bounding (fixing a 200s budget that got jobs hard-killed at ~75s), FedEx/USPS batch + UPS
|
|
197
|
+
single-call polling, coarse→12-status mapping with the DELIVERED-agreement and literal
|
|
198
|
+
`return to sender` guards, and a `Parameters`-table lease (owner-scoped release) + watermark
|
|
199
|
+
reusing the NetSuite convention (no new table). Write key is capability-based
|
|
200
|
+
(`AclRecordPermissions.allowUpdate` rec 62 + `AclFieldPermissions.isWritable` field 357), skip +
|
|
201
|
+
once-daily alert if none. Added the five internal-only `c_` bookkeeping columns (4-part custom-field
|
|
202
|
+
registration, no API/UI surface) and the cron/ACL/Tdsynnex-key migrations. **Decided write path =
|
|
203
|
+
api2 PUT (not direct-DB)** to enable a future `postPut` email interceptor. Recorded the **PHP-8.5
|
|
204
|
+
worker → api2 hard-crash go-live gate**, the beta `ENVIRONMENT`-slug enumeration caveat, and the
|
|
205
|
+
conflict with the 1.0 Compass delivered-email cron's `WHERE status <> 'DELIVERED'` guard.
|
|
206
|
+
Verified 33/33 capable keys on beta; NOT yet live. (bala)
|
|
207
|
+
</content>
|
|
208
|
+
</invoke>
|
|
@@ -6,8 +6,8 @@ project: Worker
|
|
|
6
6
|
client: shared
|
|
7
7
|
type: workflow
|
|
8
8
|
status: active
|
|
9
|
-
updated: 2026-
|
|
10
|
-
owners: [jcardinal]
|
|
9
|
+
updated: 2026-08-24
|
|
10
|
+
owners: [jcardinal, bala]
|
|
11
11
|
files:
|
|
12
12
|
- worker2/composer.json
|
|
13
13
|
- worker2/composer.lock
|
|
@@ -16,6 +16,8 @@ files:
|
|
|
16
16
|
- worker2/Worker/Forecast/Import.php
|
|
17
17
|
related:
|
|
18
18
|
- ../architecture.md
|
|
19
|
+
- ../features/tracking-status-refresh.md
|
|
20
|
+
- ../features/nychh-asset-tag-backfill.md
|
|
19
21
|
- ../features/team-sprint-management.md
|
|
20
22
|
- ../features/platform-cache-cleanup.md
|
|
21
23
|
- ../../../1.0/apps/tools/workflows/deploy-to-elastic-beanstalk-al2023.md
|
|
@@ -160,6 +162,24 @@ that production will hit next.
|
|
|
160
162
|
Post-upgrade verification: `Platform/Cache/Truncate` confirmed working on PHP 8.5 (see
|
|
161
163
|
[Platform Cache Cleanup](../features/platform-cache-cleanup.md)).
|
|
162
164
|
|
|
165
|
+
## ⚠ PHP 8.5 regression discovered after the bump — worker2 → api2 hard-crash
|
|
166
|
+
|
|
167
|
+
**Not caught during this upgrade; surfaced later (2026-08).** After the prod worker box moved to
|
|
168
|
+
PHP 8.5 (OpenSSL 3.5.5), an outbound **worker2 → api2 HTTPS call hard-crashes the request: a
|
|
169
|
+
process-level 500 with no catchable trace, and the shutdown handler never fires.** It is a
|
|
170
|
+
**platform-level** fault (worker TLS/curl on 8.5), **not** application code — **api2 itself is fine**
|
|
171
|
+
(verified on local PHP 8.1: auth + `PUT /tracking-numbers` succeeded). Because the prod worker was
|
|
172
|
+
bumped **2026-07-29** and worker → api2 was **never re-verified there**, any worker2 feature that
|
|
173
|
+
writes through api2 is currently blocked on this.
|
|
174
|
+
|
|
175
|
+
Practical consequences:
|
|
176
|
+
- Existing worker2 crons dodge it by writing **direct-DB instead of api2** (e.g.
|
|
177
|
+
[NYCHH asset-tag backfill](../features/nychh-asset-tag-backfill.md)).
|
|
178
|
+
- The [tracking-status refresh](../features/tracking-status-refresh.md) **cannot** — it deliberately
|
|
179
|
+
writes via api2 PUT for a future email interceptor, so this crash is its **go-live gate**.
|
|
180
|
+
- **After any worker2 runtime bump, re-verify the worker → api2 round-trip on the actual target box**
|
|
181
|
+
— a green local run on an older PHP proves nothing about the deployed tier.
|
|
182
|
+
|
|
163
183
|
## Open risk — pre-existing composer advisories (own ticket)
|
|
164
184
|
|
|
165
185
|
**Unrelated to this work and not introduced by it.** `composer audit` reports **16
|
|
@@ -187,6 +207,13 @@ of the 8.5 branch to keep it a single-concern change — **needs its own ticket.
|
|
|
187
207
|
- **Code first (dual-runtime), runtime second (isolated).**
|
|
188
208
|
|
|
189
209
|
## Change history
|
|
210
|
+
- 2026-08-24 — Recorded a **PHP 8.5 regression that surfaced after this bump**: worker2 → api2
|
|
211
|
+
outbound HTTPS **hard-crashes** (process-level 500, no trace, shutdown handler never fires) on
|
|
212
|
+
8.5 / OpenSSL 3.5.5. Platform-level (worker TLS/curl), **not** code — api2 verified fine on local
|
|
213
|
+
8.1. Prod worker went 8.5 on 2026-07-29 and this path was never re-verified there, so it gates
|
|
214
|
+
every worker2 feature that writes through api2 (the reason the tracking-status refresh writes via
|
|
215
|
+
api2 PUT is blocked; other crons use direct-DB to avoid it). Added the rule to re-verify the
|
|
216
|
+
worker → api2 round-trip on the target box after any runtime bump. (bala)
|
|
190
217
|
- 2026-07-29 — Initial capture. worker2 upgraded phpspreadsheet 1.30.2 → 3.10.7 (`^3.10`) and
|
|
191
218
|
root require `>=8.2` → `>=8.2 <8.6`; 856 `*ByColumnAndRow` call sites migrated to the array
|
|
192
219
|
coordinate form across `Team/Sprint.php`, `Client/TowFoundation/ProcessReceipts.php`, and
|
package/knowledge/INDEX.md
CHANGED
|
@@ -19,7 +19,7 @@ _Auto-generated by `knowledge.js index`. Do not hand-edit._
|
|
|
19
19
|
## 2.0 framework
|
|
20
20
|
|
|
21
21
|
- **_underscore** (_Underscore) _(framework core)_ — 61 doc(s) → [2.0/apps/_underscore/INDEX.md](2.0/apps/_underscore/INDEX.md)
|
|
22
|
-
- **worker2** (Worker) —
|
|
22
|
+
- **worker2** (Worker) — 55 doc(s) → [2.0/apps/worker2/INDEX.md](2.0/apps/worker2/INDEX.md)
|
|
23
23
|
- **api2** (API) — 25 doc(s) → [2.0/apps/api2/INDEX.md](2.0/apps/api2/INDEX.md)
|
|
24
24
|
- **dbchanges2** (Database Changes) _(framework core)_ — 8 doc(s) → [2.0/apps/dbchanges2/INDEX.md](2.0/apps/dbchanges2/INDEX.md)
|
|
25
25
|
- **toga2-supply** (TOGa Supply) — 7 doc(s) → [2.0/apps/toga2-supply/INDEX.md](2.0/apps/toga2-supply/INDEX.md)
|
package/package.json
CHANGED