toga-ai 1.0.41 → 1.0.43

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.
@@ -9,7 +9,7 @@ _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)
12
+ - **_underscore** (_Underscore) _(framework core)_ — 4 doc(s) → [2.0/apps/_underscore/INDEX.md](2.0/apps/_underscore/INDEX.md)
13
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)
@@ -17,8 +17,9 @@ _Auto-generated by `knowledge.js index`. Do not hand-edit._
17
17
 
18
18
  ## Clients
19
19
 
20
- - **compass-canada** → [clients/compass-canada/INDEX.md](clients/compass-canada/INDEX.md)
21
- - **compass-usa** → [clients/compass-usa/INDEX.md](clients/compass-usa/INDEX.md)
22
- - **elite** → [clients/elite/INDEX.md](clients/elite/INDEX.md)
23
- - **nycdoe** → [clients/nycdoe/INDEX.md](clients/nycdoe/INDEX.md)
20
+ - **Compass Canada** (`compass-canada`) → [clients/compass-canada/INDEX.md](clients/compass-canada/INDEX.md)
21
+ - **Compass USA** (`compass-usa`) → [clients/compass-usa/INDEX.md](clients/compass-usa/INDEX.md)
22
+ - **Elite** (`elite`) → [clients/elite/INDEX.md](clients/elite/INDEX.md)
23
+ - **New York City Department of Education** (`nycdoe`) → [clients/nycdoe/INDEX.md](clients/nycdoe/INDEX.md)
24
+ - **Prudential Financial** (`prudential`) → [clients/prudential/INDEX.md](clients/prudential/INDEX.md)
24
25
 
@@ -1,4 +1,4 @@
1
- # Client: compass-canada
1
+ # Client: Compass Canada `compass-canada`
2
2
 
3
3
  | Doc | Framework | Summary | Files |
4
4
  |-----|-----------|---------|-------|
@@ -1,5 +1,6 @@
1
1
  ---
2
2
  title: Compass Canada
3
+ name: "Compass Canada"
3
4
  framework: "2.0"
4
5
  project: _Underscore
5
6
  client: compass-canada
@@ -1,6 +1,7 @@
1
- # Client: compass-usa
1
+ # Client: Compass USA `compass-usa`
2
2
 
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.
@@ -1,5 +1,6 @@
1
1
  ---
2
2
  title: Compass USA
3
+ name: "Compass USA"
3
4
  framework: "2.0"
4
5
  project: _Underscore
5
6
  client: compass-usa
@@ -1,4 +1,4 @@
1
- # Client: elite
1
+ # Client: Elite `elite`
2
2
 
3
3
  | Doc | Framework | Summary | Files |
4
4
  |-----|-----------|---------|-------|
@@ -1,5 +1,6 @@
1
1
  ---
2
2
  title: Elite Client Profile
3
+ name: "Elite"
3
4
  framework: "2.0"
4
5
  project: Worker
5
6
  client: elite
@@ -1,4 +1,4 @@
1
- # Client: nycdoe
1
+ # Client: New York City Department of Education `nycdoe`
2
2
 
3
3
  | Doc | Framework | Summary | Files |
4
4
  |-----|-----------|---------|-------|
@@ -1,5 +1,6 @@
1
1
  ---
2
2
  title: NYC DOE (New York City Department of Education)
3
+ name: "New York City Department of Education"
3
4
  framework: "1.0"
4
5
  project: Worker
5
6
  client: nycdoe
