toga-ai 1.0.40 → 1.0.42

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.
@@ -5,3 +5,4 @@
5
5
  | [_underscore Framework Architecture](architecture.md) | `_underscore` is the shared PHP backend framework for **all 2.0 applications**. | _underscore/_underscore.php, _underscore/Loader.php, _underscore/Framework.php, _underscore/Model.php, _underscore/Database.php, _underscore/Query.php, _underscore/Route.php, _underscore/Component.php |
6
6
  | [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 | _underscore/Model/Client/ItemFulfillment.php, _underscore/Component/Library/Carriers/Ups/Ups.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 |
7
7
  | [Recursive Item Fulfillments (upstream mirroring)](features/recursive-item-fulfillments.md) | In a multi-tier supply chain a sales order (SO) spawns a purchase order (PO) that becomes another SO downstream, and so on. | _underscore/Model/Client/ItemFulfillment.php, _underscore/Model/Client/ItemFulfillmentItem.php, _underscore/Model/Client/ItemFulfillmentItemUnit.php, _underscore/Model/Client/ItemFulfillmentPackage.php, _underscore/Model/Compass/AdvanceShippingNotice.php, dbchanges2/Core/2026-02-13 - 75601 - RecursiveItemFulfillmentCreation.sql, dbchanges2/Core/2026-06-04 - RecursiveItemFulfillmentPut.sql |
8
+ | [Tracking-Number Bridge Migration (ASN / Item Fulfillment / Item Receipt)](features/tracking-number-bridges.md) | Shipment tracking numbers used to live as **scalar FK columns** (`trackingNumberId`, `returnTrackingNumberId`) directly on the lowest-level "unit"/"item" tables | _underscore/Model/Client/AdvanceShippingNoticeItemUnit.php, _underscore/Model/Client/AdvanceShippingNoticeItemUnits/TrackingNumber.php, _underscore/Model/Client/ItemFulfillmentItemUnits/TrackingNumber.php, _underscore/Model/Client/ItemFulfillment.php, _underscore/Model/Prudential/AdvanceShippingNotice.php, _underscore/Model/Compass/AdvanceShippingNotice.php, _underscore/Trait/Netsuite/ItemFulfillment.php, api2/Component/Api/Cxml/Cxml.php, dbchanges2/Client/2026-06-10 - TrackingNumberBridges.sql, dbchanges2/Core/2026-06-10 - TrackingNumberBridges.sql |
@@ -0,0 +1,123 @@
1
+ ---
2
+ title: Tracking-Number Bridge Migration (ASN / Item Fulfillment / Item Receipt)
3
+ framework: "2.0"
4
+ repo: _underscore
5
+ project: _Underscore
6
+ client: shared
7
+ type: feature
8
+ status: active
9
+ updated: 2026-06-10
10
+ owners: ["jcardinal"]
11
+ files:
12
+ - _underscore/Model/Client/AdvanceShippingNoticeItemUnit.php
13
+ - _underscore/Model/Client/AdvanceShippingNoticeItemUnits/TrackingNumber.php
14
+ - _underscore/Model/Client/ItemFulfillmentItemUnits/TrackingNumber.php
15
+ - _underscore/Model/Client/ItemFulfillment.php
16
+ - _underscore/Model/Prudential/AdvanceShippingNotice.php
17
+ - _underscore/Model/Compass/AdvanceShippingNotice.php
18
+ - _underscore/Trait/Netsuite/ItemFulfillment.php
19
+ - api2/Component/Api/Cxml/Cxml.php
20
+ - dbchanges2/Client/2026-06-10 - TrackingNumberBridges.sql
21
+ - dbchanges2/Core/2026-06-10 - TrackingNumberBridges.sql
22
+ related:
23
+ - recursive-item-fulfillments.md
24
+ ---
25
+
26
+ ## Summary
27
+
28
+ Shipment tracking numbers used to live as **scalar FK columns** (`trackingNumberId`,
29
+ `returnTrackingNumberId`) directly on the lowest-level "unit"/"item" tables across the ASN,
30
+ Item Fulfillment (IF), and Item Receipt (IR) hierarchies. This migration moves all tracking
31
+ into dedicated **`*_TrackingNumbers` bridge tables** (one consistent pattern per hierarchy
32
+ level), so a record can carry multiple tracking numbers and the model is uniform. It also:
33
+
34
+ - renames the ASN **`AdvanceShippingNoticeUnits`** table → **`AdvanceShippingNoticeItemUnits`**
35
+ (and its bridge + FK column `advanceShippingNoticeUnitId` → `advanceShippingNoticeItemUnitId`),
36
+ - consolidates **`ItemFulfillmentPackages`** (header-level tracking) into
37
+ **`ItemFulfillments_TrackingNumbers`** and **drops the packages table**,
38
+ - makes tracking a model concept submitted/read **as an inherent child of the parent record**
39
+ (not a scalar field).
40
+
41
+ ## The bridge pattern
42
+
43
+ Each hierarchy level gets a bridge keyed to its parent plus `trackingNumberId`
44
+ (+ `returnTrackingNumberId` on ASN and IF bridges; IR has no returns):
45
+
46
+ | Level | Bridge table | Core Record id | Route (inherent-child key) |
47
+ |---|---|---|---|
48
+ | ASN | AdvanceShippingNotices_TrackingNumbers | 218 | advance-shipping-notice-tracking-numbers |
49
+ | ASN item | AdvanceShippingNoticeItems_TrackingNumbers | 217 | advance-shipping-notice-item-tracking-numbers |
50
+ | ASN unit | AdvanceShippingNoticeItemUnits_TrackingNumbers | 216 (renamed) | advance-shipping-notice-item-unit-tracking-numbers |
51
+ | IF | ItemFulfillments_TrackingNumbers | 317 (new) | item-fulfillment-tracking-numbers |
52
+ | IF item | ItemFulfillmentItems_TrackingNumbers | 318 (new) | item-fulfillment-item-tracking-numbers |
53
+ | IF item unit | ItemFulfillmentItemUnits_TrackingNumbers | 319 (new) | item-fulfillment-item-unit-tracking-numbers |
54
+ | IR | ItemReceipts_TrackingNumbers | 320 (new) | item-receipt-tracking-numbers |
55
+ | IR item | ItemReceiptItems_TrackingNumbers | 321 (new) | item-receipt-item-tracking-numbers |
56
+ | IR item unit | ItemReceiptItemUnits_TrackingNumbers | 322 (new) | item-receipt-item-unit-tracking-numbers |
57
+
58
+ All bridges are registered as **`InherentRecordChildren`** of their parent record, so the
59
+ engine derives the payload key from the bridge route (kebab→camel) — e.g. an ASN unit's
60
+ tracking is submitted as `advanceShippingNoticeItemUnitTrackingNumbers: [{ trackingNumber: {...},
61
+ returnTrackingNumber: {...} }]`, and an IFIU's as `itemFulfillmentItemUnitTrackingNumbers`.
62
+
63
+ ## Key files / entry points
64
+
65
+ - **Bridge models** — `Model/Client/<Parent>/TrackingNumber.php` (e.g. `ItemFulfillmentItemUnits/TrackingNumber.php`).
66
+ The renamed ASN unit model is `Model/Client/AdvanceShippingNoticeItemUnit.php`; the old
67
+ `AdvanceShippingNoticeUnit.php` + `AdvanceShippingNoticeUnits/TrackingNumber.php` were deleted.
68
+ - **Recursive feature** — `Model/Client/ItemFulfillment.php`: `reconcileUpstreamUnits` and
69
+ `reconcileUpstreamPackages` now read/write the bridges (POST `/item-fulfillment-item-unit-tracking-numbers`
70
+ and `/item-fulfillment-tracking-numbers`); unit deletes cascade to the inherent-child bridge rows.
71
+ The FedEx/shipping-label methods read header tracking from `ItemFulfillments_TrackingNumbers`.
72
+ - **Prudential interceptor** — `Model/Prudential/AdvanceShippingNotice.php::prePost` (see the
73
+ Prudential client doc).
74
+ - **cXML inbound** — `api2/Component/Api/Cxml/Cxml.php` emits the new key + bridge-child tracking.
75
+ - **DB** — `dbchanges2/Core/...` (records/fields/ACL/inherent-children, record-41 teardown),
76
+ `dbchanges2/Client/...` (renames, bridges, migrations, ACL) split into an additions file and a
77
+ companion `..._DROPS.sql` for the column/table drops to run after confirmation.
78
+
79
+ ## Data model
80
+
81
+ - New bridges: `id, uuid, <parentFk>, trackingNumberId (NULL), [returnTrackingNumberId (NULL)]`,
82
+ `InnoDB / utf8mb4_unicode_ci`, `UNIQUE(uuid)`, `UNIQUE(<parentFk>, trackingNumberId)`, FKs to
83
+ the parent + `TrackingNumbers`.
84
+ - Dropped (in the DROPS file): `AdvanceShippingNoticeItemUnits.trackingNumberId/returnTrackingNumberId`,
85
+ `ItemFulfillmentItemUnits.trackingNumberId`, `ItemReceiptItems.trackingNumberId`,
86
+ `ItemReceiptItemUnits.trackingNumberId`, and the entire `ItemFulfillmentPackages` table.
87
+ - Core: Records 61 (unit) + 216 (unit bridge) renamed; new Records 317–322 + RecordFields 2171–2200;
88
+ `returnTrackingNumberId` RecordFields added to 216/217/218; dropped RecordFields 334/652/1431/1576/1577;
89
+ record 41 (item-fulfillment-packages) fully removed. Label settings (`DefaultRecordFieldSettings`)
90
+ and `Records.labelRecordFieldId` are **repointed** from dropped fields to the new bridge fields.
91
+
92
+ ## Client variations
93
+
94
+ - **Compass (Client_Compass / compass-usa):** ASN→IF auto-creation (`Model/Compass/AdvanceShippingNotice.php`)
95
+ posts unit tracking + header tracking via the bridge routes; `Model/Compass/SalesOrder.php::_trackingNumbers`
96
+ reads through the bridges. The `item-fulfillments-for-sales-order-items` TableView was rebuilt
97
+ (see the compass-usa client doc).
98
+ - **Prudential (Client_Prudential):** Dell posts the legacy `advanceShippingNoticeUnits` key with flat
99
+ tracking — a PRE/POST interceptor rewrites it (see the prudential client doc).
100
+ - **NetSuite-integrated clients:** `Trait/Netsuite/ItemFulfillment.php` now reads
101
+ `itemFulfillmentTrackingNumbers` and posts `/item-fulfillment-tracking-numbers`. The 1.0 library
102
+ `App_Api_Toga2` (`library/app/api/toga2.php`) got the same change for the worker tier.
103
+
104
+ ## Gotchas / known issues
105
+
106
+ - **Tracking is now an inherent child, not a scalar.** Any producer that sent a flat `trackingNumber`
107
+ on a unit must nest it under the unit's `*TrackingNumbers` child array, or it is silently dropped.
108
+ - **Migration FK pitfalls (all handled in the SQL):** deleting `RecordFields` hits `DefaultRecordFieldSettings`
109
+ FKs (repoint first); deleting `AclRecordPermissions` hits the `AclLogicGroups`→`AclLogicGroupExpressions`
110
+ chain (record-41 teardown wraps it in `SET FOREIGN_KEY_CHECKS=0`); deleting `TableViewFields` hits
111
+ `TableViews.sortPrimaryTableViewFieldId` (null it first, re-point after); self-referencing `INSERT … NOT EXISTS`
112
+ on the target table errors 1093 (use a derived table); and orphaned/dangling `trackingNumberId` values
113
+ fail the bridge FK 1452 (join `TrackingNumbers` so orphans become NULL / are skipped).
114
+ - **UUIDs in SQL:** generated with `RANDOM_BYTES()` (MySQL 8) — never MySQL `UUID()` (per 2.0 standard).
115
+ - **Dropped FK constraint names vary per client** — drop them dynamically via `INFORMATION_SCHEMA` + PREPARE.
116
+ - **Deploy coupling:** renames are NOT backward compatible — apply the DB migration and deploy
117
+ `_underscore` + `api2` + `worker` + `library` together. Run the additions/migrations DB file first;
118
+ run the companion `..._DROPS.sql` (and the Core deletes) only after the bridges + app are confirmed.
119
+
120
+ ## Related docs
121
+ - Recursive Item Fulfillment feature (the upstream-sync engine this migration reworked).
122
+ - compass-usa: ASN→Item Fulfillment, and the Item Fulfillment tracking TableView.
123
+ - prudential: Dell ASN units interceptor.
@@ -6,3 +6,4 @@
6
6
  | [ClickUp Project & Opportunity Multi-List Routing](features/clickup-project-routing.md) | Routes ClickUp tasks into the correct **secondary multi-list memberships** based on their custom-field values, via the `clickup` webhook. | worker2/Worker/Clickup/Project.php, worker2/Worker/Clickup.php |
7
7
  | [Creating Worker Actions](features/creating-worker-actions.md) | How to add a new callable Worker action — a PHP class whose `public static` methods are invoked as background jobs (via webhook, cron, or `_Worker::runTask()`). | worker2/Worker/, worker2/Controller/Index.php, _underscore/Worker.php |
8
8
  | [Elite Freshservice Sync (worker2)](features/elite-freshservice-sync.md) | `_Worker_Elite` processes Freshservice webhook events and syncs them into TOGA 2. | worker2/Worker/Elite.php, worker2/Config/dev-kmaramreddy-laptop.ini |
9
+ | [Monitoring Framework (Orchestrator + Child Monitors)](features/monitoring-framework.md) | A unified, DB-driven monitoring framework for business-critical data flows (Compass POs, Prudential asset imports, AIG closed claims, …). | worker2/Worker/Monitor.php, worker2/Worker/Monitors/, worker2/Worker/Notification/Email.php, dbchanges2/Core/2026-05-21 - Monitors.sql |
@@ -0,0 +1,183 @@
1
+ ---
2
+ title: Monitoring Framework (Orchestrator + Child Monitors)
3
+ framework: "2.0"
4
+ repo: worker2
5
+ project: Worker
6
+ client: shared
7
+ type: feature
8
+ status: active
9
+ updated: 2026-06-10
10
+ owners: [mhammontree]
11
+ files:
12
+ - worker2/Worker/Monitor.php
13
+ - worker2/Worker/Monitors/
14
+ - worker2/Worker/Notification/Email.php
15
+ - dbchanges2/Core/2026-05-21 - Monitors.sql
16
+ related:
17
+ - ../architecture.md
18
+ - ./creating-worker-actions.md
19
+ - ../../dbchanges2/architecture.md
20
+ ---
21
+
22
+ ## Summary
23
+
24
+ A unified, DB-driven monitoring framework for business-critical data flows (Compass POs,
25
+ Prudential asset imports, AIG closed claims, …). One orchestrator applies consistent
26
+ anti-flap logic and notification rules to any registered "is X healthy?" check. Replaces
27
+ scattered/ad-hoc monitoring where failures surfaced only when a client complained.
28
+
29
+ **Status (2026-06-10):** v1.0 framework + orchestrator landed; **first child monitor
30
+ pending**. Migration applied to local Core_2 only — **not yet on staging/production Core**
31
+ (coordinate before merge). Dashboard/acknowledgment layer under discussion (see Pending
32
+ scope).
33
+
34
+ Design principles: async isolation (one cron per monitor — a slow monitor can't block
35
+ others) · anti-flap on the recovery side only (N consecutive OKs before declaring
36
+ recovery) · immediate alert on first failure, throttled reminders, one recovery email per
37
+ incident · dumb-and-light children (all state/escalation/email logic lives once in the
38
+ orchestrator) · runtime config in the `Core.Monitors` table (no redeploy).
39
+
40
+ ## Key files / entry points
41
+
42
+ | File | Purpose |
43
+ |---|---|
44
+ | `worker2/Worker/Monitor.php` | Orchestrator — `abstract class _Worker_Monitor`, action route `Monitor/Run` |
45
+ | `worker2/Worker/Monitors/<Name>.php` | Child classes `_Worker_Monitors_<Name>`, one per monitor |
46
+ | `worker2/Worker/Notification/Email.php` | Reused `_Worker_Notification_Email::Send` for all notification mail |
47
+ | `dbchanges2/Core/2026-05-21 - Monitors.sql` | `Core.Monitors` table definition + INSERT templates |
48
+
49
+ Reused with **no changes**: `Core.CronJobs`, `Core.WorkerJobs`, the `WorkerCronScheduler`
50
+ Lambda, the EB worker tier + SQS delivery (see [worker2 architecture](../architecture.md)).
51
+
52
+ ## How it works
53
+
54
+ One `Core.CronJobs` row per monitor (`action = 'Monitor/Run'`, `parameters =
55
+ {"monitorId": N}`, schedule evaluated in **Central** time). Each tick, the orchestrator:
56
+
57
+ 1. Fetches the `Core.Monitors` row by `monitorId` (not found → return message, no writes).
58
+ 2. Guards `isActive` — if 0, returns early; no child invocation, no mail.
59
+ 3. Invokes the child: `class_exists` / `method_exists` checks, then `$phpClass::Run()`
60
+ inside try/catch. **Any `Throwable` becomes `{isOk: false}`** with the exception
61
+ message — a child can never crash the orchestrator. The return must be an object with
62
+ `isOk` and `message`; anything else is treated as failure.
63
+ 4. Runs the state machine (below) to compute new state, counter, and notification.
64
+ 5. Sends mail if needed via `_Worker_Notification_Email::Send` with
65
+ `clientIdentifier: 'Core'` (infrastructure mail, never client-scoped).
66
+ 6. Persists one UPDATE: `state`, `consecutiveOkCount`, `lastRunDt`, `lastResultMessage`,
67
+ plus `lastNotificationDt`/`lastNotificationType` when a notification was sent.
68
+ 7. Returns a one-line summary that lands in `WorkerJobs` for diagnostics.
69
+
70
+ Uses `_Query($sql, _underscore::DB_CORE)` and `_Database::escape()` for interpolated values.
71
+
72
+ ### State machine (the agreed contract)
73
+
74
+ | Previous | Current | New state | Counter | Notification |
75
+ |---|---|---|---|---|
76
+ | ok | ok | ok | reset to 0 | none |
77
+ | ok | alert | **alert** | reset to 0 | **alert** — always, immediate |
78
+ | alert | alert | alert | unchanged (0) | **reminder** — only if `reminderFrequencyMinutes` elapsed since `lastNotificationDt` |
79
+ | alert | ok | alert until counter hits threshold | +1 | **recovery** — only when `consecutiveOkCount >= requiredConsecutiveOks`; on send, flip to ok and reset counter |
80
+
81
+ Three **intentional product decisions** (deviations from the original meeting summary —
82
+ if you change any, update the plan doc and announce in #engineering):
83
+ 1. No anti-flap on the alarm side — first failure alerts immediately.
84
+ 2. Recovery fires only on an actual alert → ok transition (the meeting's "ok→ok sends
85
+ recovery" branch was folded out).
86
+ 3. Recovery is **not** gated on the reminder window — gating could swallow the
87
+ "all clear" email entirely.
88
+
89
+ Reminder-window math uses PHP server time vs `lastNotificationDt` written via DB `NOW()`
90
+ — consistent on a single DB server, **not explicitly UTC**; revisit if a cross-region DB
91
+ is ever introduced.
92
+
93
+ ### Notifications
94
+
95
+ Subjects: `[Monitor ALERT|REMINDER|RECOVERED] <monitor name>`. Plain-text body with
96
+ monitor name, state, notification type, timestamp, and the child's message verbatim.
97
+ From `donotreply@goagilant.com`; recipients parsed from `Monitors.peopleToNotify` (JSON
98
+ array of emails — empty array means no mail sent, no error raised).
99
+
100
+ ## Data model
101
+
102
+ ### `Core.Monitors` (new)
103
+
104
+ | Column | Type | Purpose |
105
+ |---|---|---|
106
+ | `id` / `uuid` | INT UNSIGNED PK / char(36) UNIQUE | Standard identifiers |
107
+ | `isActive` | TINYINT default 1 | 0 = orchestrator returns early |
108
+ | `dtCreated` | DATETIME | Record create time |
109
+ | `name` | VARCHAR(255) | Human-readable; appears in email subjects |
110
+ | `phpClass` | VARCHAR(255) | Child class, e.g. `_Worker_Monitors_Compass` |
111
+ | `state` | ENUM('ok','alert') default 'ok' | **Confirmed** current state |
112
+ | `consecutiveOkCount` | INT UNSIGNED default 0 | Anti-flap counter (alert→ok only) |
113
+ | `requiredConsecutiveOks` | TINYINT UNSIGNED default 2 | Threshold to flip alert→ok |
114
+ | `reminderFrequencyMinutes` | SMALLINT UNSIGNED default 60 | Reminder cadence while in alert |
115
+ | `peopleToNotify` | JSON | Array of email addresses |
116
+ | `lastRunDt` / `lastResultMessage` | DATETIME / TEXT | Last run + last child message (or exception text) |
117
+ | `lastNotificationDt` / `lastNotificationType` | DATETIME / ENUM('alert','reminder','recovery') | Most recent notification |
118
+
119
+ Index `Monitors_isActive_IDX (isActive)`. `Core.CronJobs` reused as-is — one row per monitor.
120
+
121
+ ## Child monitor contract
122
+
123
+ - File `worker2/Worker/Monitors/<Name>.php`, class `abstract class _Worker_Monitors_<Name>`
124
+ with `public static function Run(): object` returning
125
+ `(object)['isOk' => bool, 'message' => string]` — matches the worker2 action convention.
126
+ - `Run()` takes **no parameters**; all per-monitor config lives in the `Monitors` row.
127
+ - **Don't**: send notifications, write to `Monitors`, implement escalation, or wrap
128
+ everything in try/catch (the orchestrator catches all `Throwable`s — bubbling up is the
129
+ simplest way to fail loudly).
130
+ - **Do**: check exactly one signal per monitor; put actionable context in `message`
131
+ (recipients see it verbatim); register any non-Core DB connection inline at the top of
132
+ `Run()` — the framework does **not** call `initialize()` on child monitors.
133
+
134
+ ### Adding a new monitor (runbook)
135
+
136
+ 1. Write the child class (one check, returns `{isOk, message}`).
137
+ 2. INSERT the `Core.Monitors` row (uuid, name, phpClass, thresholds, peopleToNotify).
138
+ 3. INSERT the `Core.CronJobs` row (`action = 'Monitor/Run'`,
139
+ `parameters = JSON_OBJECT('monitorId', <id>)`).
140
+ 4. Verify end-to-end in dev (8-step checklist: migration sanity, cron registration,
141
+ first-failure alert, reminder cadence, anti-flap recovery, exception path, inactive
142
+ monitor, missing class — all must pass before production).
143
+ 5. Deploy class to staging+production worker2; apply the two INSERTs to each Core.
144
+
145
+ No Lambda, SQS, or orchestrator changes needed per monitor.
146
+
147
+ ### Operational notes
148
+
149
+ - Force a recheck: set `lastRunDt = NULL`, or POST `{"action":"Monitor/Run","parameters":{"monitorId":N}}` to the worker2 test endpoint (debug path, bypasses WorkerJobs).
150
+ - Pause a monitor: `isActive = 0` (cron row can stay active). Pause email only: `peopleToNotify = JSON_ARRAY()`.
151
+ - Manual state reset: clear `state`/`consecutiveOkCount`/`lastNotificationDt`/`lastNotificationType` — sparingly; the state machine self-corrects.
152
+
153
+ ## Client variations
154
+
155
+ None — the framework is shared Core infrastructure. Individual monitors target specific
156
+ clients' data flows (Compass, Prudential, AIG, …) but live as separate child classes.
157
+
158
+ ## Gotchas / known issues
159
+
160
+ - **First child monitor not yet built** — `worker2/Worker/Monitors/` does not exist yet
161
+ (as of 2026-06-10).
162
+ - **Migration not applied to staging/production Core** — only local Core_2. Coordinate
163
+ before merge.
164
+ - `worker2/MONITORING_PLAN.md` is referenced by the design doc but **missing on disk** —
165
+ stale reference to resolve.
166
+ - Child return value must be an **object** with both `isOk` and `message`; a bare array
167
+ or missing property is treated as failure by design.
168
+ - Reminder math is server-time based, not UTC — see state-machine section.
169
+
170
+ ## Pending scope (NOT implemented)
171
+
172
+ - **Dashboard + acknowledgments** — ack columns on `Monitors` (or a history table),
173
+ reminder gate becomes "elapsed AND not acknowledged", recovery clears the ack. Open
174
+ questions: ack survival across flap-back, auto-expiry, which auth system owns dashboard
175
+ identity. Owner: mhammontree, pending team discussion.
176
+ - Slack / SMS / PagerDuty (email-only today) · `MonitorRuns` history table ·
177
+ HTML email + dashboard deep-links · anti-flap on the alarm side.
178
+
179
+ ## Related docs
180
+
181
+ - [worker2 architecture](../architecture.md) — cron/WorkerJobs pipeline the orchestrator rides on
182
+ - [Creating Worker Actions](./creating-worker-actions.md) — the action class/method conventions child monitors follow
183
+ - [dbchanges2 architecture](../../dbchanges2/architecture.md) — migration naming/ordering rules for the Monitors table change
@@ -9,8 +9,8 @@ _Auto-generated by `knowledge.js index`. Do not hand-edit._
9
9
 
10
10
  ## 2.0 framework
11
11
 
12
- - **_underscore** (_Underscore) _(framework core)_ — 3 doc(s) → [2.0/apps/_underscore/INDEX.md](2.0/apps/_underscore/INDEX.md)
13
- - **worker2** (Worker) — 4 doc(s) → [2.0/apps/worker2/INDEX.md](2.0/apps/worker2/INDEX.md)
12
+ - **_underscore** (_Underscore) _(framework core)_ — 4 doc(s) → [2.0/apps/_underscore/INDEX.md](2.0/apps/_underscore/INDEX.md)
13
+ - **worker2** (Worker) — 5 doc(s) → [2.0/apps/worker2/INDEX.md](2.0/apps/worker2/INDEX.md)
14
14
  - **api2** (API) — 1 doc(s) → [2.0/apps/api2/INDEX.md](2.0/apps/api2/INDEX.md)
15
15
  - **dbchanges2** (Database Changes) _(framework core)_ — 1 doc(s) → [2.0/apps/dbchanges2/INDEX.md](2.0/apps/dbchanges2/INDEX.md)
16
16
  - **toga2-supply** (TOGa Supply) — 2 doc(s) → [2.0/apps/toga2-supply/INDEX.md](2.0/apps/toga2-supply/INDEX.md)
@@ -21,4 +21,5 @@ _Auto-generated by `knowledge.js index`. Do not hand-edit._
21
21
  - **compass-usa** → [clients/compass-usa/INDEX.md](clients/compass-usa/INDEX.md)
22
22
  - **elite** → [clients/elite/INDEX.md](clients/elite/INDEX.md)
23
23
  - **nycdoe** → [clients/nycdoe/INDEX.md](clients/nycdoe/INDEX.md)
24
+ - **prudential** → [clients/prudential/INDEX.md](clients/prudential/INDEX.md)
24
25
 
@@ -3,4 +3,5 @@
3
3
  | Doc | Framework | Summary | Files |
4
4
  |-----|-----------|---------|-------|
5
5
  | [Compass ASN → ItemFulfillment Auto-Creation](features/asn-to-item-fulfillment.md) | 2.0 | For Compass USA, posting an AdvanceShippingNotice (ASN) auto-creates the ItemFulfillment (IF) on the upstream SalesOrder. | _underscore/Model/Compass/AdvanceShippingNotice.php |
6
+ | [Compass: Item Fulfillments for Sales Order Items TableView (tracking via bridge)](features/item-fulfillment-tracking-tableview.md) | 2.0 | The Compass TableView `item-fulfillments-for-sales-order-items` (`TableViews.id = 13` in Client_Compass) was rebuilt as part of the tracking-number bridge migra | dbchanges2/Client_Compass/2026-06-10 - ItemFulfillmentsForSalesOrderItemsTableView.sql |
6
7
  | [Compass USA](profile.md) | 2.0 | Compass USA is a TOGA client running a multi-tier supply-chain commerce operation. | |
@@ -0,0 +1,46 @@
1
+ ---
2
+ title: "Compass: Item Fulfillments for Sales Order Items TableView (tracking via bridge)"
3
+ framework: "2.0"
4
+ project: _Underscore
5
+ client: compass-usa
6
+ type: client-feature
7
+ status: active
8
+ updated: 2026-06-10
9
+ owners: ["jcardinal"]
10
+ files:
11
+ - dbchanges2/Client_Compass/2026-06-10 - ItemFulfillmentsForSalesOrderItemsTableView.sql
12
+ related:
13
+ - ../../../2.0/apps/_underscore/features/tracking-number-bridges.md
14
+ ---
15
+
16
+ ## Summary
17
+
18
+ The Compass TableView `item-fulfillments-for-sales-order-items` (`TableViews.id = 13` in
19
+ Client_Compass) was rebuilt as part of the tracking-number bridge migration. It was rooted at
20
+ **Units** with INNER joins (so non-serialized items with no unit never appeared) and sourced the
21
+ outbound tracking number from the now-dropped `ItemFulfillmentItemUnits.trackingNumberId`.
22
+
23
+ ## How it works (new definition)
24
+
25
+ - **Base record re-rooted** to `ItemFulfillmentItems` (record 29).
26
+ - Joins: INNER `SalesOrderItems`; **OUTER** `ItemFulfillmentItemUnits` (so items without a
27
+ serialized unit still return); OUTER `Units`; OUTER the new `ItemFulfillmentItemUnits_TrackingNumbers`
28
+ bridge (record 319) → OUTER `TrackingNumbers` → OUTER `ShippingCarriers`.
29
+ - Columns, in order: **Sales Order Line Number, Quantity Fulfilled (new — `ItemFulfillmentItems.quantity`),
30
+ Outbound Tracking #, Carrier, Serial #, Asset Tag.**
31
+ - Default sort (`TableViews.sortPrimaryTableViewFieldId`) re-pointed to the line-number field.
32
+
33
+ ## Gotchas / known issues
34
+
35
+ - Tracking is sourced through the bridge join (`ItemFulfillmentItemUnits_TrackingNumbers.trackingNumberId`),
36
+ NOT the dropped column. The bridge record/fields (319 / 2183 itemFulfillmentItemUnitId / 2184
37
+ trackingNumberId) come from the Core migration and must exist before this file runs.
38
+ - `TableViews.sortPrimaryTableViewFieldId` is a FK to `TableViewFields`; it must be set NULL before the
39
+ old fields are deleted, then re-pointed (via a multi-table UPDATE, not a self-subquery, to avoid 1093).
40
+ - `TableViewFields` must be deleted before `TableViewJoins` (`tableViewJoinId` FK).
41
+
42
+ ## Client variations
43
+ This is Compass-only. The underlying bridge model is shared (see the _underscore feature doc).
44
+
45
+ ## Related docs
46
+ - 2.0 _underscore: Tracking-Number Bridge Migration.
@@ -0,0 +1,6 @@
1
+ # Client: prudential
2
+
3
+ | Doc | Framework | Summary | Files |
4
+ |-----|-----------|---------|-------|
5
+ | [Prudential: Dell ASN units PRE/POST interceptor (legacy key + flat tracking)](features/dell-asn-units-interceptor.md) | 2.0 | After the tracking-number bridge migration, the ASN unit route was renamed (`advance-shipping-notice-units` → `advance-shipping-notice-item-units`), so the inhe | _underscore/Model/Prudential/AdvanceShippingNotice.php, dbchanges2/Client_Prudential/2026-06-10 - AsnUnitsInterceptor.sql |
6
+ | [Prudential](profile.md) | 2.0 | Prudential is a TOGA client whose device-fulfillment flow is driven by **Dell** via the Dell API (`Client_Prudential.Apis.id = 2`). | |
@@ -0,0 +1,51 @@
1
+ ---
2
+ title: "Prudential: Dell ASN units PRE/POST interceptor (legacy key + flat tracking)"
3
+ framework: "2.0"
4
+ project: _Underscore
5
+ client: prudential
6
+ type: client-feature
7
+ status: active
8
+ updated: 2026-06-10
9
+ owners: ["jcardinal"]
10
+ files:
11
+ - _underscore/Model/Prudential/AdvanceShippingNotice.php
12
+ - dbchanges2/Client_Prudential/2026-06-10 - AsnUnitsInterceptor.sql
13
+ related:
14
+ - ../../../2.0/apps/_underscore/features/tracking-number-bridges.md
15
+ ---
16
+
17
+ ## Summary
18
+
19
+ After the tracking-number bridge migration, the ASN unit route was renamed
20
+ (`advance-shipping-notice-units` → `advance-shipping-notice-item-units`), so the inherent-child
21
+ key became `advanceShippingNoticeItemUnits`, and unit tracking now lives in the bridge child
22
+ `advanceShippingNoticeItemUnitTrackingNumbers` (the scalar `trackingNumber`/`returnTrackingNumber`
23
+ fields on the unit were dropped). **Dell will not change their payload**, so a Prudential-only
24
+ interceptor translates it on the way in.
25
+
26
+ ## How it works
27
+
28
+ - **`Model/Prudential/AdvanceShippingNotice.php::prePost(&$api, &$payload)`** — for each
29
+ `$payload->advanceShippingNoticeItems[]`: renames the legacy `advanceShippingNoticeUnits` key to
30
+ `advanceShippingNoticeItemUnits`, and lifts each unit's flat `trackingNumber` / `returnTrackingNumber`
31
+ into a single `advanceShippingNoticeItemUnitTrackingNumbers` bridge-child row, removing the flat fields.
32
+ - **Registration** — `Client_Prudential.ApiPayloadInterceptors` row: `recordId = 55`
33
+ (advance-shipping-notices), `prePostProcessing = 'PRE'`, `httpMethod = 'POST'`, `apiId = 2` (Dell),
34
+ `isActive = 1`. The engine resolves `_Model_Client_AdvanceShippingNotice` → `_Model_Prudential_AdvanceShippingNotice`
35
+ by client identifier; **both** the DB interceptor row AND the model method are required for it to fire.
36
+
37
+ ## Inbound payload (Dell, unchanged)
38
+ ```
39
+ advanceShippingNoticeItems: [ { quantity, advanceShippingNoticeUnits: [
40
+ { trackingNumber:{number}, returnTrackingNumber:{number}, unit:{serialNumber} } ] } ]
41
+ ```
42
+ The interceptor rewrites each item to `advanceShippingNoticeItemUnits` with the tracking moved into
43
+ `advanceShippingNoticeItemUnitTrackingNumbers`.
44
+
45
+ ## Gotchas
46
+ - Scoped to `apiId = 2` so other (internal) Prudential callers — which already send the new shape —
47
+ are not double-transformed.
48
+ - DB change lives in `dbchanges2/Client_Prudential/`.
49
+
50
+ ## Related docs
51
+ - 2.0 _underscore: Tracking-Number Bridge Migration.
@@ -0,0 +1,34 @@
1
+ ---
2
+ title: Prudential
3
+ framework: "2.0"
4
+ project: _Underscore
5
+ client: prudential
6
+ type: profile
7
+ status: active
8
+ updated: 2026-06-10
9
+ owners: ["jcardinal"]
10
+ files: []
11
+ related:
12
+ - features/dell-asn-units-interceptor.md
13
+ ---
14
+
15
+ ## Summary
16
+ Prudential is a TOGA client whose device-fulfillment flow is driven by **Dell** via the Dell
17
+ API (`Client_Prudential.Apis.id = 2`). Dell posts Advance Shipping Notices (with tracking and
18
+ return-tracking per unit) into the 2.0 platform (DB `Client_Prudential`); the 1.0 worker tier
19
+ (`crons/toga2/prudential/`) handles NetSuite sync, ServiceNow (RITM) close/ship updates, and
20
+ order-status transmissions.
21
+
22
+ ## Integrations
23
+ - **Inbound:** Dell API → POST `/v2/advance-shipping-notices` (creates ASNs + units + tracking).
24
+ - **Outbound (worker crons):** `transmissions_to_netsuite.php`, `transmit_ordershipped_updates_prudential.php`,
25
+ `transmit_closecomplete_updates_prudential.php`, `transmit_order_delivered_updates_from_fedex_toga2.php`,
26
+ `update_tracking_number_from_netsuite_to_toga.php`, `manually_insert_ASN.php`.
27
+
28
+ ## Gotchas
29
+ - Dell will not change their payload shape — see the Dell ASN units interceptor feature doc for the
30
+ PRE/POST translation that keeps their feed working after the tracking-number bridge migration.
31
+
32
+ ## Related docs
33
+ - Dell ASN units interceptor.
34
+ - 2.0 _underscore: Tracking-Number Bridge Migration.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "toga-ai",
3
- "version": "1.0.40",
3
+ "version": "1.0.42",
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",