toga-ai 1.0.815 → 1.0.817
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/store/INDEX.md +6 -0
- package/knowledge/1.0/apps/store/architecture.md +72 -0
- package/knowledge/1.0/apps/store/features/saml-sso-certificate-and-keys.md +146 -0
- package/knowledge/2.0/apps/toga25-supply/features/client-configurable-fields.md +20 -2
- package/knowledge/2.0/apps/toga25-supply/workflows/amplify-deployment.md +25 -2
- package/knowledge/2.0/apps/toga25-supply/workflows/cypress-testing.md +148 -22
- package/knowledge/INDEX.md +1 -0
- package/knowledge/clients/prudential/INDEX.md +1 -0
- package/knowledge/clients/prudential/profile.md +13 -0
- package/knowledge/clients/prudential/workflows/store-saml-deploy-topology.md +101 -0
- package/knowledge/clients/rumcsi/profile.md +10 -2
- package/knowledge/registry.json +7 -0
- package/package.json +1 -1
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
# store (Store) — 1.0 knowledge
|
|
2
|
+
|
|
3
|
+
| Doc | Summary |
|
|
4
|
+
|-----|---------|
|
|
5
|
+
| [Store (1.0 Storefront / SAML ACS) Architecture](architecture.md) | `store` is a framework **1.0** (`App_`) storefront app. |
|
|
6
|
+
| [Store SAML SP Certificate & Private Keys (togahub.com)](features/saml-sso-certificate-and-keys.md) | How the `store` repo's SAML Service Provider credential — the `togahub.com` X.509 cert and its RSA private key — is stored, loaded and rotated. |
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Store (1.0 Storefront / SAML ACS) Architecture
|
|
3
|
+
framework: "1.0"
|
|
4
|
+
repo: store
|
|
5
|
+
project: Store
|
|
6
|
+
client: shared
|
|
7
|
+
type: architecture
|
|
8
|
+
status: active
|
|
9
|
+
updated: 2026-09-15
|
|
10
|
+
owners: ["rgirish"]
|
|
11
|
+
files:
|
|
12
|
+
- store/_/app/framework.php
|
|
13
|
+
- store/config.production.ini
|
|
14
|
+
related:
|
|
15
|
+
- ../library/architecture.md
|
|
16
|
+
- ./features/saml-sso-certificate-and-keys.md
|
|
17
|
+
- ../../../clients/prudential/workflows/store-saml-deploy-topology.md
|
|
18
|
+
---
|
|
19
|
+
|
|
20
|
+
## Summary
|
|
21
|
+
|
|
22
|
+
`store` is a framework **1.0** (`App_`) storefront app. It also hosts the **SAML Assertion
|
|
23
|
+
Consumer Service (ACS)** for several clients on the `*.togahub.com` hostnames — the inbound
|
|
24
|
+
IdP assertion is decrypted **here**, not in the 2.0 `saml` gateway. SAML lives inline in
|
|
25
|
+
`store/_/app/framework.php` as a large `switch ($returnTo)` on the RelayState value, with one
|
|
26
|
+
inline `$settingsInfo` block per client and a `case 'meta' / case 'acs'` pair inside each.
|
|
27
|
+
|
|
28
|
+
The SP cert/key material lives in the sibling `library` repo under `library/ssl/toga/` and is
|
|
29
|
+
read from disk at request time via `__LIBROOT__`.
|
|
30
|
+
|
|
31
|
+
**Critical rules:** never paste private-key material into `framework.php` — read it from
|
|
32
|
+
`library/ssl/toga/*.key` with `file_get_contents`. The SAML ACS host and the storefront host for
|
|
33
|
+
a client can be **different EB environments**; check DNS before assuming a deploy target
|
|
34
|
+
(see the Prudential store SAML deploy-topology workflow). Certificate and private key must be
|
|
35
|
+
rotated **together** — the three inline SP blocks each carry an `x509cert` that has to match the
|
|
36
|
+
key file.
|
|
37
|
+
|
|
38
|
+
## SAML entry points
|
|
39
|
+
|
|
40
|
+
Routing is by the RelayState value (`$_POST['RelayState']`, read as `$returnTo`), not by path:
|
|
41
|
+
|
|
42
|
+
| Sub-route | Purpose |
|
|
43
|
+
|---|---|
|
|
44
|
+
| `?meta` | SP metadata XML from `OneLogin_Saml2_Settings::getSPMetadata()` |
|
|
45
|
+
| `?acs` | Assertion Consumer Service — decrypts the inbound `SAMLResponse` |
|
|
46
|
+
|
|
47
|
+
Two SAML stacks are in play in the same file:
|
|
48
|
+
- **php-saml (`OneLogin_Saml2_*`)** — builds the SP settings + `?meta` metadata.
|
|
49
|
+
- **LightSAML + xmlseclibs** — does the actual `?acs` assertion decryption
|
|
50
|
+
(`decryptMultiAssertion`). See the SAML SP certificate & keys feature doc.
|
|
51
|
+
|
|
52
|
+
The RUMCSI branch (`rumcsi.togaview.agilantsolutions.com`) skips decryption entirely and parses
|
|
53
|
+
the raw assertion XML with `SimpleXMLElement` + XPath.
|
|
54
|
+
|
|
55
|
+
## Handoff to the storefront
|
|
56
|
+
|
|
57
|
+
After a successful assertion, the Prudential-style branches base64-encode a plain
|
|
58
|
+
`xid:first:last:email` string and redirect to `<returnTo>?hash=<base64>`. This is **not
|
|
59
|
+
encrypted and not signed** — unlike the 2.0 gateway's `?saml=` encrypted + uuid-validated
|
|
60
|
+
handoff. See the "Client_True migration (deferred)" section of the SAML SP certificate & keys
|
|
61
|
+
feature doc.
|
|
62
|
+
|
|
63
|
+
## Known issues
|
|
64
|
+
|
|
65
|
+
- `case 'meta'` sets the `Content-Type` header and `exit`s **without ever echoing
|
|
66
|
+
`$metadata`** — the `?meta` endpoint returns an empty body. Present in both `meta` blocks.
|
|
67
|
+
- `'debug' => true` is set on all three SAML `$settingsInfo` blocks, and a `debugVar()` call
|
|
68
|
+
dumps SAML attributes (XID, name, email) to output.
|
|
69
|
+
- `'strict'` is never set, so php-saml defaults it to `false` and skips condition, timestamp and
|
|
70
|
+
audience validation. Neither `wantAssertionsSigned` nor `wantMessagesSigned` is set.
|
|
71
|
+
- Prudential's SAML config is ~700 lines of inline `switch` in one file with no
|
|
72
|
+
`_Model_<Client>_ClientAuthentication` equivalent.
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Store SAML SP Certificate & Private Keys (togahub.com)
|
|
3
|
+
framework: "1.0"
|
|
4
|
+
repo: store
|
|
5
|
+
project: Store
|
|
6
|
+
client: shared
|
|
7
|
+
type: feature
|
|
8
|
+
status: active
|
|
9
|
+
updated: 2026-09-15
|
|
10
|
+
owners: ["rgirish"]
|
|
11
|
+
files:
|
|
12
|
+
- store/_/app/framework.php
|
|
13
|
+
- library/ssl/toga/togahub_com_2027-03-31.crt
|
|
14
|
+
- library/ssl/toga/toga_private_key_2027-03-31.key
|
|
15
|
+
related:
|
|
16
|
+
- ../architecture.md
|
|
17
|
+
- ../../library/features/app-sso-initiation.md
|
|
18
|
+
- ../../../2.0/apps/saml/workflows/rotating-the-saml-sp-certificate.md
|
|
19
|
+
- ../../../2.0/standards/ssl-certificate-trust-model.md
|
|
20
|
+
- ../../../clients/prudential/workflows/store-saml-deploy-topology.md
|
|
21
|
+
---
|
|
22
|
+
|
|
23
|
+
## Summary
|
|
24
|
+
|
|
25
|
+
How the `store` repo's SAML Service Provider credential — the `togahub.com` X.509 cert and its
|
|
26
|
+
RSA private key — is stored, loaded and rotated. `store` decrypts inbound IdP assertions itself
|
|
27
|
+
(LightSAML), so this credential is what makes Prudential/RUMCSI SSO work. It is **separate from**
|
|
28
|
+
the 2.0 `saml` gateway's SP credential — rotating one does not rotate the other. (As of
|
|
29
|
+
2026-09-15 the gateway's `/acs` loads its decrypt certs from AWS Secrets Manager, not from disk;
|
|
30
|
+
`store` still reads files. See the SP cert-rotation workflow.)
|
|
31
|
+
|
|
32
|
+
The cert and key files live in the sibling `library` repo at `library/ssl/toga/` and are read at
|
|
33
|
+
request time through `__LIBROOT__`.
|
|
34
|
+
|
|
35
|
+
## How it works
|
|
36
|
+
|
|
37
|
+
### Where the material lives (never paste values)
|
|
38
|
+
|
|
39
|
+
- **AWS Secrets Manager**, account **654654170868**, region **us-east-1**.
|
|
40
|
+
- Secret: **`toga/ssl/togahub_com-casigned-2027-03-31`**.
|
|
41
|
+
- JSON keys: `certificate`, `privateKey`, `certificateChain`, `passphrase`.
|
|
42
|
+
- On disk (deployed with the `library` repo):
|
|
43
|
+
- `library/ssl/toga/togahub_com_2027-03-31.crt` — active cert
|
|
44
|
+
- `library/ssl/toga/togahub_com_2027-03-31-chain.crt` — chain
|
|
45
|
+
- `library/ssl/toga/toga_private_key_2027-03-31.key` — active key, **stored decrypted**, mode `600`
|
|
46
|
+
- `library/ssl/toga/togahub_com_2026-02-27.crt` + `toga_private_key.key` — previous pair,
|
|
47
|
+
kept only as the rotation fallback
|
|
48
|
+
|
|
49
|
+
### Current certificate (rotated 2026-09-15)
|
|
50
|
+
|
|
51
|
+
| | Old | New |
|
|
52
|
+
|---|---|---|
|
|
53
|
+
| File | `togahub_com_2026-02-27.crt` | `togahub_com_2027-03-31.crt` |
|
|
54
|
+
| Issuer | DigiCert / Thawte | Amazon (ACM, `Amazon RSA 2048 M01`) |
|
|
55
|
+
| Validity | expired **2026-09-16** | **2026-09-15 → 2027-03-31** |
|
|
56
|
+
| SANs | 14 | **2** — `togahub.com`, `*.togahub.com` |
|
|
57
|
+
|
|
58
|
+
**The new cert covers only `togahub.com`.** The six other domains the old cert carried
|
|
59
|
+
(`togacommerce`, `togadesk`, `togaview`, `togasupply`, `togaretail`, `togaiq`, plus their
|
|
60
|
+
wildcards) are **not** on it — each of those needs its own ACM cert on its own listener.
|
|
61
|
+
|
|
62
|
+
### Dual-keypair decrypt (rotation overlap)
|
|
63
|
+
|
|
64
|
+
`case 'acs'` in `store/_/app/framework.php` builds a `$samlKeyPairs` array — **active keypair
|
|
65
|
+
first, previous keypair second** — then one `LightSaml\Credential\X509Credential` per readable
|
|
66
|
+
pair, and passes the whole list to
|
|
67
|
+
`$reader->decryptMultiAssertion($credentials, $decryptDeserializeContext)`.
|
|
68
|
+
|
|
69
|
+
LightSAML's `decryptMulti()` loops the credentials, swallows each individual failure and returns
|
|
70
|
+
on the first success, re-throwing only when all fail. So **no custom retry logic is needed** and
|
|
71
|
+
it fails closed. An IdP still encrypting to the old cert and one already on the new cert both
|
|
72
|
+
work during the overlap. Unreadable keypairs are skipped with `error_log`; an empty credential
|
|
73
|
+
list throws `RuntimeException`.
|
|
74
|
+
|
|
75
|
+
This mirrors the overlap the 2.0 `saml` gateway provides, though the gateway now does it with
|
|
76
|
+
a multi-cert list out of Secrets Manager rather than two files on disk — see the SP
|
|
77
|
+
cert-rotation workflow.
|
|
78
|
+
|
|
79
|
+
### Private key loading
|
|
80
|
+
|
|
81
|
+
The three inline `$settingsInfo` SP blocks read the key from disk:
|
|
82
|
+
|
|
83
|
+
```php
|
|
84
|
+
'privateKey' => trim(file_get_contents(__LIBROOT__ . '/ssl/toga/toga_private_key_2027-03-31.key')),
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
The key file must be **decrypted on disk** because `framework.php` calls
|
|
88
|
+
`KeyHelper::createPrivateKey($path, '', true)` with an empty password — an encrypted PEM will not
|
|
89
|
+
load.
|
|
90
|
+
|
|
91
|
+
**Deploy model:** the `ssl/**/*.key` ignore rule was deliberately removed from
|
|
92
|
+
`library/.gitignore` so the key ships with the repo. That is the team's current model for this
|
|
93
|
+
key; it is a record of what we do, not a recommendation.
|
|
94
|
+
|
|
95
|
+
## Gotchas
|
|
96
|
+
|
|
97
|
+
- **PEM newlines are stored as SPACES in Secrets Manager.** The `certificate` / `privateKey`
|
|
98
|
+
values in `toga/ssl/togahub_com-casigned-2027-03-31` have their line breaks replaced by spaces.
|
|
99
|
+
`openssl` rejects the value straight out of the secret. Re-wrap the base64 body at 64 chars
|
|
100
|
+
before writing it to disk.
|
|
101
|
+
- **The date in a secret name is the EXPIRY date, not the issue date.** The secret
|
|
102
|
+
`toga/ssl/togahub_com-casigned-2026-09-16` is **not** a newer cert — it holds the cert that was
|
|
103
|
+
already expiring. Read the dates out of the cert itself, never off the secret name.
|
|
104
|
+
- **Rotate the cert and the key together.** Before 2026-09-15 the three SP blocks had the raw
|
|
105
|
+
private key pasted inline as a base64 string, and the `x509cert` values had been rotated while
|
|
106
|
+
those inline keys had not — cert and key no longer matched, which would have broken SSO on the
|
|
107
|
+
next deploy. Keys are now read from a file, so they cannot drift apart silently.
|
|
108
|
+
- **Don't assume where the ACS runs.** The host that terminates `?acs` for a client may be a
|
|
109
|
+
different EB environment from the storefront. See the Prudential store SAML deploy-topology
|
|
110
|
+
workflow before deploying a cert change.
|
|
111
|
+
|
|
112
|
+
## Client_True migration (deferred 2026-09-15)
|
|
113
|
+
|
|
114
|
+
Moving Prudential SAML onto the `Client_True` pattern (config `[saml]` section +
|
|
115
|
+
`Core.ClientAuthentications` / `ClientAuthentications_Sso` rows + `App_Sso` + a
|
|
116
|
+
`_Model_<Client>_ClientAuthentication`) was reviewed and **deferred to its own ticket** — the cert
|
|
117
|
+
expired the next day and the migration is multi-day work.
|
|
118
|
+
|
|
119
|
+
Reference implementation to copy when it is picked up: `tools/config.production.ini` `[saml]`
|
|
120
|
+
section, `library/app/sso.php` (`App_Sso::initiate`), `tools/mvc/sso/initiate/get.php`,
|
|
121
|
+
`tools/mvc/sso/get.php`.
|
|
122
|
+
|
|
123
|
+
What the migration must handle:
|
|
124
|
+
- Prudential today has ~700 lines of inline `switch` keyed on `$returnTo`, with certs pasted into
|
|
125
|
+
PHP, and no `_Model_Prudential_ClientAuthentication`.
|
|
126
|
+
- Its handoff is a plain-base64 `?hash=` of `xid:first:last:email` — unencrypted and forgeable —
|
|
127
|
+
versus the encrypted, uuid-validated `?saml=` handoff `Client_True` uses.
|
|
128
|
+
- Prudential **auto-creates users on first login**. `Client_True`'s `getAuthenticatedSsoUser()`
|
|
129
|
+
does not, so that behavior must be written in or SSO logins will start failing for new users.
|
|
130
|
+
|
|
131
|
+
## Open security items (not fixed 2026-09-15)
|
|
132
|
+
|
|
133
|
+
- The old private key is still in git history — `library` commits `6addc58f7` and `29d55bb97`,
|
|
134
|
+
plus the `store` `framework.php` history. Tracked as a separate compromised-credential ticket.
|
|
135
|
+
- `'debug' => true`, missing `'strict'`, and the attribute-dumping `debugVar()` — see the store
|
|
136
|
+
architecture doc's Known issues.
|
|
137
|
+
|
|
138
|
+
## Change history
|
|
139
|
+
- 2026-09-15 — Rotated the `togahub.com` SP cert to the ACM-issued `togahub_com_2027-03-31.crt`
|
|
140
|
+
and added a dual-keypair fallback in `?acs` so both the old and new certs decrypt during the
|
|
141
|
+
overlap (`decryptMultiAssertion`). Removed three hardcoded RSA private-key blobs from
|
|
142
|
+
`store/_/app/framework.php` and replaced them with `file_get_contents` on
|
|
143
|
+
`library/ssl/toga/toga_private_key_2027-03-31.key`; chmod 600 the old world-readable key.
|
|
144
|
+
Recorded the Secrets Manager location, the space-instead-of-newline PEM gotcha, and that the
|
|
145
|
+
new cert carries only 2 SANs. Deferred the `Client_True` SAML migration. Commit `3839e49a`.
|
|
146
|
+
(rgirish)
|
|
@@ -6,8 +6,8 @@ project: TOGa 2.5 Supply
|
|
|
6
6
|
client: shared
|
|
7
7
|
type: feature
|
|
8
8
|
status: active
|
|
9
|
-
updated: 2026-09-
|
|
10
|
-
owners: [apeterson]
|
|
9
|
+
updated: 2026-09-15
|
|
10
|
+
owners: [apeterson, tcox]
|
|
11
11
|
files:
|
|
12
12
|
- toga25-supply/src/fieldsConfig/index.ts
|
|
13
13
|
- toga25-supply/src/fieldsConfig/resolveRole.ts
|
|
@@ -196,11 +196,29 @@ modal's chrome from the same per-client JSON, not from the component:
|
|
|
196
196
|
the Enter-PO modal fields — its nested `valueKey`s are **not** action buttons. `DEFAULT/orderViewFields.json`
|
|
197
197
|
has no `recordActionFields` at all. Any tooling that walks action buttons must scope strictly to the
|
|
198
198
|
`actionOptions` array.
|
|
199
|
+
- **⚠ A client JSON file existing does NOT mean it is wired.** Verified 2026-09-15: the per-client
|
|
200
|
+
`vendorItemRecordCreateFields.json` files (COMPASS, COMPASSCANADA, NYCHH) are **not imported into
|
|
201
|
+
`fieldsConfig/index.ts`**, so `FIELDS` resolves `DEFAULT` for every client and edits to those
|
|
202
|
+
files change nothing. Adding the file is step 1 of five (see *Adding a new client-configurable
|
|
203
|
+
field*) — if your override has no effect, check the wiring before you debug the JSON.
|
|
204
|
+
- **A missing `header.modalTag` renders a BLANK badge, not no badge.** The modal chrome is fully
|
|
205
|
+
config-driven, so a client file that omits a block another client has just draws empty chrome.
|
|
206
|
+
Found on the create modal: COMPASSCANADA's and NYCHH's `itemRecordCreateFields.json` lacked the
|
|
207
|
+
`header.modalTag` block COMPASS had. **When you add a header/action key to one client's file, add
|
|
208
|
+
it to every client's file for that modal mode** — there is no per-key fallback to DEFAULT once a
|
|
209
|
+
client bundle exists.
|
|
199
210
|
- Worked examples: `salesOrdersPageFields` (client × role) and `inventoryGroupings` (client-only +
|
|
200
211
|
hydration; full write-up in `src/pages/Inventory/README.md`); `itemRecordViewFields` /
|
|
201
212
|
`vendorItemRecordViewFields` are record-modal-layout examples living under `src/layout/.../viewModel/FIELDS/`.
|
|
202
213
|
|
|
203
214
|
## Change history
|
|
215
|
+
- 2026-09-15 — Added the `header.modalTag` block to COMPASSCANADA's and NYCHH's
|
|
216
|
+
`itemRecordCreateFields.json` (both rendered a **blank** badge in the create modal because the
|
|
217
|
+
block was missing, not absent-by-design). Recorded two gotchas found while writing the create-modal
|
|
218
|
+
Cypress specs: a client JSON file can exist and be **unwired** (the per-client
|
|
219
|
+
`vendorItemRecordCreateFields.json` files are never imported, so `FIELDS` always uses DEFAULT), and
|
|
220
|
+
a header key added to one client's file must be added to every client's file for that modal mode.
|
|
221
|
+
(tcox)
|
|
204
222
|
- 2026-09-03 — Documented the actual `resolveRole` flag → role table (COMPASS/COMPASSCANADA:
|
|
205
223
|
`_isAdmin`/`_isSuperAdmin`/`_isSupervisor`, in that order, else `USER`; QUAD:
|
|
206
224
|
`_isGlobalAdmin`/`_isBuyer`/`_isItShopper`, else `USER`; every other client `DEFAULT`) and the
|
|
@@ -6,8 +6,8 @@ project: TOGa 2.5 Supply
|
|
|
6
6
|
client: shared
|
|
7
7
|
type: workflow
|
|
8
8
|
status: active
|
|
9
|
-
updated: 2026-
|
|
10
|
-
owners: [jcardinal, apeterson]
|
|
9
|
+
updated: 2026-09-15
|
|
10
|
+
owners: [jcardinal, apeterson, tcox]
|
|
11
11
|
files:
|
|
12
12
|
- toga25-supply/amplify.yml
|
|
13
13
|
- toga25-supply/src/api/api.ts
|
|
@@ -16,6 +16,7 @@ files:
|
|
|
16
16
|
- toga25-supply/package.json
|
|
17
17
|
- toga25-supply/.env.development
|
|
18
18
|
related:
|
|
19
|
+
- ./cypress-testing.md
|
|
19
20
|
- ../architecture.md
|
|
20
21
|
- ../../toga-blox/workflows/dynamic-publish-pipeline.md
|
|
21
22
|
- ../features/transfer-orders-page.md
|
|
@@ -70,6 +71,23 @@ production.
|
|
|
70
71
|
- Node: Vite 7 requires Node 20.19+ or 22.12+ (Node 22 recommended). Amplify's default image
|
|
71
72
|
is fine; optionally pin via `.nvmrc`.
|
|
72
73
|
|
|
74
|
+
### The Cypress test phase (added 2026-09-15, `_sandbox-client`)
|
|
75
|
+
|
|
76
|
+
`amplify.yml` now runs the full Cypress suite in the build. This is the app's **only** CI — there
|
|
77
|
+
is no GitHub Actions workflow. See [cypress-testing](./cypress-testing.md) for the suite itself.
|
|
78
|
+
|
|
79
|
+
- **`preTest`** — writes the four `*.togasupply` entries into `/etc/hosts` (the tenant is resolved
|
|
80
|
+
from the hostname, so without them every spec visits the wrong client), installs Cypress with
|
|
81
|
+
`CYPRESS_CACHE_FOLDER` set so the binary is cacheable, installs `mochawesome`, and serves the
|
|
82
|
+
built app with **`pm2` + `vite preview` on 5173**. `vite.config.ts` needs
|
|
83
|
+
`preview.allowedHosts: ['.togasupply']` or preview rejects the tenant hostnames.
|
|
84
|
+
- **`test`** — one Cypress run **per tenant** (one superdomain per run) plus the component project.
|
|
85
|
+
- **`postTest`** — `mochawesome-merge` combines the per-tenant reports into one.
|
|
86
|
+
- **`USER_DISABLE_TESTS`** gates the phase per branch, so a branch can deploy without the suite.
|
|
87
|
+
- `.gitignore` excludes `cypress/reports` and `.cypress-cache`.
|
|
88
|
+
|
|
89
|
+
A failing spec fails the deploy — same posture as the `npx tsc -b` step.
|
|
90
|
+
|
|
73
91
|
### Why the exact `--mode` matters at runtime
|
|
74
92
|
|
|
75
93
|
`import.meta.env.MODE` (the `--mode` value) is baked into the bundle and read at **runtime**
|
|
@@ -219,6 +237,11 @@ branch does NOT reconnect it**: pushes land in GitHub and Amplify ignores them e
|
|
|
219
237
|
has ever been developed against a linked local checkout.
|
|
220
238
|
|
|
221
239
|
## Change history
|
|
240
|
+
- 2026-09-15 — Documented the **Cypress test phase** added to `amplify.yml` on `_sandbox-client`
|
|
241
|
+
(`preTest` hosts entries + cached Cypress install + `pm2`/`vite preview` on 5173, one `test` run
|
|
242
|
+
per tenant plus the component project, `postTest` mochawesome merge, `USER_DISABLE_TESTS` gate),
|
|
243
|
+
the required `preview.allowedHosts: ['.togasupply']` in `vite.config.ts`, and the new `.gitignore`
|
|
244
|
+
entries. This build phase is the app's only CI. (tcox)
|
|
222
245
|
- 2026-08-31 — Restored the branch's deploy inputs and recorded three systemic findings.
|
|
223
246
|
(1) **`amplify.yml` was absent from the root of every remote branch** (it lived in a single commit
|
|
224
247
|
`5a276be`), so Amplify had been building from a **console-stored spec** and the fail-loud
|
|
@@ -6,36 +6,30 @@ project: TOGa 2.5 Supply
|
|
|
6
6
|
client: shared
|
|
7
7
|
type: workflow
|
|
8
8
|
status: active
|
|
9
|
-
updated: 2026-09-
|
|
10
|
-
owners: [apeterson]
|
|
9
|
+
updated: 2026-09-15
|
|
10
|
+
owners: [apeterson, tcox]
|
|
11
11
|
files:
|
|
12
12
|
- toga25-supply/cypress.config.ts
|
|
13
13
|
- toga25-supply/cypress/README.md
|
|
14
|
-
- toga25-supply/cypress/tsconfig.json
|
|
15
14
|
- toga25-supply/cypress/support/e2e.ts
|
|
16
|
-
- toga25-supply/cypress/support/commands.ts
|
|
17
|
-
- toga25-supply/cypress/support/component.tsx
|
|
18
15
|
- toga25-supply/cypress/support/tenants.ts
|
|
16
|
+
- toga25-supply/cypress/support/tenantExpectations.ts
|
|
19
17
|
- toga25-supply/cypress/support/api/stubApi.ts
|
|
20
|
-
- toga25-supply/cypress/support/api/envelope.ts
|
|
21
|
-
- toga25-supply/cypress/support/api/users.ts
|
|
22
|
-
- toga25-supply/cypress/support/api/surfaces.ts
|
|
23
|
-
- toga25-supply/cypress/support/api/session.ts
|
|
24
18
|
- toga25-supply/cypress/e2e/emptyNavigation.cy.ts
|
|
25
|
-
- toga25-supply/cypress/
|
|
26
|
-
- toga25-supply/cypress/
|
|
27
|
-
- toga25-supply/cypress/
|
|
28
|
-
- toga25-supply/cypress/fixtures/tenants/
|
|
29
|
-
- toga25-supply/cypress/fixtures/tenants/
|
|
19
|
+
- toga25-supply/cypress/e2e/itemModalCreate.cy.ts
|
|
20
|
+
- toga25-supply/cypress/e2e/vendorItemModalCreate.cy.ts
|
|
21
|
+
- toga25-supply/cypress/e2e/vendorItemModalEdit.cy.ts
|
|
22
|
+
- toga25-supply/cypress/fixtures/tenants/COMPASS/expectedUI/itemCreate.ts
|
|
23
|
+
- toga25-supply/cypress/fixtures/tenants/COMPASS/expectedUI/vendorItemCreate.ts
|
|
30
24
|
- toga25-supply/scripts/cypress.mjs
|
|
31
|
-
- toga25-supply/tsconfig.node.json
|
|
32
|
-
- toga25-supply/package.json
|
|
33
25
|
related:
|
|
34
26
|
- ../features/surface-frontend.md
|
|
35
27
|
- ../features/client-configurable-fields.md
|
|
28
|
+
- ../features/record-modals-and-nested-tables.md
|
|
36
29
|
- ../features/side-navigation-and-default-route.md
|
|
37
30
|
- ../features/sso-redirect-and-session-gating.md
|
|
38
31
|
- ../features/force-logout-on-deployment.md
|
|
32
|
+
- ./amplify-deployment.md
|
|
39
33
|
- ../architecture.md
|
|
40
34
|
- ../../../2.0/standards/frontend.md
|
|
41
35
|
---
|
|
@@ -47,8 +41,10 @@ a stub layer that answers **every** `/v2` call so no test touches a backend. It
|
|
|
47
41
|
the thing that actually breaks in this app — **tenant × role × feature** — without needing real
|
|
48
42
|
logins or a seeded database.
|
|
49
43
|
|
|
50
|
-
Current state (2026-09-
|
|
51
|
-
|
|
44
|
+
Current state (2026-09-15, branch `TRUE-79810`): the base harness (2026-09-03, `TRUE-81735`)
|
|
45
|
+
plus the **record-modal specs** — create + edit for Items and Vendor Items, per tenant. Tenants are
|
|
46
|
+
COMPASS, COMPASSCANADA, NYCHH, QUAD; only COMPASS and COMPASSCANADA have a role that reaches
|
|
47
|
+
`/items` and `/vendor-items`, so NYCHH and QUAD run a single placeholder test per modal spec.
|
|
52
48
|
`cypress/README.md` is the hands-on version of this doc for a developer working without Claude
|
|
53
49
|
(run commands, recipes, traps). Read that to *write* a test; read this to understand *why* it is
|
|
54
50
|
shaped this way.
|
|
@@ -121,6 +117,80 @@ Three custom commands: `cy.stubApi()`, `cy.visitAsGuest(path)` (storage cleared
|
|
|
121
117
|
stable semantic attribute (`input[name]`, `button[type=submit]`, `a[href]`) rather than adding a
|
|
122
118
|
testid for its own sake.
|
|
123
119
|
|
|
120
|
+
## Record-modal specs (create + edit)
|
|
121
|
+
|
|
122
|
+
`e2e/itemModalCreate.cy.ts` (16 tests) and `e2e/vendorItemModalCreate.cy.ts` (14 tests) are the
|
|
123
|
+
template for any new create/edit modal spec. They test the Pattern 6 form modal — see
|
|
124
|
+
[record-modals-and-nested-tables](../features/record-modals-and-nested-tables.md).
|
|
125
|
+
|
|
126
|
+
**Shape — expectations live in a per-tenant fixture, never in the spec.**
|
|
127
|
+
|
|
128
|
+
- `cypress/fixtures/tenants/<SLUG>/expectedUI/<modal>.ts` holds everything tenant-specific:
|
|
129
|
+
section and field labels, the option text to pick, and the **names of the records to act on**
|
|
130
|
+
(`vendorNameWithoutIntegrationMethods`, `vendorNameWithIntegrationMethods`, `itemName`).
|
|
131
|
+
- The shape is typed in `cypress/support/tenantExpectations.ts`
|
|
132
|
+
(`ItemCreateExpectations`, `VendorItemCreateExpectations`), so a tenant that adds a field fails
|
|
133
|
+
type-check instead of failing at runtime.
|
|
134
|
+
- **A tenant with no fixture must fail as a failing `it()`, not a module-level `throw`.** A throw at
|
|
135
|
+
import time kills the whole spec file with no readable reason; a failing test names the missing
|
|
136
|
+
fixture.
|
|
137
|
+
- Tenants whose role cannot reach the page get **one placeholder test**, so the run count stays
|
|
138
|
+
honest instead of the spec silently registering zero tests.
|
|
139
|
+
|
|
140
|
+
**What the create specs cover** (both modals): empty-form state, the header label following the
|
|
141
|
+
picked vendor, the exact POST body (relations sent as `{uuid}`), required-field errors **per
|
|
142
|
+
field**, currency/number formats, `POST` 500 → error toast + modal closes, the unsaved-input guard
|
|
143
|
+
dialog, a clean form after discard, and the table refetch.
|
|
144
|
+
|
|
145
|
+
Item-create adds: Etilize ID lookup loading images (`GET /vendor-items` join row →
|
|
146
|
+
`GET /items/{uuid}`), option **objects** in the body, an `itemPrices` row for the Default Purchase
|
|
147
|
+
Method, `personaItems` nested as `{persona:{uuid}}`, **no** `itemAssortments` on the body (one
|
|
148
|
+
`POST /assortment-items` per assortment instead), and the *"Unit/Lease price is required when
|
|
149
|
+
Default Price Type is Each/Lease"* rule — which is only reachable on **create**, because on edit
|
|
150
|
+
blox formats the empty price to `0.00` and the rule never fires.
|
|
151
|
+
|
|
152
|
+
Vendor-item-create adds the PO-integration select, which only unlocks for a vendor that has
|
|
153
|
+
integrations: the spec serves `GET /integrations` **keyed on the vendor uuid in the URL**, because
|
|
154
|
+
the tenant-wide stub returns the same rows for every vendor. Use `stubNoIntegrations()` for the
|
|
155
|
+
"vendor without integrations" cases.
|
|
156
|
+
|
|
157
|
+
**New stubs (`support/api/stubApi.ts`):** a default `POST /vendor-items` and a `POST /items` that
|
|
158
|
+
echoes the body back under `data.items` with a 201. Both `/assortment-items` and
|
|
159
|
+
`/assortment-items/` are matched — the app posts the trailing-slash form from create hooks and the
|
|
160
|
+
bare form from edit hooks (see *App findings* below).
|
|
161
|
+
|
|
162
|
+
**Assertion rules learned here — follow them:**
|
|
163
|
+
|
|
164
|
+
- **Refetch = exactly one more GET than before**, asserted as `before + 1` on `cy.get('@alias.all')`.
|
|
165
|
+
`> 1` passes on a double-fetch bug.
|
|
166
|
+
- **Never assert "no create happened" without first proving the submit ran.** Untouched-field
|
|
167
|
+
errors only appear *after* a submit, so assert an error (or `cy.focused()` landing on the first
|
|
168
|
+
invalid field) and *then* assert no POST. Otherwise the test passes when the button did nothing.
|
|
169
|
+
- **Per-field required errors:** find the smallest ancestor that contains the message and assert it
|
|
170
|
+
contains **exactly one input** — the field's. Counting message text passes when the wrong field
|
|
171
|
+
is the one complaining.
|
|
172
|
+
- **Null-safe header assertions:** the vendor header falls back, so assert `number || "Part Number"`.
|
|
173
|
+
|
|
174
|
+
## Selector rules for blox inputs
|
|
175
|
+
|
|
176
|
+
The edit spec (`vendorItemModalEdit.cy.ts`) failed **0/5 on both tenants** because it was written
|
|
177
|
+
against selectors that do not exist. These rules are why:
|
|
178
|
+
|
|
179
|
+
- **Only use a `data-testid` that is actually published in blox.** `getByTestId('Active')`,
|
|
180
|
+
`'Vendor Sku'`, `'toggleTrack'`, `'virtualRows'` exist in **no** published blox build — not the
|
|
181
|
+
pinned `1.1.2-production.144`, not the newest `1.1.4-sandbox-client.148`, not the local checkout.
|
|
182
|
+
Cypress just fails to find them. Check the built package before using a testid.
|
|
183
|
+
- **Target a field by `input#<valueKey>`** — the field's config key is its DOM id, and that is
|
|
184
|
+
stable across tenants.
|
|
185
|
+
- **Pick select options by their exact text, never by menu row index.** Option order differs per
|
|
186
|
+
tenant, so an index-based pick tests a different value on COMPASSCANADA than on COMPASS. Put the
|
|
187
|
+
option text in the tenant's `expectedUI` fixture.
|
|
188
|
+
- **Assert the guard dialog as `dialog[open]`.** `cy.get('cancelDialogGuardrail')` is a bogus
|
|
189
|
+
selector — it looks for a `<cancelDialogGuardrail>` element.
|
|
190
|
+
- **Re-picking a record's current value is not a change** and fires no dependent request. The
|
|
191
|
+
Canada edit spec had to switch its "vendor with integrations" case to **COMPASS CANADA**, because
|
|
192
|
+
re-selecting the record's existing vendor (GRAND & TOY) triggered no `/integrations` fetch.
|
|
193
|
+
|
|
124
194
|
## Guardrails built into the suite
|
|
125
195
|
|
|
126
196
|
### The fall-through check (the main one)
|
|
@@ -163,11 +233,13 @@ detectable instead of silently self-healing.
|
|
|
163
233
|
`cypress/component/harness.cy.tsx` mounts a blox component and asserts one React + one
|
|
164
234
|
QueryClient. It fails when the component-test Vite config drifts from `vite.config.ts`.
|
|
165
235
|
|
|
166
|
-
###
|
|
236
|
+
### CI — now an Amplify build phase (not GitHub Actions)
|
|
167
237
|
|
|
168
|
-
|
|
169
|
-
`*.togasupply` `/etc/hosts` entries
|
|
170
|
-
|
|
238
|
+
The suite runs in the **Amplify build** for `_sandbox-client`: `preTest` writes the four
|
|
239
|
+
`*.togasupply` `/etc/hosts` entries and serves the build, `test` runs one Cypress run per tenant
|
|
240
|
+
plus the component project, `postTest` merges the mochawesome reports, and `USER_DISABLE_TESTS`
|
|
241
|
+
gates it per branch. Full spec and the reasoning live in
|
|
242
|
+
[amplify-deployment](./amplify-deployment.md).
|
|
171
243
|
|
|
172
244
|
## How to run it
|
|
173
245
|
|
|
@@ -245,10 +317,51 @@ Every `cy:*` script goes through **`node scripts/cypress.mjs`** — see the firs
|
|
|
245
317
|
branch.** Otherwise mounted components get a second copy of React / react-query — "Invalid hook
|
|
246
318
|
call", or "No QueryClient set" with the provider plainly visible in the stack.
|
|
247
319
|
`component/harness.cy.tsx` is the canary that fails when it drifts.
|
|
320
|
+
- **⚠ A blox `AdvancedSelect` input collapses to ZERO width once a value is picked** — so clicking
|
|
321
|
+
the `<input>` to reopen the menu fails ("element has no size"). Click the **control**, i.e. the
|
|
322
|
+
input's `.parent()`.
|
|
323
|
+
- **⚠ Ignore the ResizeObserver error PER SPEC, never globally.** Chrome throws
|
|
324
|
+
`ResizeObserver loop completed with undelivered notifications` when the
|
|
325
|
+
`CancelDialogGuardrail` `<dialog>` opens, because the app's `ButtonPair.tsx` writes button widths
|
|
326
|
+
**inside** a `ResizeObserver` callback. Swallow that one message with `cy.on('uncaught:exception')`
|
|
327
|
+
in the spec that opens the dialog. A global handler would hide real app exceptions — the harness
|
|
328
|
+
is meant to fail loud (CTO call).
|
|
329
|
+
- **⚠ Some lists are fetched TWICE when they open** (once as the unfiltered search source, once for
|
|
330
|
+
the filtered options), so a `cy.wait('@alias')` can consume the wrong request and assert against
|
|
331
|
+
the wrong body. Assert on **`cy.get('@alias.all')`** and pick the request you mean.
|
|
332
|
+
- **⚠ After merging a branch off `_feature-cypress`, run `npm install` before anything else.** Those
|
|
333
|
+
branches import Talos packages (`@assistant-ui/react`, `@assistant-ui/react-markdown`,
|
|
334
|
+
`@langchain/langgraph-sdk`, `remark-gfm`); with a stale `node_modules` **every page** dies with
|
|
335
|
+
*"Failed to resolve import"* and every spec fails for a reason that looks nothing like the cause.
|
|
336
|
+
The lockfile already has them, so plain `npm install` fixes it. **Note:** `npm install` replaces a
|
|
337
|
+
locally linked blox with the registry copy — relink afterwards if you were testing a local blox.
|
|
248
338
|
- **Scrub captured payloads.** Copying a real body out of Chrome DevTools (Network → filter
|
|
249
339
|
`/v2/` → Response → Copy response) is the fastest way to build a fixture — but those payloads
|
|
250
340
|
carry **real customer data**. Remove emails, names, addresses and tokens before committing.
|
|
251
341
|
|
|
342
|
+
## App findings surfaced by the suite (open — not fixed)
|
|
343
|
+
|
|
344
|
+
Writing the create specs turned up five real app defects. They are recorded here because the specs
|
|
345
|
+
are what proves them; fix them in the app, then tighten the spec.
|
|
346
|
+
|
|
347
|
+
- **`useItemCreateForm`'s success toast says "New Vendor Item Created" for an item.** Copy bug.
|
|
348
|
+
- **`useVendorItemCreateForm`'s follow-up `POST /assortment-items/` can never fire.** It reads
|
|
349
|
+
`response.data.items.uuid`, but the API returns `data.vendorItems`. It also posts
|
|
350
|
+
`_unitPrice: { enteredPrice }`.
|
|
351
|
+
- **Trailing-slash split:** create hooks post to `"/assortment-items/"`, edit hooks to
|
|
352
|
+
`"/assortment-items"`. The stub matches both; api2's real behaviour is **unverified**.
|
|
353
|
+
- **`ButtonPair.tsx` writes button widths inside a `ResizeObserver`** — the source of the Chrome
|
|
354
|
+
ResizeObserver warning above.
|
|
355
|
+
- **The per-client `vendorItemRecordCreateFields.json` files are not wired** (COMPASS,
|
|
356
|
+
COMPASSCANADA, NYCHH) — `FIELDS` always resolves DEFAULT. See
|
|
357
|
+
[client-configurable-fields](../features/client-configurable-fields.md).
|
|
358
|
+
|
|
359
|
+
## Why e2e per tenant, and not component tests
|
|
360
|
+
|
|
361
|
+
Decided 2026-09-15 (CTO + reviewer): the record modals are tested as **e2e specs, one run per
|
|
362
|
+
tenant**. There is no component mount harness for these modals, and building one inside a test
|
|
363
|
+
ticket is the bigger risk — the tenant axis is the URL, which only the e2e project models.
|
|
364
|
+
|
|
252
365
|
## Rest of the repo's test state (for context)
|
|
253
366
|
|
|
254
367
|
- **Vitest is the other half.** `npm test` (`vitest run`) currently has **one failing test file**:
|
|
@@ -265,6 +378,19 @@ Every `cy:*` script goes through **`node scripts/cypress.mjs`** — see the firs
|
|
|
265
378
|
`SurfaceOverrides IS_VISIBLE` row in `dbchanges2`; the fixture only simulates the grant.
|
|
266
379
|
|
|
267
380
|
## Change history
|
|
381
|
+
- 2026-09-15 — Added the **record-modal create specs** on `TRUE-79810`: `itemModalCreate.cy.ts`
|
|
382
|
+
(16 tests) and `vendorItemModalCreate.cy.ts` (14 tests), per-tenant `expectedUI` fixtures typed by
|
|
383
|
+
`support/tenantExpectations.ts`, and `POST /items` / `POST /vendor-items` stubs. **Rewrote the
|
|
384
|
+
failing `vendorItemModalEdit.cy.ts`** (0/5 on both tenants) — it used `data-testid`s that exist in
|
|
385
|
+
no published blox build and picked select options by menu row index; now `input#<valueKey>` +
|
|
386
|
+
exact option text + record names in the fixtures, `dialog[open]` for the guard dialog, and
|
|
387
|
+
`stubNoIntegrations()` for the no-integrations cases. Recorded three new traps (zero-width
|
|
388
|
+
`AdvancedSelect` input → click `.parent()`; the `ButtonPair` ResizeObserver error, ignored
|
|
389
|
+
per-spec only; double-fetched lists → assert on `@alias.all`), the per-field required-error
|
|
390
|
+
assertion pattern, the refetch-as-`before+1` rule, and the stale-`node_modules` /
|
|
391
|
+
`npm install` trap after merging from `_feature-cypress`. Logged five open app defects the specs
|
|
392
|
+
surfaced, and the decision to test these modals e2e-per-tenant rather than build a component
|
|
393
|
+
mount harness. CI section updated: the suite now runs as an Amplify build phase. (tcox)
|
|
268
394
|
- 2026-09-14 — Re-confirmed the `useTalosSurface.test.ts` / blox `dist/global.css` Vitest load
|
|
269
395
|
failure on a clean tree and refreshed the pass counts (148/148 on the rest). Still unfixed; it
|
|
270
396
|
silently costs one whole suite. No code change. (apeterson)
|
package/knowledge/INDEX.md
CHANGED
|
@@ -14,6 +14,7 @@ _Auto-generated by `knowledge.js index`. Do not hand-edit._
|
|
|
14
14
|
- **walmarttechservices** (Walmart Tech Services) — 1 doc(s) → [1.0/apps/walmarttechservices/INDEX.md](1.0/apps/walmarttechservices/INDEX.md)
|
|
15
15
|
- **test** (Test) — 16 doc(s) → [1.0/apps/test/INDEX.md](1.0/apps/test/INDEX.md)
|
|
16
16
|
- **toga** (TOGa) — 3 doc(s) → [1.0/apps/toga/INDEX.md](1.0/apps/toga/INDEX.md)
|
|
17
|
+
- **store** (Store) — 2 doc(s) → [1.0/apps/store/INDEX.md](1.0/apps/store/INDEX.md)
|
|
17
18
|
- **tools** (Tools) — 18 doc(s) → [1.0/apps/tools/INDEX.md](1.0/apps/tools/INDEX.md)
|
|
18
19
|
|
|
19
20
|
## 2.0 framework
|
|
@@ -16,3 +16,4 @@
|
|
|
16
16
|
| [Prudential Financial](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`). |
|
|
17
17
|
| [Prudential: Dell ASN failed POST backfill replay](workflows/dell-asn-backfill-replay.md) | 2.0 | When Dell ASN POSTs fail in bulk (e.g. |
|
|
18
18
|
| [Prudential — HTTP/2 disabled on the Commerce ALB (proxy workaround) + open re-enable action](workflows/http2-alb-workaround.md) | 2.0 | Prudential users hit **`ERR_HTTP2_PROTOCOL_ERROR`** in Chrome and Edge (both Chromium) loading `https://prudential.togacommerce.com`, while the site worked fine |
|
|
19
|
+
| [Prudential store/SAML Deploy Topology & the Wedged-Agent Deploy Timeout](workflows/store-saml-deploy-topology.md) | 1.0 | Where Prudential's SAML ACS actually runs versus where its storefront runs — they are **two different Elastic Beanstalk environments**, and the CodePipeline act |
|
|
@@ -12,6 +12,7 @@ apps:
|
|
|
12
12
|
- toga2-commerce
|
|
13
13
|
- toga25-supply
|
|
14
14
|
- saml
|
|
15
|
+
- store
|
|
15
16
|
project: _Underscore
|
|
16
17
|
client: prudential
|
|
17
18
|
type: profile
|
|
@@ -33,6 +34,7 @@ related:
|
|
|
33
34
|
- features/supply-orders-csv-export.md
|
|
34
35
|
- features/order-email-pdf-attachments.md
|
|
35
36
|
- features/itemclasses-device-tier-data.md
|
|
37
|
+
- workflows/store-saml-deploy-topology.md
|
|
36
38
|
---
|
|
37
39
|
|
|
38
40
|
## Summary
|
|
@@ -80,6 +82,13 @@ The order-created cron branches on this id; the shipped/delivered crons branch o
|
|
|
80
82
|
breaks HTTP/2, causing `ERR_HTTP2_PROTOCOL_ERROR` in Chrome/Edge. HTTP/2 is currently disabled
|
|
81
83
|
globally on the `true-togacommerce-prod` ALB as a workaround — **open action to re-enable once
|
|
82
84
|
their IT fixes the proxy.** See workflows/http2-alb-workaround.md.
|
|
85
|
+
- **SAML ACS and the storefront are DIFFERENT EB environments.** `prudential.togahub.com` (where
|
|
86
|
+
the SAML assertion is decrypted, in the 1.0 `store` repo) points at `prudential-store-prod`
|
|
87
|
+
(us-west-2, account 502614707982), deployed by the pipeline action **`PrudentialStore`**.
|
|
88
|
+
`prudential.togacommerce.com` (the storefront) points at `true-togacommerce-prod` and is
|
|
89
|
+
deployed by **`TrueProductionDeployment`** — so the action named "PrudentialStore" does *not*
|
|
90
|
+
deploy the storefront. `rumcsi.togahub.com` shares `prudential-store-prod`. See
|
|
91
|
+
workflows/store-saml-deploy-topology.md.
|
|
83
92
|
- Dell will not change their payload shape — see the Dell ASN units interceptor feature doc for the
|
|
84
93
|
PRE/POST translation that keeps their feed working after the tracking-number bridge migration.
|
|
85
94
|
- Service request region is resolved from `Customers.name` string (`'USA'`/`'India'`/`'Ireland'`).
|
|
@@ -117,6 +126,10 @@ The order-created cron branches on this id; the shipped/delivered crons branch o
|
|
|
117
126
|
linking, the "null null" contact-export incident + dedup/backfill remediation.
|
|
118
127
|
- features/order-email-pdf-attachments.md — which PDF guide is attached to which customer
|
|
119
128
|
order email at which stage, the S3 asset list, and the four crons that send them.
|
|
129
|
+
- workflows/store-saml-deploy-topology.md — where Prudential's SAML ACS actually runs vs
|
|
130
|
+
the storefront, the split AWS accounts, and the wedged-agent EB deploy timeout.
|
|
131
|
+
- ../../1.0/apps/store/features/saml-sso-certificate-and-keys.md — the togahub.com SP cert/key
|
|
132
|
+
that decrypts Prudential's assertions.
|
|
120
133
|
- features/supply-orders-csv-export.md — the Prudential-only Sales Orders CSV export in TOGa 2.5
|
|
121
134
|
Supply (`toga25-supply`): its two queries, why contacts join `SalesOrders.contactId` (not
|
|
122
135
|
`Units.contactId`, NULL until Dell assigns the asset), and the `ServiceRequests_Units` fan-out.
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Prudential store/SAML Deploy Topology & the Wedged-Agent Deploy Timeout
|
|
3
|
+
framework: "1.0"
|
|
4
|
+
project: Store
|
|
5
|
+
client: prudential
|
|
6
|
+
type: workflow
|
|
7
|
+
status: active
|
|
8
|
+
updated: 2026-09-15
|
|
9
|
+
owners: ["rgirish"]
|
|
10
|
+
files:
|
|
11
|
+
- store/_/app/framework.php
|
|
12
|
+
related:
|
|
13
|
+
- ../profile.md
|
|
14
|
+
- ../../../1.0/apps/store/architecture.md
|
|
15
|
+
- ../../../1.0/apps/store/features/saml-sso-certificate-and-keys.md
|
|
16
|
+
---
|
|
17
|
+
|
|
18
|
+
## Summary
|
|
19
|
+
|
|
20
|
+
Where Prudential's SAML ACS actually runs versus where its storefront runs — they are **two
|
|
21
|
+
different Elastic Beanstalk environments**, and the CodePipeline action names point at the
|
|
22
|
+
opposite one from what you would guess. Read this before deploying anything SAML-related for
|
|
23
|
+
Prudential. It also records the EB deploy failure mode we hit on 2026-09-15 (a wedged command
|
|
24
|
+
agent that times out after 14 minutes while the environment still shows Green).
|
|
25
|
+
|
|
26
|
+
## The routing — the pipeline action names are misleading
|
|
27
|
+
|
|
28
|
+
| Hostname | CNAME → EB environment | Deployed by pipeline action |
|
|
29
|
+
|---|---|---|
|
|
30
|
+
| `prudential.togacommerce.com` (storefront) | `true-togacommerce-prod.us-west-2` | **`TrueProductionDeployment`** |
|
|
31
|
+
| `prudential.togahub.com` (**SAML ACS**) | `prudential-store-prod.us-west-2` | **`PrudentialStore`** |
|
|
32
|
+
| `rumcsi.togahub.com` | `prudential-store-prod.us-west-2` | `PrudentialStore` |
|
|
33
|
+
|
|
34
|
+
**The action named `PrudentialStore` does NOT deploy the Prudential storefront.**
|
|
35
|
+
`TrueProductionDeployment` does. `PrudentialStore` deploys the host that decrypts the SAML
|
|
36
|
+
assertion — so **cert / decrypt code changes must land on `prudential-store-prod`.**
|
|
37
|
+
|
|
38
|
+
`rumcsi.togahub.com` shares that same environment, so a bad SAML deploy there breaks RUMCSI
|
|
39
|
+
login too.
|
|
40
|
+
|
|
41
|
+
### Accounts and regions (they are split)
|
|
42
|
+
|
|
43
|
+
- **Pipeline:** `Store-Production`, AWS account **502614707982**, region **us-west-2**.
|
|
44
|
+
Not `us-east-1`, and not account `654654170868` where most other TOGA pipelines live.
|
|
45
|
+
- **Route53** zones for `togacommerce.com` and `togahub.com` are in account **654654170868**,
|
|
46
|
+
while the EB environments they point at are in **502614707982**. You need both accounts to
|
|
47
|
+
trace a hostname end-to-end.
|
|
48
|
+
|
|
49
|
+
## Failure mode — 14-minute command timeout with a Green environment
|
|
50
|
+
|
|
51
|
+
Symptoms in the EB event log:
|
|
52
|
+
|
|
53
|
+
```
|
|
54
|
+
The following instances have not responded in the allowed command timeout time
|
|
55
|
+
Command execution completed on all instances. Summary: [Successful: 0, TimedOut: 1]
|
|
56
|
+
Failed to deploy application.
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
The environment still reports **Green / Ok**, which is misleading — the deploy never ran, but
|
|
60
|
+
the old version is still serving traffic fine.
|
|
61
|
+
|
|
62
|
+
### How to confirm it is a wedged command agent
|
|
63
|
+
|
|
64
|
+
1. Check what the instance actually thinks it is running:
|
|
65
|
+
```
|
|
66
|
+
aws elasticbeanstalk describe-instances-health --attribute-names All \
|
|
67
|
+
--environment-name prudential-store-prod --region us-west-2
|
|
68
|
+
```
|
|
69
|
+
A wedged agent shows `Deployment.VersionLabel` still on the **old** build with
|
|
70
|
+
`Deployment.Status: Deployed`, while `CPU Idle` is ~99% and load average ~0.0. That means the
|
|
71
|
+
instance never picked up the deploy command at all — it is not busy, it is deaf.
|
|
72
|
+
2. Check whether SSM sees the instance:
|
|
73
|
+
```
|
|
74
|
+
aws ssm describe-instance-information --region us-west-2
|
|
75
|
+
```
|
|
76
|
+
An **empty** result for the instance confirms the agent is not reporting.
|
|
77
|
+
|
|
78
|
+
Observed 2026-09-15: `prudential-store-prod` on `i-07ae1b81ee48df938` (t3.micro, running since
|
|
79
|
+
2026-07-17), stuck on an Aug 13 build. `spglobal-store-prod` failed identically on
|
|
80
|
+
`i-04a6a50cffd37457e` — that environment has **no instances and no DNS pointing at it**.
|
|
81
|
+
|
|
82
|
+
## What you need to do
|
|
83
|
+
|
|
84
|
+
1. Confirm the diagnosis with the two AWS commands above before touching anything.
|
|
85
|
+
2. Reboot the EC2 instance (EC2 console or `aws ec2 reboot-instances`).
|
|
86
|
+
3. Watch the EB event log for `Command is executing on all instances` — that means the EB command
|
|
87
|
+
channel is back.
|
|
88
|
+
4. Re-run the failed pipeline deploy.
|
|
89
|
+
5. If it times out again, do **not** reboot a second time. Use an EB **Instance refresh** /
|
|
90
|
+
instance replacement instead.
|
|
91
|
+
|
|
92
|
+
A reboot restored the EB command channel on 2026-09-15, but SSM still did not report after 3
|
|
93
|
+
minutes — EB working again is the signal to retry, not SSM.
|
|
94
|
+
|
|
95
|
+
## Change history
|
|
96
|
+
- 2026-09-15 — Created while rotating the `togahub.com` SAML cert. Documented that
|
|
97
|
+
`prudential.togahub.com` (SAML ACS) and `prudential.togacommerce.com` (storefront) are separate
|
|
98
|
+
EB environments with swapped-sounding pipeline action names, the split AWS accounts/regions,
|
|
99
|
+
and the wedged-command-agent deploy timeout with its `describe-instances-health` /
|
|
100
|
+
`describe-instance-information` diagnosis and reboot-then-instance-refresh remediation.
|
|
101
|
+
(rgirish)
|
|
@@ -5,12 +5,13 @@ apps:
|
|
|
5
5
|
- togadesk
|
|
6
6
|
- togaview
|
|
7
7
|
- library
|
|
8
|
+
- store
|
|
8
9
|
project: TOGa Desk
|
|
9
10
|
client: rumcsi
|
|
10
11
|
type: profile
|
|
11
12
|
status: active
|
|
12
|
-
updated: 2026-
|
|
13
|
-
owners: ["mhammontree"]
|
|
13
|
+
updated: 2026-09-15
|
|
14
|
+
owners: ["mhammontree", "rgirish"]
|
|
14
15
|
files: []
|
|
15
16
|
related:
|
|
16
17
|
- ../../1.0/apps/togadesk/features/per-client-host-restriction.md
|
|
@@ -19,6 +20,7 @@ related:
|
|
|
19
20
|
- ../../1.0/apps/togadesk/features/email-to-ticket-intake.md
|
|
20
21
|
- ../../1.0/apps/togadesk/architecture.md
|
|
21
22
|
- ../../1.0/apps/togaview/architecture.md
|
|
23
|
+
- ../../1.0/apps/store/features/saml-sso-certificate-and-keys.md
|
|
22
24
|
---
|
|
23
25
|
|
|
24
26
|
## Summary
|
|
@@ -124,6 +126,12 @@ hardcoded to `clientId = 2`; no RUMC row has `adPeopleId`/`ldap_user`), and
|
|
|
124
126
|
- **No SAML auto-provisioning.** Unlike towfoundation and newcenturyholdingsllc, the rumcsi
|
|
125
127
|
branch only `SELECT`s `people`; it never creates them. That is precisely why the bulk load was
|
|
126
128
|
necessary.
|
|
129
|
+
- **`rumcsi.togahub.com` runs on the `prudential-store-prod` EB environment** (1.0 `store` repo,
|
|
130
|
+
us-west-2, account 502614707982) — the same host as Prudential's SAML ACS. A bad SAML/cert
|
|
131
|
+
deploy there breaks RUMCSI login too. RUMCSI's ACS branch skips assertion decryption (raw
|
|
132
|
+
`SimpleXMLElement` + XPath), so the SP cert rotation does not affect it directly. See
|
|
133
|
+
../../1.0/apps/store/features/saml-sso-certificate-and-keys.md and
|
|
134
|
+
../prudential/workflows/store-saml-deploy-topology.md.
|
|
127
135
|
|
|
128
136
|
## Change history
|
|
129
137
|
|
package/knowledge/registry.json
CHANGED
package/package.json
CHANGED