toga-ai 1.0.367 → 1.0.369
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/2.0/apps/toga25-supply/INDEX.md +1 -0
- package/knowledge/2.0/apps/toga25-supply/workflows/amplify-deployment.md +119 -0
- package/knowledge/2.0/standards/frontend-deploy.md +36 -0
- package/knowledge/INDEX.md +1 -1
- package/knowledge/clients/rate/INDEX.md +1 -1
- package/knowledge/clients/rate/features/service-purchase-emails.md +13 -1
- package/knowledge/clients/rate/features/whole-home-warranty-purchase-guard.md +46 -2
- package/package.json +1 -1
|
@@ -9,4 +9,5 @@
|
|
|
9
9
|
| [Meta-Driven Page & Table Setup](features/meta-driven-table-data.md) | A page in this app is **meta-driven end to end**: the page view model fetches *page meta* (labels, sections, ACL) and *table meta* (the columns/fields + table s | toga25-supply/src/pages/SalesOrders/viewModel/useSalesOrdersPageViewModel.tsx, toga25-supply/src/pages/SalesOrders/hooks/useSalesOrdersTableState.ts, toga25-supply/src/hooks/useTablePageMeta.ts, toga-blox-npm/dist/hooks/useFetchPageMeta.d.ts, toga-blox-npm/dist/hooks/useFetchTablePageMeta.d.ts, toga-blox-npm/dist/hooks/useAssignTableFieldLabels.d.ts, toga-blox-npm/dist/components/Table/hooks/useTableData.d.ts |
|
|
10
10
|
| [Record Modals & Nested Tables](features/record-modals-and-nested-tables.md) | The repo's family of modal + nested-table patterns layered over toga-blox `TableRecordModal` and `PrimaryTable*Layout`. | toga25-supply/src/layout/ItemRecordModalLayout/, toga25-supply/src/layout/SalesOrderRecordModalLayout/, toga25-supply/src/layout/SalesOrderItemsTableLayout/, toga25-supply/src/layout/ItemFulfillmentModal/, toga25-supply/src/layout/GenericNestedTables/, toga25-supply/src/hooks/useTableCellInteractions.ts |
|
|
11
11
|
| [Surface Frontend (DB-driven UI consumption, src/surface/)](features/surface-frontend.md) | The frontend consumer of the platform-wide Surface layer — DB-driven UI config fetched from `GET /v2/surfaces/meta?slug=<slug>` instead of statically-imported J | toga25-supply/src/surface/useFetchSurfaceMeta.ts, toga25-supply/src/pages/SalesOrders/helpers/surfaceBundleToTenantFields.ts, toga25-supply/src/pages/SalesOrders/helpers/buildPatchedTenantFields.ts, toga25-supply/src/pages/SalesOrders/view/SalesOrderRecordModalLayout/viewModel/useSalesOrderRecordModalLayoutModel.tsx, toga25-supply/src/pages/SalesOrders/view/SalesOrderRecordModalLayout/view/SalesOrderView.tsx, toga25-supply/src/pages/SalesOrders/view/SalesOrderRecordModalLayout/view/layoutComponents/SalesOrderSummaryGrid.tsx, toga25-supply/src/pages/SalesOrders/view/SalesOrderRecordModalLayout/helpers/getDetailSections.tsx, toga25-supply/src/pages/SalesOrders/view/SalesOrderRecordModalLayout/helpers/index.ts, toga25-supply/src/surface/evaluateSurfaceRule.ts, toga25-supply/src/surface/actionRegistry.ts, toga25-supply/src/surface/componentRegistry.tsx, toga25-supply/src/surface/SurfaceActionBar.tsx, toga25-supply/src/surface/SurfaceSection.tsx, toga25-supply/src/surface/resolve.ts, toga25-supply/src/surface/types.ts, toga25-supply/src/surface/index.ts, toga25-supply/src/pages/Login/LoginPage.tsx, toga25-supply/src/pages/SalesOrders/SalesOrders.tsx, toga25-supply/src/pages/SalesOrders/view/SurfaceRowActions.tsx, toga25-supply/src/pages/SalesOrders/view/SalesOrderRecordModalLayout/view/sections/SalesOrderTopBar.tsx, toga25-supply/src/pages/Items/ItemsPage.tsx, toga25-supply/src/pages/Items/viewModel/useItemsPageViewModel.tsx, toga25-supply/src/pages/VendorItems/VendorItemsPage.tsx, toga25-supply/src/pages/VendorItems/viewModel/useVendorItemsPageViewModel.tsx, toga25-supply/src/pages/Inventory/Inventory.tsx, toga25-supply/src/pages/Inventory/viewModel/useInventoryPageViewModel.tsx, toga25-supply/src/pages/Inventory/viewModel/FIELDS/index.ts, toga25-supply/src/fieldsConfig/index.ts |
|
|
12
|
+
| [AWS Amplify Multi-Environment Deployment](workflows/amplify-deployment.md) | How `toga25-supply` deploys to **all** of its environments on AWS Amplify from a **single shared `amplify.yml`**. | toga25-supply/amplify.yml, toga25-supply/src/api/api.ts, toga25-supply/src/hooks/useAuthenticationFlow.ts, toga25-supply/vite.config.ts, toga25-supply/package.json |
|
|
12
13
|
| [Cypress Testing Harness (component + e2e)](workflows/cypress-testing.md) | The Cypress test harness for the `toga25-supply` frontend, bootstrapped from scratch (`cypress` was already a dependency but there was no config, no `cypress/` | toga25-supply/cypress.config.ts, toga25-supply/cypress/tsconfig.json, toga25-supply/cypress/support/component.tsx, toga25-supply/cypress/support/component-index.html, toga25-supply/cypress/support/e2e.ts, toga25-supply/cypress/support/commands.ts, toga25-supply/cypress/support/fixtures.ts, toga25-supply/cypress/support/mocks/useApprovalModalViewModel.ts, toga25-supply/cypress/component/SalesOrderApprovalModalsLayout.cy.tsx, toga25-supply/cypress/component/RecordApprovalModalLayout.cy.tsx, toga25-supply/cypress/component/EnterPoNumberModal.cy.tsx, toga25-supply/cypress/e2e/salesOrderApproval.cy.ts |
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: AWS Amplify Multi-Environment Deployment
|
|
3
|
+
framework: "2.0"
|
|
4
|
+
repo: toga25-supply
|
|
5
|
+
project: TOGa 2.5 Supply
|
|
6
|
+
client: shared
|
|
7
|
+
type: workflow
|
|
8
|
+
status: active
|
|
9
|
+
updated: 2026-07-17
|
|
10
|
+
owners: [jcardinal]
|
|
11
|
+
files:
|
|
12
|
+
- toga25-supply/amplify.yml
|
|
13
|
+
- toga25-supply/src/api/api.ts
|
|
14
|
+
- toga25-supply/src/hooks/useAuthenticationFlow.ts
|
|
15
|
+
- toga25-supply/vite.config.ts
|
|
16
|
+
- toga25-supply/package.json
|
|
17
|
+
related:
|
|
18
|
+
- ../architecture.md
|
|
19
|
+
---
|
|
20
|
+
|
|
21
|
+
## What it is
|
|
22
|
+
|
|
23
|
+
How `toga25-supply` deploys to **all** of its environments on AWS Amplify from a **single
|
|
24
|
+
shared `amplify.yml`**. One build spec serves every branch; the correct Vite build mode is
|
|
25
|
+
derived from the Amplify branch name at build time, so there is **zero per-branch console
|
|
26
|
+
configuration**. This supports 21 per-mode environments: `development`, `sandbox-dev`,
|
|
27
|
+
`sandbox-client`, `client-alpha/beta/gamma`, `qa-task/hotfix/alpha/beta/gamma`,
|
|
28
|
+
`qc-task/hotfix/security/performance/alpha/beta/gamma`, `stage`, `demo`, and `production`.
|
|
29
|
+
|
|
30
|
+
Use this doc when adding a new environment/branch, debugging a deploy that shipped the wrong
|
|
31
|
+
environment identity or wrong API base URL, or touching `amplify.yml`.
|
|
32
|
+
|
|
33
|
+
## How it works
|
|
34
|
+
|
|
35
|
+
### Branch → mode convention (load-bearing)
|
|
36
|
+
|
|
37
|
+
- Branch name convention is **`_<mode>`** — a single leading underscore followed by the mode.
|
|
38
|
+
- `amplify.yml` derives the Vite mode from the built-in `AWS_BRANCH` variable by stripping the
|
|
39
|
+
one leading underscore: branch `_sandbox-client` → `VITE_MODE=sandbox-client`.
|
|
40
|
+
- `<mode>` matches **both** the `.env.<mode>` file suffix **and** the `package.json` script
|
|
41
|
+
name literally.
|
|
42
|
+
- Build command is:
|
|
43
|
+
|
|
44
|
+
```
|
|
45
|
+
npx vite build --mode "$VITE_MODE"
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
This loads `.env.<mode>` and bakes `import.meta.env.MODE` into the bundle.
|
|
49
|
+
|
|
50
|
+
- **Not** `npm run build` — that has no `--mode` and defaults to `production` on every branch
|
|
51
|
+
(this was the original bug: every environment shipped as production).
|
|
52
|
+
- **Not** `npx vite "$VITE_MODE"` / `npm run <mode>` — the per-mode `package.json` scripts are
|
|
53
|
+
**dev servers** (`vite --mode X --host Y`), not builds.
|
|
54
|
+
|
|
55
|
+
### Fail-loud guardrails
|
|
56
|
+
|
|
57
|
+
The spec **fails the build** if the branch has no leading underscore, or if there is no
|
|
58
|
+
matching `.env.<mode>` file. A misconfigured branch therefore can **never** silently ship as
|
|
59
|
+
production.
|
|
60
|
+
|
|
61
|
+
### Build/cache mechanics
|
|
62
|
+
|
|
63
|
+
- `preBuild`: `npm ci --cache .npm --prefer-offline`
|
|
64
|
+
- artifacts `baseDirectory`: `dist`
|
|
65
|
+
- cache paths: `.npm/**/*` and `node_modules/.vite/**/*`
|
|
66
|
+
- Node: Vite 7 requires Node 20.19+ or 22.12+ (Node 22 recommended). Amplify's default image
|
|
67
|
+
is fine; optionally pin via `.nvmrc`.
|
|
68
|
+
|
|
69
|
+
### Why the exact `--mode` matters at runtime
|
|
70
|
+
|
|
71
|
+
`import.meta.env.MODE` (the `--mode` value) is baked into the bundle and read at **runtime**
|
|
72
|
+
by the auth flow: `useAuthenticationFlow.ts` sends it as the `environment` value to the
|
|
73
|
+
`/domains` API endpoint. A generic `vite build` (mode `production`) therefore ships the wrong
|
|
74
|
+
**environment identity**, not just the wrong URL. Each deployed environment must be built with
|
|
75
|
+
its exact `--mode` string.
|
|
76
|
+
|
|
77
|
+
Environments differ only by `VITE_API` base URL (per `.env.<mode>`). Per-**client** behavior
|
|
78
|
+
is separate and resolves at **runtime by hostname** (`useHostnameStore` / `clientSlug`), not
|
|
79
|
+
at build time — this is a multi-client shared build, so there are no client-specific branches.
|
|
80
|
+
|
|
81
|
+
### API base-URL resolution (`api.ts`)
|
|
82
|
+
|
|
83
|
+
axios `baseURL` resolves as:
|
|
84
|
+
|
|
85
|
+
```
|
|
86
|
+
import.meta.env["VITE_API_" + hostname.split(".")[0].toUpperCase()] || import.meta.env.VITE_API
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
i.e. an optional per-subdomain override, falling back to `VITE_API`. The `.env.<mode>` files
|
|
90
|
+
are committed and git-tracked; they contain **only public `VITE_API` base URLs, no secrets**,
|
|
91
|
+
and are the single source of truth for environment API endpoints.
|
|
92
|
+
|
|
93
|
+
## Adding a new environment
|
|
94
|
+
|
|
95
|
+
1. Create `.env.<mode>` with the public `VITE_API` base URL.
|
|
96
|
+
2. Create an Amplify branch named `_<mode>` (matching the `.env` suffix exactly).
|
|
97
|
+
3. Do **not** add any `VITE_*` environment variable in the Amplify console (see gotcha).
|
|
98
|
+
4. Deploy — `amplify.yml` handles the rest.
|
|
99
|
+
|
|
100
|
+
## Gotchas
|
|
101
|
+
|
|
102
|
+
- **NEVER set `VITE_API` (or any `VITE_*` URL) as an Amplify console environment variable.**
|
|
103
|
+
Vite does not let `.env` files overwrite variables that already exist in the build shell.
|
|
104
|
+
Amplify injects every "App settings > Environment variables" into the build shell as an OS
|
|
105
|
+
env var, so a console `VITE_API` silently **overrides every `.env.<mode>` file on every
|
|
106
|
+
branch**. Confirmed live: a console `VITE_API="sandbox-client"` produced a bundle whose
|
|
107
|
+
axios `baseURL` was the literal relative string `sandbox-client`, so API calls hit
|
|
108
|
+
`https://<site-origin>/sandbox-client/auth/...` (404) instead of
|
|
109
|
+
`https://api.client.sandbox.togahub.com/v2`. **Fix:** remove the console variable **and
|
|
110
|
+
redeploy** — removing the var does not retroactively fix an already-built bundle; a fresh
|
|
111
|
+
build is required. The `.env.<mode>` files are the single source of truth.
|
|
112
|
+
- **`npm run build` ships production everywhere** — always build with an explicit
|
|
113
|
+
`--mode "$VITE_MODE"`; the per-mode `package.json` scripts are dev servers, not builds.
|
|
114
|
+
|
|
115
|
+
## Change history
|
|
116
|
+
- 2026-07-17 — Documented the single shared `amplify.yml` multi-environment deploy: `_<mode>`
|
|
117
|
+
branch → `--mode` derivation, fail-loud guardrails, runtime `import.meta.env.MODE` identity,
|
|
118
|
+
and the critical never-set-`VITE_API`-in-the-Amplify-console gotcha (console vars override
|
|
119
|
+
all `.env` files; requires removal **and** redeploy). (jcardinal)
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: 2.0 Frontend Deployment (Vite + AWS Amplify)
|
|
3
|
+
framework: "2.0"
|
|
4
|
+
project: _Underscore
|
|
5
|
+
client: shared
|
|
6
|
+
type: standard
|
|
7
|
+
status: active
|
|
8
|
+
updated: 2026-07-17
|
|
9
|
+
owners: [jcardinal]
|
|
10
|
+
related:
|
|
11
|
+
- ../apps/toga25-supply/workflows/amplify-deployment.md
|
|
12
|
+
---
|
|
13
|
+
|
|
14
|
+
## Scope
|
|
15
|
+
Applies to all 2.0 Vite + React/TS frontends deployed on AWS Amplify.
|
|
16
|
+
|
|
17
|
+
## Branch → build mode
|
|
18
|
+
- Amplify branches are named `_<mode>` (single leading underscore + mode).
|
|
19
|
+
- The shared `amplify.yml` derives the Vite mode from `AWS_BRANCH` by stripping the
|
|
20
|
+
leading underscore and builds with `npx vite build --mode "$VITE_MODE"`.
|
|
21
|
+
- `<mode>` must match the `.env.<mode>` file suffix exactly.
|
|
22
|
+
- Never deploy with `npm run build` (no `--mode` → defaults to production on every branch).
|
|
23
|
+
- The build spec must fail loudly when a branch lacks the leading underscore or a matching
|
|
24
|
+
`.env.<mode>` file, so a misconfigured branch can never silently ship as production.
|
|
25
|
+
|
|
26
|
+
## Environment variables
|
|
27
|
+
- **Never** set `VITE_*` URL variables (e.g. `VITE_API`) in the Amplify console. Vite will
|
|
28
|
+
not let `.env` files override variables already present in the build shell, so a console
|
|
29
|
+
var silently overrides every `.env.<mode>` file on every branch.
|
|
30
|
+
- Removing such a console var requires a **redeploy** — it does not fix an already-built
|
|
31
|
+
bundle.
|
|
32
|
+
- Public base URLs live in committed, git-tracked `.env.<mode>` files (no secrets); these
|
|
33
|
+
are the single source of truth.
|
|
34
|
+
|
|
35
|
+
## Node
|
|
36
|
+
- Vite 7 requires Node 20.19+ or 22.12+ (Node 22 recommended). Pin via `.nvmrc` if needed.
|
package/knowledge/INDEX.md
CHANGED
|
@@ -29,7 +29,7 @@ _Auto-generated by `knowledge.js index`. Do not hand-edit._
|
|
|
29
29
|
- **voice-to-voice** (TOGa Voice) — 4 doc(s) → [2.0/apps/voice-to-voice/INDEX.md](2.0/apps/voice-to-voice/INDEX.md)
|
|
30
30
|
- **ai-bdr** (AI-BDR) — 7 doc(s) → [2.0/apps/ai-bdr/INDEX.md](2.0/apps/ai-bdr/INDEX.md)
|
|
31
31
|
- **toga2-commerce** (TOGa Commerce) — 9 doc(s) → [2.0/apps/toga2-commerce/INDEX.md](2.0/apps/toga2-commerce/INDEX.md)
|
|
32
|
-
- **toga25-supply** (TOGa 2.5 Supply) —
|
|
32
|
+
- **toga25-supply** (TOGa 2.5 Supply) — 9 doc(s) → [2.0/apps/toga25-supply/INDEX.md](2.0/apps/toga25-supply/INDEX.md)
|
|
33
33
|
- **toga-blox** (TOGa Blox) — 7 doc(s) → [2.0/apps/toga-blox/INDEX.md](2.0/apps/toga-blox/INDEX.md)
|
|
34
34
|
- **bdr** (BDR) — 0 doc(s) → [2.0/apps/bdr/INDEX.md](2.0/apps/bdr/INDEX.md)
|
|
35
35
|
|
|
@@ -8,5 +8,5 @@
|
|
|
8
8
|
| [Rate SAML SSO](features/saml-sso.md) | 2.0 | Rate uses Azure AD as its IdP (`login.rate.com`). | _underscore/Model/Rate/ClientAuthentication.php, saml/Controller/Index.php, toga2-view/src/hooks/useAuthenticationFlow.ts |
|
|
9
9
|
| [Service Card Entitlement Display](features/service-card-entitlements.md) | 2.0 | Rate's home and services pages display one service card per purchased entitlement. | src/components/ServiceCard/ServiceCard.tsx, src/components/ServiceCard/index.ts, src/hooks/useBundleServices.ts, src/pages/Home/api/homeApi.ts, src/pages/Home/view/HomePage.tsx, src/pages/Home/viewModels/useHomePageViewModel.ts, src/pages/Services/view/ServicesPage.tsx, src/pages/Services/viewModels/useServicePageViewModel.ts |
|
|
10
10
|
| [Rate Service-Purchase Confirmation Emails (Tech / Warranty)](features/service-purchase-emails.md) | 2.0 | When a Rate customer purchases a service, a confirmation email is sent. | _underscore/Model/Rate/Entitlement.php, worker2/Worker/Notification/EmailTemplate.php, dbchanges2/Client_Rate/2026-06-30a - Rate purchase email templates.sql |
|
|
11
|
-
| [Rate Whole Home Warranty Per-Address Purchase Guard](features/whole-home-warranty-purchase-guard.md) | 2.0 |
|
|
11
|
+
| [Rate Whole Home Warranty Per-Address Purchase Guard](features/whole-home-warranty-purchase-guard.md) | 2.0 | > **⚠ NOT IN PRODUCTION (as of 2026-07-17).** TRUE-79533 is **not** merged to `_production`. | _underscore/Model/Rate/Entitlement.php, _underscore/Model/Client/Address.php, dbchanges2/Client_Rate/2026-07-07a - WholeHomeWarrantyPerAddressGuard.sql, test/@Mark/Rate/verify_wholehome_per_address_guard.php |
|
|
12
12
|
| [Rate](profile.md) | 2.0 | Rate is a mortgage/lending client. | |
|
|
@@ -6,7 +6,7 @@ project: _Underscore
|
|
|
6
6
|
client: rate
|
|
7
7
|
type: client-feature
|
|
8
8
|
status: active
|
|
9
|
-
updated: 2026-
|
|
9
|
+
updated: 2026-07-17
|
|
10
10
|
owners: [mhammontree]
|
|
11
11
|
files:
|
|
12
12
|
- _underscore/Model/Rate/Entitlement.php
|
|
@@ -118,6 +118,15 @@ Email web-font reality: Gmail/Outlook strip web fonts and fall back; Apple Mail/
|
|
|
118
118
|
|
|
119
119
|
## Gotchas / known issues
|
|
120
120
|
|
|
121
|
+
- **Shared interceptor with TRUE-79533 (WH per-address guard) — same `prePost`/`postPost`.** This
|
|
122
|
+
workflow's `postPost` trigger + helpers live in the **same** `_Model_Rate_Entitlement`
|
|
123
|
+
interceptors that the [Whole Home Warranty per-address guard](whole-home-warranty-purchase-guard.md)
|
|
124
|
+
edits, and TRUE-79251 also adds the passThrough `const PASSTHROUGH_KEY_FULFILLMENT_PREFERENCE` +
|
|
125
|
+
the contractFulfillmentPreference (EV-8) fix inside `prePost`. A bad `_production`→`_beta` merge
|
|
126
|
+
already mis-resolved this once and fatally broke every Rate purchase save. When merging either
|
|
127
|
+
ticket across branches, hand-merge into ONE `prePost()` keeping both features' logic — see the
|
|
128
|
+
**shared-interceptor merge hazard** gotcha on that doc for the correct resolution and why `php -l`
|
|
129
|
+
is not a sufficient gate.
|
|
121
130
|
- **`border-radius` on a `<td>` renders SQUARE in many clients (Outlook).** Use an `<img>` for
|
|
122
131
|
circular badges/icons (e.g. the green circle-check) instead of a CSS-rounded table cell.
|
|
123
132
|
- **ASCII-only subjects (mojibake workaround).** `_Email::send()` does not set PHPMailer
|
|
@@ -150,6 +159,9 @@ Remaining items are **optional / separate, not go-live blockers**:
|
|
|
150
159
|
|
|
151
160
|
## Change history
|
|
152
161
|
|
|
162
|
+
- 2026-07-17 — Added a cross-reference to the [WH per-address guard](whole-home-warranty-purchase-guard.md):
|
|
163
|
+
both tickets edit the same `_Model_Rate_Entitlement` `prePost`/`postPost` interceptors, a
|
|
164
|
+
shared-interceptor merge hazard that already broke `_beta` once. No behavior change here. (mhammontree)
|
|
153
165
|
- 2026-06-30 — Built the Rate service-purchase confirmation email workflow (TRUE-79251):
|
|
154
166
|
`postPost` trigger + helpers in `_Model_Rate_Entitlement`, two DB-stored `Client_Rate`
|
|
155
167
|
templates (tech / warranty) dispatched off-thread via
|
|
@@ -5,8 +5,8 @@ repo: _underscore
|
|
|
5
5
|
project: _Underscore
|
|
6
6
|
client: rate
|
|
7
7
|
type: client-feature
|
|
8
|
-
status:
|
|
9
|
-
updated: 2026-07-
|
|
8
|
+
status: draft
|
|
9
|
+
updated: 2026-07-17
|
|
10
10
|
owners: [mhammontree]
|
|
11
11
|
files:
|
|
12
12
|
- _underscore/Model/Rate/Entitlement.php
|
|
@@ -19,6 +19,13 @@ related:
|
|
|
19
19
|
- ../../../2.0/apps/_underscore/features/address-validation.md
|
|
20
20
|
---
|
|
21
21
|
|
|
22
|
+
> **⚠ NOT IN PRODUCTION (as of 2026-07-17).** TRUE-79533 is **not** merged to `_production`.
|
|
23
|
+
> Its `_underscore` + `dbchanges2` PRs are still OPEN / "BACK-END REVIEW" (git-verified: commit
|
|
24
|
+
> `be5002f4` is contained only in branches `TRUE-79533` and `_beta`, never `_production`). An
|
|
25
|
+
> earlier belief that it "shipped weeks ago" is incorrect — TRUE-79251 (purchase emails) shipped,
|
|
26
|
+
> this guard did not. The branch is being caught up to `_production` now. See the
|
|
27
|
+
> **shared-interceptor merge hazard** gotcha below before merging.
|
|
28
|
+
|
|
22
29
|
## Summary
|
|
23
30
|
|
|
24
31
|
A customer may hold **one active Whole Home Warranty (WH) per validated address, globally**
|
|
@@ -127,6 +134,36 @@ live carrier waterfall is opt-in via `RUN_LIVE=1` (defaults off). Verified **18/
|
|
|
127
134
|
|
|
128
135
|
## Gotchas / known issues
|
|
129
136
|
|
|
137
|
+
- **Shared-interceptor merge hazard — TRUE-79533 and TRUE-79251 edit the SAME methods.** Both
|
|
138
|
+
this guard and the [purchase-email workflow](service-purchase-emails.md) (TRUE-79251) modify the
|
|
139
|
+
**same** `prePost`/`postPost` interceptors on `_Model_Rate_Entitlement`, and TRUE-79251 also adds
|
|
140
|
+
the passThrough `const PASSTHROUGH_KEY_FULFILLMENT_PREFERENCE` + the contractFulfillmentPreference
|
|
141
|
+
(EV-8) fix inside `prePost`. This makes the file a merge landmine:
|
|
142
|
+
- **What already went wrong (on `_beta`):** a later `Merge branch '_production' into _beta`
|
|
143
|
+
mis-resolved the conflict — it reverted `prePost` to production's version and **deleted** the
|
|
144
|
+
private helpers `isWholeHomeWarranty()`, `normalizedField()`, `hasActiveWarrantyAtAddress()`,
|
|
145
|
+
while leaving the caller `persistWarrantyServiceAddress()` (a separate hunk). Result:
|
|
146
|
+
`postPost` → `persistWarrantyServiceAddress()` → `self::isWholeHomeWarranty()` = fatal
|
|
147
|
+
`Error: Call to undefined method`, thrown **before** the method's own try/catch (so it is NOT
|
|
148
|
+
swallowed). This breaks the entitlement save for **every** Rate purchase (tech + warranty).
|
|
149
|
+
Because the crash happens **after** `sendPurchaseEmail()` was enqueued and the record saved, the
|
|
150
|
+
symptom is: record created + email sent, yet the API returns an error → user retries →
|
|
151
|
+
**duplicate entitlements / emails / AIG contracts**.
|
|
152
|
+
- **Correct conflict resolution is NEITHER side alone, and NOT "accept both".** The conflict is a
|
|
153
|
+
single block where HEAD (TRUE-79533) defines the WH-guard `prePost()` + 3 helpers and
|
|
154
|
+
`_production` defines the passThrough `prePost()` + `PASSTHROUGH_KEY_FULFILLMENT_PREFERENCE`.
|
|
155
|
+
Both sides define `prePost()`. accept-HEAD drops the constant (→ undefined-constant fatal in
|
|
156
|
+
`postPost`, and regresses the EV-8 contractFulfillment fix); accept-theirs reproduces the
|
|
157
|
+
`isWholeHomeWarranty` undefined-method crash; accept-both yields a fatal duplicate `prePost()`
|
|
158
|
+
redeclare. **Hand-merge into ONE `prePost()`** that (1) strips `contractFulfillmentPreference`
|
|
159
|
+
→ `$api->passThrough` for every purchase, then (2) runs the WH guard for warranty purchases —
|
|
160
|
+
keeping the constant AND all three private helpers. The common `postPost` below the conflict
|
|
161
|
+
references **both** symbols (the constant at the AIG-contract build, and `isWholeHomeWarranty()`
|
|
162
|
+
in `persistWarrantyServiceAddress`), so both must survive.
|
|
163
|
+
- **`php -l` is NOT a sufficient gate here.** Lint passes on the broken accept-HEAD state — the
|
|
164
|
+
two failures (undefined class constant, EV-8 regression) are runtime, not syntax. Verify with
|
|
165
|
+
`test/@Mark/Rate/verify_wholehome_per_address_guard.php` (18/18 against `Client_Rate`,
|
|
166
|
+
carrier calls off).
|
|
130
167
|
- **TOCTOU on the uniqueness check (accepted).** The check-then-persist dedup is **not**
|
|
131
168
|
lock-guarded. Two truly concurrent same-address WH purchases could both pass. Accepted for
|
|
132
169
|
sequential purchases; documented in code.
|
|
@@ -143,6 +180,13 @@ live carrier waterfall is opt-in via `RUN_LIVE=1` (defaults off). Verified **18/
|
|
|
143
180
|
|
|
144
181
|
## Change history
|
|
145
182
|
|
|
183
|
+
- 2026-07-17 — **Status corrected to `draft` / not-in-production** (TRUE-79533): git-verified the
|
|
184
|
+
guard was never merged to `_production` (PRs open / back-end review), contrary to a belief it had
|
|
185
|
+
shipped. Documented the **shared-interceptor merge hazard** with TRUE-79251 and the correct
|
|
186
|
+
hand-merge of the dual `prePost()` — a bad `_production`→`_beta` merge had deleted the WH helpers
|
|
187
|
+
(`isWholeHomeWarranty()` et al.) while keeping the caller, fatally breaking every Rate purchase
|
|
188
|
+
save and causing duplicate entitlements/emails/AIG contracts. Resolved the catch-up merge;
|
|
189
|
+
`php -l` clean and 18/18 verify against `Client_Rate`. (mhammontree)
|
|
146
190
|
- 2026-07-08 — Added a backend verification script
|
|
147
191
|
(`test/@Mark/Rate/verify_wholehome_per_address_guard.php`, 18/18 pass against the migrated
|
|
148
192
|
`Client_Rate` schema) and a Verification pointer. (mhammontree)
|
package/package.json
CHANGED