@@ -0,0 +1,6 @@
1
+ # Client: Prudential Financial `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,35 @@
1
+ ---
2
+ title: Prudential
3
+ name: "Prudential Financial"
4
+ framework: "2.0"
5
+ project: _Underscore
6
+ client: prudential
7
+ type: profile
8
+ status: active
9
+ updated: 2026-06-10
10
+ owners: ["jcardinal"]
11
+ files: []
12
+ related:
13
+ - features/dell-asn-units-interceptor.md
14
+ ---
15
+
16
+ ## Summary
17
+ Prudential is a TOGA client whose device-fulfillment flow is driven by **Dell** via the Dell
18
+ API (`Client_Prudential.Apis.id = 2`). Dell posts Advance Shipping Notices (with tracking and
19
+ return-tracking per unit) into the 2.0 platform (DB `Client_Prudential`); the 1.0 worker tier
20
+ (`crons/toga2/prudential/`) handles NetSuite sync, ServiceNow (RITM) close/ship updates, and
21
+ order-status transmissions.
22
+
23
+ ## Integrations
24
+ - **Inbound:** Dell API → POST `/v2/advance-shipping-notices` (creates ASNs + units + tracking).
25
+ - **Outbound (worker crons):** `transmissions_to_netsuite.php`, `transmit_ordershipped_updates_prudential.php`,
26
+ `transmit_closecomplete_updates_prudential.php`, `transmit_order_delivered_updates_from_fedex_toga2.php`,
27
+ `update_tracking_number_from_netsuite_to_toga.php`, `manually_insert_ASN.php`.
28
+
29
+ ## Gotchas
30
+ - Dell will not change their payload shape — see the Dell ASN units interceptor feature doc for the
31
+ PRE/POST translation that keeps their feed working after the tracking-number bridge migration.
32
+
33
+ ## Related docs
34
+ - Dell ASN units interceptor.
35
+ - 2.0 _underscore: Tracking-Number Bridge Migration.
package/knowledge.js CHANGED
@@ -252,12 +252,16 @@ function cmdIndex() {
252
252
 
253
253
  // per-client INDEX.md
254
254
  const clientsDir = path.join(ROOT, 'clients');
255
+ const clientFormalNames = {}; // slug -> formal name (from profile.md `name:`)
255
256
  if (fs.existsSync(clientsDir)) {
256
257
  for (const client of fs.readdirSync(clientsDir, { withFileTypes: true }).filter(e => e.isDirectory()).map(e => e.name)) {
257
258
  const clientDir = path.join(clientsDir, client);
258
259
  const clientDocs = docs.filter(d => d.file.startsWith(clientDir + path.sep));
260
+ const profileDoc = clientDocs.find(d => path.basename(d.file) === 'profile.md');
261
+ const formalName = (profileDoc && profileDoc.data.name) || client;
262
+ clientFormalNames[client] = formalName;
259
263
  const lines = [
260
- `# Client: ${client}`,
264
+ `# Client: ${formalName} \`${client}\``,
261
265
  '',
262
266
  '| Doc | Framework | Summary | Files |',
263
267
  '|-----|-----------|---------|-------|',
@@ -284,7 +288,7 @@ function cmdIndex() {
284
288
  ? fs.readdirSync(clientsDir, { withFileTypes: true }).filter(e => e.isDirectory()).map(e => e.name) : [];
285
289
  master.push('## Clients', '');
286
290
  if (clientNames.length === 0) master.push('_No client knowledge captured yet._', '');
287
- else { for (const c of clientNames) master.push(`- **${c}** → [clients/${c}/INDEX.md](clients/${c}/INDEX.md)`); master.push(''); }
291
+ else { for (const c of clientNames) master.push(`- **${clientFormalNames[c] || c}** (\`${c}\`) → [clients/${c}/INDEX.md](clients/${c}/INDEX.md)`); master.push(''); }
288
292
  writeIndex(path.join(ROOT, 'INDEX.md'), master);
289
293
 
290
294
  console.log('index rebuilt: ' + docs.length + ' doc(s) across ' + registry.length + ' registered repo(s).');
@@ -340,6 +344,7 @@ function cmdValidate() {
340
344
  const isClientDoc = d.rel.startsWith('clients/');
341
345
  if (isClientDoc) {
342
346
  if (!d.data.client || d.data.client === 'shared') { fail(`${d.rel}: client doc must set a real "client"`); ok = false; }
347
+ if (path.basename(d.rel) === 'profile.md' && !d.data.name) { fail(`${d.rel}: client profile must set a formal "name" (e.g. name: "Compass USA")`); ok = false; }
343
348
  } else {
344
349
  // framework/apps doc: framework + repo must match the path
345
350
  const parts = d.rel.split('/'); // <fw>/apps/<repo>/... OR <fw>/standards/...
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "toga-ai",
3
- "version": "1.0.41",
3
+ "version": "1.0.43",
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",
@@ -146,6 +146,8 @@ For every doc, set/refresh frontmatter: `framework`, `repo`, `project`, `client`
146
146
  `type`, `status`, `updated` (today's date), `owners`, `files`, `related`.
147
147
  Set `owners` to `["<AUTHOR_USERNAME>"]` from Step 1b — **never leave it empty**; when a doc
148
148
  already has owners, add `AUTHOR_USERNAME` if missing rather than replacing.
149
+ For a client `profile.md`, set the dedicated `name:` field to the client's **formal name**
150
+ (distinct from `title`, the doc title) — `validate` requires it; see New-client onboarding.
149
151
  Add `related:` cross-links. Append new repos to `<TEAM_REPO>/knowledge/registry.json`.
150
152
 
151
153
  ## Step 6 — Validate, index, mirror back (mandatory)
@@ -355,6 +357,38 @@ Ask **all** — assume nothing (a heuristic may seed a default to confirm):
355
357
 
356
358
  Append the entry to `registry.json`, then continue. `validate` will confirm consistency.
357
359
 
360
+ ## New-client onboarding (client has no `knowledge/clients/<slug>/` folder yet)
361
+
362
+ When this session's work is for a client with no `clients/<slug>/` folder, before writing
363
+ any client docs:
364
+
365
+ 1. **Slug** — confirm the folder slug (lowercase, hyphenated, e.g. `compass-usa`). The slug
366
+ is used only for the folder name.
367
+ 2. **Formal name** — **ASK the developer for the client's formal name** (e.g. "Compass USA",
368
+ "New York City Department of Education"). **Never invent it from the slug.**
369
+ 3. Create `clients/<slug>/profile.md` with the formal name in the dedicated `name:` field
370
+ (distinct from `title`). `validate` **requires** `name:` on every client profile — a
371
+ profile missing it fails the build.
372
+
373
+ ```markdown
374
+ ---
375
+ title: <Client> Profile
376
+ name: "<Formal Client Name>" # formal name; the slug is only the folder
377
+ framework: "<1.0|2.0>"
378
+ project: <Project>
379
+ client: <slug>
380
+ type: profile
381
+ status: active
382
+ updated: <YYYY-MM-DD>
383
+ owners: ["<AUTHOR_USERNAME>"]
384
+ files: []
385
+ related: []
386
+ ---
387
+
388
+ ## Summary
389
+ Who the client is and what their integration does.
390
+ ```
391
+
358
392
  ---
359
393
 
360
394
  ### After Capture
@@ -117,8 +117,10 @@ Ask these in one message. **Present the repos/projects you already know** (read
117
117
  choice;** a session often spans more than one repo/project, so always let the developer
118
118
  pick several. If they pick a repo not listed, run **New-repo onboarding** (below) for
119
119
  each new repo before continuing.
120
- 4. **Client** — which client is this for, or "shared / internal"? **List EVERY folder under
121
- `knowledge/clients/`** plus "shared / internal" and "a new client" — do not show only a
120
+ 4. **Client** — which client is this for, or "shared / internal"? **List EVERY client by its
121
+ formal name** read the `name:` field from each `knowledge/clients/<slug>/profile.md`
122
+ and show the **formal name, not the slug** (fall back to a title-cased slug only if
123
+ `name:` is missing). Include "shared / internal" and "a new client". Do not show only a
122
124
  subset.
123
125
  5. **What are you building or changing?** (a sentence — used to pick relevant feature docs.)
124
126
 
@@ -142,9 +144,10 @@ and naturally supports multi-select.
142
144
  N. a new repo not listed
143
145
 
144
146
  **Which client?** (one, or "shared / internal")
145
- 1. compass-canada
146
- 2. compass-usa
147
- ... every folder under knowledge/clients/ ...
147
+ 1. Compass Canada
148
+ 2. Compass USA
149
+ 3. Elite
150
+ ... every client's FORMAL name (from clients/<slug>/profile.md `name:`), not the slug ...
148
151
  S. shared / internal
149
152
  X. a new client
150
153
  ```