toga-ai 1.0.354 → 1.0.356
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/tools/features/talos-kb-documents-admin.md +121 -42
- package/knowledge/INDEX.md +1 -1
- package/knowledge/clients/compass-canada/profile.md +8 -1
- package/knowledge/clients/compass-usa/INDEX.md +1 -0
- package/knowledge/clients/compass-usa/features/approval-decision-flow.md +100 -0
- package/knowledge/clients/compass-usa/profile.md +6 -1
- package/package.json +1 -1
|
@@ -6,7 +6,7 @@ project: Tools
|
|
|
6
6
|
client: shared
|
|
7
7
|
type: feature
|
|
8
8
|
status: active
|
|
9
|
-
updated: 2026-07-
|
|
9
|
+
updated: 2026-07-16
|
|
10
10
|
owners: [jcardinal]
|
|
11
11
|
files:
|
|
12
12
|
- tools/mvc/talos/kb-documents/get.php
|
|
@@ -97,36 +97,100 @@ false success.
|
|
|
97
97
|
### `POST /talos/knowledge-bases` — create a new KB (synchronous provisioning)
|
|
98
98
|
|
|
99
99
|
Admin action that provisions a brand-new Talos knowledge base end to end in **one
|
|
100
|
-
synchronous request
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
**
|
|
111
|
-
|
|
100
|
+
synchronous request**. The KB is a **classic VECTOR knowledge base backed by Amazon S3
|
|
101
|
+
Vectors** (the canonical design — see the architecture note below), and the flow is now
|
|
102
|
+
**9 steps** across systems with **no shared transaction**:
|
|
103
|
+
|
|
104
|
+
1. **validate slug** (≤255) + derive via **`App_Talos_S3::slugify()`** (kebab-case, `iconv`
|
|
105
|
+
ASCII translit; rejects a duplicate slug — `slug` is UNIQUE).
|
|
106
|
+
2. **S3 folders** — 3 markers `development-team/{slug}/{approved,archive,upload}/` via
|
|
107
|
+
**`App_Talos_S3::putFolderMarker()`**.
|
|
108
|
+
3. **S3 Vectors store** — create a dedicated vector bucket + index via
|
|
109
|
+
`App_Talos_Bedrock::createVectorStore()`, capturing the authoritative **`indexArn`**.
|
|
110
|
+
4. **VECTOR KB** — `createKnowledgeBase($bedrock, $slug, $indexArn)` (VECTOR + `S3_VECTORS`
|
|
111
|
+
storage).
|
|
112
|
+
5. **wait for `ACTIVE`** (`waitForKnowledgeBaseActive`).
|
|
113
|
+
6. **S3 data source** — plain `S3` connector + `BEDROCK_DATA_AUTOMATION` parsing.
|
|
114
|
+
7. **`Team.KnowledgeBases`** (`db_team`, Team MySQL cluster).
|
|
115
|
+
8. **`Client_True.VectorIndexes`** (`db_true`, **Client MySQL cluster** — not the Team cluster).
|
|
116
|
+
9. **2 Talos Postgres rows** (`public.knowledge_bases` + `public.assistant_knowledge_bases`,
|
|
117
|
+
the latter linking a hard-coded assistant id).
|
|
118
|
+
|
|
119
|
+
The create branch sets `set_time_limit(CREATE_MAX_EXECUTION_SECONDS = 600)` up top because the
|
|
120
|
+
flow blocks on multi-minute AWS waits (S3 Vectors index readiness + KB `ACTIVE`, up to ~300s).
|
|
121
|
+
This only prevents the PHP fatal — it does **not** lift the EB Apache `ProxyTimeout` ceiling
|
|
122
|
+
(handled separately in `.platform`, see below). This flow **stays synchronous by design** —
|
|
123
|
+
moving it to worker2/SQS async was explicitly rejected; the fix path is to raise timeouts,
|
|
124
|
+
never to background the work.
|
|
125
|
+
|
|
126
|
+
### Bedrock KB architecture — VECTOR + S3 Vectors (canonical)
|
|
127
|
+
|
|
128
|
+
The canonical Talos KB config (from **`development-team-dunn`**, confirmed identical across
|
|
129
|
+
`development-team-ninja-rmm` and `development-team-warehouse`) is a **classic VECTOR** KB, **not**
|
|
130
|
+
the fully-MANAGED type:
|
|
131
|
+
- **KnowledgeBase:** `knowledgeBaseConfiguration.type = VECTOR`;
|
|
132
|
+
`vectorKnowledgeBaseConfiguration.embeddingModelArn =
|
|
133
|
+
arn:aws:bedrock:us-east-1::foundation-model/amazon.titan-embed-text-v2:0`;
|
|
134
|
+
`embeddingModelConfiguration.bedrockEmbeddingModelConfiguration.embeddingDataType = FLOAT32`;
|
|
135
|
+
`storageConfiguration.type = S3_VECTORS` pointing at the per-KB S3 Vectors index ARN.
|
|
136
|
+
- **S3 Vectors index** (one dedicated vector bucket + index **per KB**): `dimension = 1024`,
|
|
137
|
+
`dataType = float32`, `distanceMetric = euclidean`,
|
|
138
|
+
`metadataConfiguration.nonFilterableMetadataKeys = [AMAZON_BEDROCK_TEXT, AMAZON_BEDROCK_METADATA]`,
|
|
139
|
+
encryption `sseType = AES256` (SSE-S3). Vector bucket name mirrors the console:
|
|
140
|
+
`bedrock-knowledge-base-{slug}`; index name `bedrock-knowledge-base-default-index`.
|
|
141
|
+
- **DataSource:** `dataSourceConfiguration.type = S3` (plain S3 connector);
|
|
142
|
+
`s3Configuration.bucketArn = arn:aws:s3:::togaiq`;
|
|
143
|
+
`inclusionPrefixes = ["development-team/{slug}/approved/"]`;
|
|
144
|
+
`vectorIngestionConfiguration.parsingConfiguration.parsingStrategy = BEDROCK_DATA_AUTOMATION`;
|
|
145
|
+
`dataDeletionPolicy = DELETE`.
|
|
146
|
+
|
|
147
|
+
Account `654654170868`, region `us-east-1`. Verified working end-to-end in production
|
|
148
|
+
(2026-07-16).
|
|
112
149
|
|
|
113
150
|
The handler is **best-effort with no rollback**: on a mid-flow failure it stops and returns
|
|
114
151
|
a `steps[]` report **without tearing down** already-created AWS resources (see Gotchas —
|
|
115
152
|
orphaned KB).
|
|
116
153
|
|
|
117
|
-
**`App_Talos_Bedrock`** (`_/app/talos/bedrock.php`) — Bedrock Agent
|
|
154
|
+
**`App_Talos_Bedrock`** (`_/app/talos/bedrock.php`) — Bedrock Agent + S3 Vectors clients:
|
|
155
|
+
- **`ROLE_ARN`** — the single **shared** execution role
|
|
156
|
+
`arn:aws:iam::654654170868:role/AmazonBedrockExecutionRoleForKnowledgeBase-talos`, hard-coded
|
|
157
|
+
(replaced the old per-KB `_tonej` role). See the IAM prerequisite below.
|
|
158
|
+
- `s3VectorsClient()` — `Aws\S3Vectors\S3VectorsClient`, api version `2025-07-15`, using the same
|
|
159
|
+
`[talos]` credential resolution as `App_Talos_S3::client()`.
|
|
160
|
+
- `vectorBucketName()` — derives `bedrock-knowledge-base-{slug}` and enforces the S3 Vectors
|
|
161
|
+
bucket-name length limit (3–63 chars).
|
|
162
|
+
- `createVectorStore()` — creates the vector bucket + index sized to Titan v2 (dim 1024,
|
|
163
|
+
float32, euclidean), then **polls `getIndex` for the authoritative index ARN** with a short
|
|
164
|
+
`NotFound` retry; returns an actionable error on `Conflict`/`AlreadyExists` from an orphaned
|
|
165
|
+
prior attempt.
|
|
166
|
+
- `createKnowledgeBase($bedrock, string $slug, string $indexArn)` — creates the VECTOR KB with
|
|
167
|
+
`S3_VECTORS` storage (signature now takes the index ARN).
|
|
118
168
|
- `waitForKnowledgeBaseActive()` polls `getKnowledgeBase` every `WAIT_INTERVAL_SECONDS` (3s)
|
|
119
|
-
up to **`WAIT_MAX_SECONDS` = 300
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
169
|
+
up to **`WAIT_MAX_SECONDS` = 300**. Exits immediately on `ACTIVE`/`FAILED`. 300s sits well
|
|
170
|
+
under the 900s Apache/PHP ceiling.
|
|
171
|
+
- `createDataSource()` — plain **`S3`** connector + `BEDROCK_DATA_AUTOMATION` parsing (rewritten
|
|
172
|
+
from the old `MANAGED_KNOWLEDGE_BASE_CONNECTOR`). See the architecture note above for the exact
|
|
173
|
+
shape.
|
|
174
|
+
- The vendored `aws/aws-sdk-php` (`^3.387`) already ships both the `S3Vectors` and `BedrockAgent`
|
|
175
|
+
clients and the `S3_VECTORS` `storageConfiguration` shape (verified against the SDK model). The
|
|
176
|
+
now-dead `BUCKET_NAME` / `BUCKET_OWNER_ACCOUNT_ID` constants were removed.
|
|
177
|
+
|
|
178
|
+
**IAM prerequisites (one-time, manual — the app does NOT provision IAM):**
|
|
179
|
+
- **Shared execution role** `AmazonBedrockExecutionRoleForKnowledgeBase-talos` — one role for
|
|
180
|
+
ALL Talos KBs (decision: do **not** create a role+policies per KB, which would require granting
|
|
181
|
+
the web app IAM write powers — rejected as a security expansion). Has 4 inline policies
|
|
182
|
+
mirroring dunn's but **wildcarded**: s3vectors data-plane on
|
|
183
|
+
`bucket/bedrock-knowledge-base-*/index/*`; `s3:GetObject` on
|
|
184
|
+
`togaiq/development-team/*/approved/*` (+ `s3:ListBucket` on `togaiq`); `bedrock:InvokeModel`
|
|
185
|
+
on `titan-embed-text-v2:0` (+ marketplace); BDA `GetDataAutomationStatus` +
|
|
186
|
+
`InvokeDataAutomationAsync`. Trust policy: `bedrock.amazonaws.com` assume-role with
|
|
187
|
+
`SourceAccount 654654170868` and `SourceArn knowledge-base/*`.
|
|
188
|
+
- **App credential grant** — the `[talos]` app credentials are IAM user
|
|
189
|
+
`arn:aws:iam::654654170868:user/bedrock-model-accessor`. It was granted (new inline policy
|
|
190
|
+
**`TalosKbProvisioning`**) `s3vectors:CreateVectorBucket/CreateIndex/GetIndex` and
|
|
191
|
+
`iam:PassRole` on the shared role. Its bedrock create actions were already covered by
|
|
192
|
+
`AmazonBedrockFullAccess` and S3 by `AmazonS3FullAccess`. **`s3vectors` is a SEPARATE service
|
|
193
|
+
NOT covered by `AmazonBedrockFullAccess`** — that was the real missing grant.
|
|
130
194
|
|
|
131
195
|
## Helpers
|
|
132
196
|
|
|
@@ -200,20 +264,23 @@ orphaned KB).
|
|
|
200
264
|
- **php-fpm `request_terminate_timeout` can still kill the request.** If
|
|
201
265
|
`/etc/php-fpm.d/www.conf`'s `request_terminate_timeout` is ever set to 60 it terminates the
|
|
202
266
|
request regardless of Apache — it must be `0` or `>= 900`.
|
|
203
|
-
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
`
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
267
|
+
- **The Bedrock API does NOT auto-provision the S3 Vectors bucket/index** (the console does).
|
|
268
|
+
They must be created first (`s3vectors CreateVectorBucket` + `CreateIndex`) and the resulting
|
|
269
|
+
index ARN passed into `CreateKnowledgeBase`. `createVectorStore()` handles this before KB
|
|
270
|
+
creation.
|
|
271
|
+
- **`s3vectors` is a separate AWS service** — it is **not** covered by `AmazonBedrockFullAccess`.
|
|
272
|
+
The app credentials need explicit `s3vectors:*` grants (see IAM prerequisite above); this was
|
|
273
|
+
the real missing permission.
|
|
274
|
+
- **KB create has no rollback → orphaned KB risk.** A failure mid-flow can leave an orphaned
|
|
275
|
+
S3 Vectors bucket/index and/or Bedrock KB with **no DB rows** (`Team.KnowledgeBases`/
|
|
276
|
+
`VectorIndexes`/Postgres never inserted). Blindly re-running with the same slug hits
|
|
277
|
+
`Conflict`/`AlreadyExists` on the vector bucket (surfaced as an actionable error) or creates a
|
|
278
|
+
**duplicate** Bedrock KB (Bedrock allows same-name KBs with new ids). After a failed create:
|
|
279
|
+
delete the orphaned vector store + KB and re-run clean, or finish the DB rows manually.
|
|
280
|
+
- **Two prior KBs use the WRONG (fully-MANAGED) type.** `development-team-craftex` and
|
|
281
|
+
`development-team-toga-desk` were created by the earlier MANAGED code and were **intentionally
|
|
282
|
+
left as-is** (this session's scope was code-only). They should be **recreated later** to match
|
|
283
|
+
the canonical VECTOR + S3 Vectors design (dunn).
|
|
217
284
|
- **KB-list source differs per page.** The new `/talos/knowledge-bases` page lists from
|
|
218
285
|
`Team.KnowledgeBases` (`db_team`); `/talos/kb-documents` lists from live S3 folders. Don't
|
|
219
286
|
assume one reflects the other.
|
|
@@ -222,15 +289,27 @@ orphaned KB).
|
|
|
222
289
|
`Client_True.VectorIndexes` (Client cluster): `id, uuid`(UNIQUE)`, name, docSlug`(UNIQUE,
|
|
223
290
|
nullable)`, sectionSlug, chunkSlug, description` — create sets `docSlug=sectionSlug=chunkSlug=
|
|
224
291
|
bedrockKbId`.
|
|
225
|
-
- **
|
|
226
|
-
and reviewed, but the create flow has not been exercised in prod pending the `pdo_pgsql`
|
|
227
|
-
deploy plus network/IAM prerequisites (the developer is handling those).
|
|
292
|
+
- **Verified working end-to-end in production (2026-07-16)** with the VECTOR + S3 Vectors design.
|
|
228
293
|
- No new secret literals introduced; credentials live in the config sections above (documented by
|
|
229
294
|
section/account, never by value). AWS account id `654654170868`, bucket `togaiq`, and the KB
|
|
230
295
|
execution-role ARN are non-secret resource identifiers.
|
|
231
296
|
|
|
232
297
|
## Change history
|
|
233
298
|
|
|
299
|
+
- 2026-07-16 — **Corrected the KB-create architecture from MANAGED to VECTOR + S3 Vectors**
|
|
300
|
+
(the canonical design, from `development-team-dunn`; the earlier code was modeled on the WRONG
|
|
301
|
+
`craftex`/MANAGED KB). Rewrote `App_Talos_Bedrock`: KB is now `VECTOR` + `S3_VECTORS` storage;
|
|
302
|
+
added `s3VectorsClient()`, `vectorBucketName()`, and `createVectorStore()` (create vector
|
|
303
|
+
bucket + index sized to Titan v2, poll `getIndex` for the ARN); `createKnowledgeBase()` now
|
|
304
|
+
takes the index ARN; `createDataSource()` rewritten to plain `S3` + `BEDROCK_DATA_AUTOMATION`;
|
|
305
|
+
removed dead `BUCKET_NAME`/`BUCKET_OWNER_ACCOUNT_ID`. Wired a new **S3 Vectors provisioning
|
|
306
|
+
step** into `post.php` before KB creation (flow is now 9 steps) and added
|
|
307
|
+
`set_time_limit(CREATE_MAX_EXECUTION_SECONDS=600)`. **Decided on ONE shared IAM execution role**
|
|
308
|
+
(`…ForKnowledgeBase-talos`, hard-coded `ROLE_ARN`) instead of per-KB roles, to avoid granting
|
|
309
|
+
the app IAM write powers; granted the `[talos]` user (`bedrock-model-accessor`) the missing
|
|
310
|
+
`s3vectors` + `iam:PassRole` permissions (`s3vectors` is NOT in `AmazonBedrockFullAccess`).
|
|
311
|
+
**Verified working end-to-end in production.** `craftex` and `toga-desk` remain on the old
|
|
312
|
+
MANAGED type (left as-is; recreate later). (jcardinal)
|
|
234
313
|
- 2026-07-13 — Added the **`/talos/knowledge-bases` list + inline-rename page** (`get.php`;
|
|
235
314
|
lists from `Team.KnowledgeBases`, rename-only, no delete, existence-check before update),
|
|
236
315
|
registered first in the `talos-kb` nav group. Added the **`App_Pg`** tools-only PDO `pgsql`
|
package/knowledge/INDEX.md
CHANGED
|
@@ -17,7 +17,7 @@ _Auto-generated by `knowledge.js index`. Do not hand-edit._
|
|
|
17
17
|
|
|
18
18
|
## 2.0 framework
|
|
19
19
|
|
|
20
|
-
- **_underscore** (_Underscore) _(framework core)_ —
|
|
20
|
+
- **_underscore** (_Underscore) _(framework core)_ — 33 doc(s) → [2.0/apps/_underscore/INDEX.md](2.0/apps/_underscore/INDEX.md)
|
|
21
21
|
- **worker2** (Worker) — 30 doc(s) → [2.0/apps/worker2/INDEX.md](2.0/apps/worker2/INDEX.md)
|
|
22
22
|
- **api2** (API) — 11 doc(s) → [2.0/apps/api2/INDEX.md](2.0/apps/api2/INDEX.md)
|
|
23
23
|
- **dbchanges2** (Database Changes) _(framework core)_ — 3 doc(s) → [2.0/apps/dbchanges2/INDEX.md](2.0/apps/dbchanges2/INDEX.md)
|
|
@@ -14,11 +14,12 @@ project: _Underscore
|
|
|
14
14
|
client: compass-canada
|
|
15
15
|
type: profile
|
|
16
16
|
status: active
|
|
17
|
-
updated: 2026-
|
|
17
|
+
updated: 2026-07-16
|
|
18
18
|
owners: [jcardinal, bala, tcox, apeterson]
|
|
19
19
|
files: []
|
|
20
20
|
related:
|
|
21
21
|
- ../compass-usa/profile.md
|
|
22
|
+
- ../compass-usa/features/approval-decision-flow.md
|
|
22
23
|
- ../../2.0/apps/toga2-commerce/features/expedited-shipping-gating.md
|
|
23
24
|
- ../../2.0/apps/_underscore/features/item-fulfillment-stage-lifecycle-and-order-status.md
|
|
24
25
|
- ../../2.0/apps/_underscore/features/surface-resolver.md
|
|
@@ -59,6 +60,12 @@ to but distinct from Compass USA. Like Compass USA it spans the **2.0** commerce
|
|
|
59
60
|
Compass USA's 1/3/4. See [Surface Resolver](../../2.0/apps/_underscore/features/surface-resolver.md).
|
|
60
61
|
- Customer language preference: `UserGlobalSettings.settingId = 2` (`en` / `fr-CA`); customer-
|
|
61
62
|
facing emails are sent in EN or FR accordingly.
|
|
63
|
+
- **Approval decisions use the shared Compass parent.** `_Model_Compass_Canada_ApprovalDecision`
|
|
64
|
+
is an empty subclass of `_Model_Compass_ApprovalDecision`; the entire approval/notification/
|
|
65
|
+
manager-reassignment/VIP-auto-approve flow lives in the parent and branches at runtime on
|
|
66
|
+
`clientIdentifier === 'Compass_Canada'` (email-template UUID + EN/FR localization). Fixes to the
|
|
67
|
+
parent cover Canada automatically — no per-tenant change. See
|
|
68
|
+
[Approval-Decision Flow](../compass-usa/features/approval-decision-flow.md).
|
|
62
69
|
- Assortment (product-grouping) names are served in fr-CA via the `AssortmentTranslations` sidecar
|
|
63
70
|
(16 rows seeded). French was extracted from the old bilingual `"English/French"` `Assortments.name`
|
|
64
71
|
values, which were then cleaned to English-only. See
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
| Doc | Framework | Summary | Files |
|
|
4
4
|
|-----|-----------|---------|-------|
|
|
5
|
+
| [Compass Approval-Decision Flow (Notifications & Manager Reassignment)](features/approval-decision-flow.md) | 2.0 | Compass's sales-order approval flow — approval/notification email lists, **manager reassignment**, VIP auto-approve, and EN/FR localization — lives **entirely i | _underscore/Model/Compass/ApprovalDecision.php, _underscore/Model/Compass/Usa/ApprovalDecision.php, _underscore/Model/Compass/Canada/ApprovalDecision.php |
|
|
5
6
|
| [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, _underscore/Model/Compass/PurchaseOrder.php, api2/Component/Api/Cxml/Cxml.php, dbchanges2/Client_Compass/2026-06-11 - AsnItemTrackingNumberAcl.sql, dbchanges2/Client_Compass/2026-06-15b - BackfillSA132781ItemFulfillmentTracking.sql, dbchanges2/Client_Compass/2026-06-16 - CleanupSA132763CrossLineTracking.sql, dbchanges2/Client_Compass/2026-06-16b - CleanupSA132743CrossLineTracking.sql, dbchanges2/Client_Compass/2026-06-16c - BackfillSA132763C40QYUCTracking.sql, dbchanges2/Client_Compass/2026-06-18a - CleanupSA132898DuplicateTracking.sql, dbchanges2/Client_Compass/2026-06-18b - CleanupSA132881DuplicateTracking.sql, dbchanges2/Client_Compass/2026-07-02a - FixSA133377TrackingSerialAndDuplicateIF.sql |
|
|
6
7
|
| [Cost Centers — Unit Locations, numeric-only policy](features/cost-centers.md) | 2.0 | A Compass "cost center" — the value a user picks in commerce and that lands on an order — is **not** a `CostCenters` row. | toga2-commerce/src/pages/Cart/api/CartApi.ts, worker1.5/crons/toga2/compass/import_locations.php, _underscore/Model/Compass/SalesOrder.php, api2/Component/Api/V2/V2.php, dbchanges2/Client_Compass/2026-07-06 - RemoveNonNumericCostCenters.sql |
|
|
7
8
|
| [Compass: Item-Fulfillment TableViews (for-sales-order-items & for-sales-orders, tracking via bridge)](features/item-fulfillment-tracking-tableview.md) | 2.0 | Two sibling Compass TableViews in `Client_Compass` display fulfilled items in toga2-supply, both driven by `TableViews` / `TableViewJoins` / `TableViewFields` c | dbchanges2/Client_Compass/2026-06-10 - ItemFulfillmentsForSalesOrderItemsTableView.sql, dbchanges2/Client_Compass/2026-06-11 - ItemFulfillmentsForSalesOrdersTableView.sql, dbchanges2/Client_Compass/2026-06-15a - FixItemFulfillmentTrackingNumberJoins.sql, dbchanges2/Client/2026-07-15a - ExcludeFeeItemsFromItemFulfillmentsForSalesOrdersView.sql |
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Compass Approval-Decision Flow (Notifications & Manager Reassignment)
|
|
3
|
+
framework: "2.0"
|
|
4
|
+
repo: _underscore
|
|
5
|
+
project: _Underscore
|
|
6
|
+
client: compass-usa
|
|
7
|
+
type: client-feature
|
|
8
|
+
status: active
|
|
9
|
+
updated: 2026-07-16
|
|
10
|
+
owners: ["apeterson"]
|
|
11
|
+
files:
|
|
12
|
+
- _underscore/Model/Compass/ApprovalDecision.php
|
|
13
|
+
- _underscore/Model/Compass/Usa/ApprovalDecision.php
|
|
14
|
+
- _underscore/Model/Compass/Canada/ApprovalDecision.php
|
|
15
|
+
related:
|
|
16
|
+
- mr-ma-order-approval-and-status.md
|
|
17
|
+
- ../../compass-canada/profile.md
|
|
18
|
+
- ../../../2.0/apps/_underscore/features/email-template-sending.md
|
|
19
|
+
---
|
|
20
|
+
|
|
21
|
+
## Summary
|
|
22
|
+
Compass's sales-order approval flow — approval/notification email lists, **manager reassignment**,
|
|
23
|
+
VIP auto-approve, and EN/FR localization — lives **entirely in the shared parent
|
|
24
|
+
`_Model_Compass_ApprovalDecision`**. Both tenant subclasses,
|
|
25
|
+
`_Model_Compass_Usa_ApprovalDecision` and `_Model_Compass_Canada_ApprovalDecision`, are **empty**
|
|
26
|
+
(`extends _Model_Compass_ApprovalDecision {}`, no overrides). Client- and language-specific
|
|
27
|
+
behavior is **not** subclassed; it branches at runtime inside the parent on
|
|
28
|
+
`$api->client->clientIdentifier` (`Compass_Usa` vs `Compass_Canada`). Practical consequence: a fix
|
|
29
|
+
to a parent method applies to **both** Compass US and Compass Canada automatically — there is no
|
|
30
|
+
per-client code change to make.
|
|
31
|
+
|
|
32
|
+
This doc covers the **approval-decision / notification** half of Compass approvals. The separate
|
|
33
|
+
auto-approval + `_status` gating on the order itself is in
|
|
34
|
+
[Compass MR/MA Order Auto-Approval & Status Gate](mr-ma-order-approval-and-status.md).
|
|
35
|
+
|
|
36
|
+
## Key files / entry points
|
|
37
|
+
- **`_underscore/Model/Compass/ApprovalDecision.php`** — the shared parent that owns the whole
|
|
38
|
+
flow (notification-list maintenance, `_swapManagerEmailAddress()`, VIP auto-approve, email-template
|
|
39
|
+
UUID resolution, localization).
|
|
40
|
+
- **`_underscore/Model/Compass/Usa/ApprovalDecision.php`** — empty subclass, no overrides.
|
|
41
|
+
- **`_underscore/Model/Compass/Canada/ApprovalDecision.php`** — empty subclass, no overrides.
|
|
42
|
+
|
|
43
|
+
## How it works
|
|
44
|
+
### One shared parent, runtime client branch (not subclass overrides)
|
|
45
|
+
Compass US and Compass Canada resolve to different model classes at request time
|
|
46
|
+
(`_Model_Compass_Usa_ApprovalDecision` / `_Model_Compass_Canada_ApprovalDecision`), but both are
|
|
47
|
+
empty shells extending `_Model_Compass_ApprovalDecision`. All behavior is inherited. Where the two
|
|
48
|
+
tenants differ, the **parent** branches on `$api->client->clientIdentifier === 'Compass_Canada'`:
|
|
49
|
+
- **Email-template resolution** — `resolveEmailTemplateUuid()` picks the tenant's template UUID.
|
|
50
|
+
- **EN/FR localization** — user language is read from `UserGlobalSettings` (`settingId = 2`;
|
|
51
|
+
`en` / `fr-CA`) so Canadian notifications go out in the recipient's language.
|
|
52
|
+
|
|
53
|
+
Because the divergence is a runtime branch and not an override, do **not** add per-client logic to
|
|
54
|
+
the empty subclasses — extend the shared parent and branch there if a genuine tenant difference is
|
|
55
|
+
needed.
|
|
56
|
+
|
|
57
|
+
### Manager reassignment and the notification list
|
|
58
|
+
When a step-2 (Manager) approval decision is **reassigned** to a new manager,
|
|
59
|
+
`_swapManagerEmailAddress()` updates the order's CC/notification list in `SalesOrderEmailAddresses`:
|
|
60
|
+
it removes the outgoing manager's email and adds the incoming manager's. The one email that must
|
|
61
|
+
**never** be dropped is the **order requester's** — the person the order is for, resolved via
|
|
62
|
+
`SalesOrders.contactId → Users` (the requester's email is looked up with a parameterized
|
|
63
|
+
`SELECT Users.email FROM SalesOrders INNER JOIN Users ON Users.contactId = SalesOrders.contactId
|
|
64
|
+
WHERE SalesOrders.id = ?`). Before deleting the old-manager email, the method compares it against
|
|
65
|
+
both the new-manager email and the requester email and **skips the delete when it matches the
|
|
66
|
+
requester**. All of these comparisons are case-insensitive (`strcasecmp()`), matching
|
|
67
|
+
`getFilteredCcEmails()`.
|
|
68
|
+
|
|
69
|
+
## Gotchas / known issues
|
|
70
|
+
- **Never drop the order requester from notifications on manager reassignment.** If the outgoing
|
|
71
|
+
manager happens to also be the order requester (same email), deleting the old-manager address
|
|
72
|
+
from `SalesOrderEmailAddresses` would silently remove the requester from all order notifications.
|
|
73
|
+
`_swapManagerEmailAddress()` guards against this by comparing the old-manager email to the
|
|
74
|
+
requester's email (looked up via `SalesOrders.contactId → Users`) and skipping the delete when
|
|
75
|
+
they match. This was a live bug (fixed 2026-07-16).
|
|
76
|
+
- **Email comparisons must be case-insensitive.** Use `strcasecmp()`, not `!==` — the notification
|
|
77
|
+
list can hold the same address in different casing, and `getFilteredCcEmails()` already compares
|
|
78
|
+
case-insensitively. A strict `!==` comparison here previously let a casing mismatch defeat the
|
|
79
|
+
requester/new-manager guards.
|
|
80
|
+
- **Don't put tenant behavior in the empty subclasses.** `Usa`/`Canada` `ApprovalDecision` are
|
|
81
|
+
intentionally empty; the tenant branch lives in the parent on `clientIdentifier`.
|
|
82
|
+
|
|
83
|
+
## Change history
|
|
84
|
+
- 2026-07-16 — Fixed `_swapManagerEmailAddress()` dropping the order requester from the order's
|
|
85
|
+
notification list (`SalesOrderEmailAddresses`) when a step-2 manager was reassigned and the
|
|
86
|
+
outgoing manager's email also belonged to the requester: now looks up the requester email via
|
|
87
|
+
`SalesOrders.contactId → Users` and skips the delete when they match; also switched the
|
|
88
|
+
old-vs-new and old-vs-requester comparisons to case-insensitive `strcasecmp()` (was `!==`) to
|
|
89
|
+
match `getFilteredCcEmails()`. Documented that the flow is the shared parent
|
|
90
|
+
`_Model_Compass_ApprovalDecision` (empty `Usa`/`Canada` subclasses, runtime `clientIdentifier`
|
|
91
|
+
branch), so the fix covers Compass Canada automatically. Shipped on branch TRUE-80244 (pushed;
|
|
92
|
+
not yet merged to `_beta`). (apeterson)
|
|
93
|
+
|
|
94
|
+
## Related docs
|
|
95
|
+
- [Compass MR/MA Order Auto-Approval & Status Gate](mr-ma-order-approval-and-status.md) — the
|
|
96
|
+
auto-approval + `_status` gating half of Compass approvals (on `_Model_Compass_SalesOrder`).
|
|
97
|
+
- [Compass Canada](../../compass-canada/profile.md) — uses this identical flow; the runtime
|
|
98
|
+
`Compass_Canada` branch drives its EN/FR notification localization.
|
|
99
|
+
- [Email Template Sending](../../../2.0/apps/_underscore/features/email-template-sending.md) — the
|
|
100
|
+
shared engine the resolved approval-notification templates are sent through.
|
|
@@ -15,12 +15,13 @@ project: _Underscore
|
|
|
15
15
|
client: compass-usa
|
|
16
16
|
type: profile
|
|
17
17
|
status: active
|
|
18
|
-
updated: 2026-07-
|
|
18
|
+
updated: 2026-07-16
|
|
19
19
|
owners: [jcardinal, bala, tcox, apeterson]
|
|
20
20
|
files: []
|
|
21
21
|
related:
|
|
22
22
|
- features/asn-to-item-fulfillment.md
|
|
23
23
|
- features/cost-centers.md
|
|
24
|
+
- features/approval-decision-flow.md
|
|
24
25
|
- workflows/cross-kit-bundle-corruption.md
|
|
25
26
|
- ../../2.0/apps/worker2/features/compass-vip-support-importer.md
|
|
26
27
|
- ../../2.0/apps/toga2-commerce/features/expedited-shipping-gating.md
|
|
@@ -75,6 +76,10 @@ separate, related client (see its own profile).
|
|
|
75
76
|
- [VIP Support Importer (worker2)](../../2.0/apps/worker2/features/compass-vip-support-importer.md)
|
|
76
77
|
— manual (Postman) worker2 action that reads Compass's quarterly VIP spreadsheet and sets
|
|
77
78
|
`Users.c_supportedByUserId` (assigned support tech) per VIP.
|
|
79
|
+
- [Approval-Decision Flow (Notifications & Manager Reassignment)](features/approval-decision-flow.md) —
|
|
80
|
+
the shared `_Model_Compass_ApprovalDecision` parent (empty `Usa`/`Canada` subclasses; runtime
|
|
81
|
+
`clientIdentifier` branch) that owns approval notifications, manager reassignment, VIP
|
|
82
|
+
auto-approve, and EN/FR localization.
|
|
78
83
|
|
|
79
84
|
## Notes
|
|
80
85
|
- **Order status is shipped-only (2026-06-30).** Compass imports all IF stages
|
package/package.json
CHANGED