toga-ai 1.0.283 → 1.0.284
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/_underscore/INDEX.md +1 -0
- package/knowledge/2.0/apps/_underscore/features/address-validation.md +76 -0
- package/knowledge/INDEX.md +1 -1
- package/knowledge/clients/rate/INDEX.md +1 -0
- package/knowledge/clients/rate/features/aig-contract-creation.md +12 -0
- package/knowledge/clients/rate/features/whole-home-warranty-purchase-guard.md +140 -0
- package/package.json +1 -1
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
|-----|---------|-------|
|
|
5
5
|
| [_underscore Framework Architecture](architecture.md) | `_underscore` is the shared PHP backend framework for **all 2.0 applications**. | _underscore/_underscore.php, _underscore/Loader.php, _underscore/Framework.php, _underscore/Model.php, _underscore/Database.php, _underscore/Query.php, _underscore/Route.php, _underscore/Component.php |
|
|
6
6
|
| [ACL Permission Chain (Record & Field Authorization)](features/acl-permission-chain.md) | Authorization in the 2.0 API is **metadata-driven**: whether a role may Create/Read/Update/Delete a record is decided by rows across **four linked tables**, not | api2/Component/Api/V2/V2.php, _underscore/Model/Core/Page.php, dbchanges2/Client/2026-06-03- BLANK_CLIENT_DATABASE.sql, dbchanges2/Client/2026-06-23b - ItemTranslationsAcl.sql |
|
|
7
|
+
| [Address Validation (carrier waterfall + validateAddress scripted endpoint)](features/address-validation.md) | `_Model_Client_Address::validateAddress` verifies a US address against a **carrier waterfall (USPS → FedEx → UPS)** and returns a single canonical, carrier-norm | _underscore/Model/Client/Address.php |
|
|
7
8
|
| [Assortment Name Translation (AssortmentTranslations sidecar)](features/assortment-name-translation.md) | Serves Assortment (product-grouping) **names** in multiple languages by adding a per-language **sidecar** table `AssortmentTranslations`, reusing the platform's | _underscore/Model/Client/AssortmentTranslation.php, dbchanges2/Client/2026-06-26a - AssortmentTranslations.sql, dbchanges2/Core/2026-06-26a - AssortmentTranslationsRecord.sql, dbchanges2/Client/2026-06-26b - AssortmentTranslationsAcl.sql |
|
|
8
9
|
| [Carrier Shipping Labels (UPS/FedEx) & NetSuite Item Fulfillment](features/carrier-shipping-labels.md) | Backend mechanics behind TOGa Supply's Fulfill & Ship: buying a carrier label (UPS/FedEx), persisting it, and creating the NetSuite Item Fulfillment with tracki | _underscore/Model/Client/ItemFulfillment.php, _underscore/Model/Client/TrackingNumber.php, _underscore/Model/Client/ItemFulfillments/TrackingNumber.php, _underscore/Component/Library/LabelPdf/LabelPdf.php, _underscore/Component/Library/Carriers/ShipmentRequest/ShipmentRequest.php, _underscore/Component/Library/Carriers/Ups/Ups.php, _underscore/Component/Library/Carriers/Fedex/Fedex.php, _underscore/Trait/Netsuite/ItemFulfillment.php, _underscore/Trait/Netsuite/SalesOrder.php, _underscore/Component/Library/NetSuite/NetSuite.php, _underscore/Model/Client/TrackingNumber.php, _underscore/Model/Client/ShippingMethod.php, _underscore/Model.php, _underscore/Cloud.php |
|
|
9
10
|
| [Client Email Template Sending](features/email-template-sending.md) | `_Model_Client_EmailTemplate` sends a stored, client-defined email template by UUID. | _underscore/Model/Client/EmailTemplate.php, _underscore/Model/Client/EmailTemplateOutgoingEmailAddress.php, _underscore/Email.php |
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: "Address Validation (carrier waterfall + validateAddress scripted endpoint)"
|
|
3
|
+
framework: "2.0"
|
|
4
|
+
repo: _underscore
|
|
5
|
+
project: _Underscore
|
|
6
|
+
client: shared
|
|
7
|
+
type: feature
|
|
8
|
+
status: active
|
|
9
|
+
updated: 2026-07-07
|
|
10
|
+
owners: [mhammontree]
|
|
11
|
+
files:
|
|
12
|
+
- _underscore/Model/Client/Address.php
|
|
13
|
+
related:
|
|
14
|
+
- ../../api2/features/scripted-api-post-body-args.md
|
|
15
|
+
- ../../api2/architecture.md
|
|
16
|
+
- ../../../clients/rate/features/whole-home-warranty-purchase-guard.md
|
|
17
|
+
---
|
|
18
|
+
|
|
19
|
+
## Summary
|
|
20
|
+
|
|
21
|
+
`_Model_Client_Address::validateAddress` verifies a US address against a **carrier
|
|
22
|
+
waterfall (USPS → FedEx → UPS)** and returns a single canonical, carrier-normalized
|
|
23
|
+
address. It is exposed to the frontend as the **global** scripted endpoint
|
|
24
|
+
`GET /addresses/validateAddress`, and is also callable server-side by any model/interceptor
|
|
25
|
+
that needs to validate + normalize an address before persisting it (e.g. the Rate
|
|
26
|
+
Whole Home Warranty purchase guard).
|
|
27
|
+
|
|
28
|
+
## Key files / entry points
|
|
29
|
+
|
|
30
|
+
- `_Model_Client_Address::validateAddress(&$api, $address1, $address2, $city, $state, $zip)`
|
|
31
|
+
(`_underscore/Model/Client/Address.php`) — runs USPS, then FedEx, then UPS.
|
|
32
|
+
- **On success** (carriers agree) returns keys: `success:true`, `address1`, `address2`,
|
|
33
|
+
`city`, `state` (2-letter code), `zipCode`, `country`. UPS additionally supplies
|
|
34
|
+
`zip4`/`zip5`.
|
|
35
|
+
- **On failure** (all carriers fail) returns the **last (UPS) response** with
|
|
36
|
+
`success:false` (and an `error`).
|
|
37
|
+
|
|
38
|
+
## The scripted endpoint is registered in the DB, not in source (parity gap)
|
|
39
|
+
|
|
40
|
+
The `GET /addresses/validateAddress` endpoint the frontend calls is wired as a **global**
|
|
41
|
+
scripted endpoint in **`Core.RecordScripts`** (id 17: `recordId` 13 = Addresses, method
|
|
42
|
+
`GET`, route `validateAddress`, `phpMethod` `validateAddress`). Verified in production
|
|
43
|
+
2026-07-07.
|
|
44
|
+
|
|
45
|
+
- It is **not** in any client-level `CustomRecordScripts` (Rate's is empty), and it is
|
|
46
|
+
**not** present in `dbchanges2` source — the registration exists **only in the live DB**.
|
|
47
|
+
Treat this as a known source/DB parity gap: you will not find the endpoint by grepping
|
|
48
|
+
`dbchanges2`.
|
|
49
|
+
- **Dispatch precedence:** the V2 engine checks `Core.RecordScripts` **before** a client's
|
|
50
|
+
`CustomRecordScripts`, so a global script wins for every client unless a client overrides it.
|
|
51
|
+
- **Response path:** a scripted method's return value is placed at
|
|
52
|
+
`data.<record>.<route>` in the API envelope — so `validateAddress` (record = Addresses,
|
|
53
|
+
route = validateAddress) returns to **`data.addresses.validateAddress`**, which is exactly
|
|
54
|
+
what the frontend reads.
|
|
55
|
+
|
|
56
|
+
## Client variations
|
|
57
|
+
|
|
58
|
+
None — this is shared engine + framework behavior. The carrier credentials are resolved
|
|
59
|
+
per the standard client carrier config.
|
|
60
|
+
|
|
61
|
+
## Gotchas / known issues
|
|
62
|
+
|
|
63
|
+
- **DB-only registration.** Because the `Core.RecordScripts` row is not in `dbchanges2`,
|
|
64
|
+
the endpoint's existence is invisible to source search and is not reproduced by replaying
|
|
65
|
+
migrations into a fresh DB. Register scripted endpoints in `dbchanges2` going forward.
|
|
66
|
+
- **Waterfall requires agreement on success.** The success shape is only returned when the
|
|
67
|
+
carriers agree; a `success:false` result carries the last (UPS) response, not a merged one.
|
|
68
|
+
|
|
69
|
+
## Change history
|
|
70
|
+
|
|
71
|
+
- 2026-07-07 — Documented `_Model_Client_Address::validateAddress` (USPS→FedEx→UPS waterfall
|
|
72
|
+
+ normalized success shape) and the global `GET /addresses/validateAddress`
|
|
73
|
+
(`Core.RecordScripts` id 17) endpoint, including the dispatch precedence, the
|
|
74
|
+
`data.<record>.<route>` response path, and the dbchanges2 source/DB parity gap
|
|
75
|
+
(registration lives only in the live DB). Prod-verified while building the Rate WH purchase
|
|
76
|
+
guard (TRUE-79533). (mhammontree)
|
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)_ — 24 doc(s) → [2.0/apps/_underscore/INDEX.md](2.0/apps/_underscore/INDEX.md)
|
|
21
21
|
- **worker2** (Worker) — 27 doc(s) → [2.0/apps/worker2/INDEX.md](2.0/apps/worker2/INDEX.md)
|
|
22
22
|
- **api2** (API) — 10 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)
|
|
@@ -8,4 +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 | A customer may hold **one active Whole Home Warranty (WH) per validated address, globally** (across all borrowers). | _underscore/Model/Rate/Entitlement.php, _underscore/Model/Client/Address.php, dbchanges2/Client_Rate/2026-07-07a - WholeHomeWarrantyPerAddressGuard.sql |
|
|
11
12
|
| [Rate](profile.md) | 2.0 | Rate is a mortgage/lending client. | |
|
|
@@ -14,6 +14,7 @@ files:
|
|
|
14
14
|
- worker2/Worker/Monitors/RateEntitlement.php
|
|
15
15
|
related:
|
|
16
16
|
- clients/rate/profile.md
|
|
17
|
+
- clients/rate/features/whole-home-warranty-purchase-guard.md
|
|
17
18
|
- ../../../2.0/apps/worker2/features/monitoring-framework.md
|
|
18
19
|
---
|
|
19
20
|
|
|
@@ -35,6 +36,13 @@ This is a base-`_underscore` interceptor specific to Rate's product (it lives un
|
|
|
35
36
|
`Model/Rate/`); documented here as a Rate client-feature because the behavior and its
|
|
36
37
|
monitoring are Rate-scoped.
|
|
37
38
|
|
|
39
|
+
**As of 2026-07-07 (TRUE-79533)** the same `postPost` also persists the validated **service
|
|
40
|
+
address** (`Entitlements.c_serviceAddressId`, `Addresses.isValidated=1`) — that persistence is
|
|
41
|
+
wrapped so it never blocks this AIG-contract flow or the confirmation email. For Whole Home
|
|
42
|
+
Warranty purchases, a new `prePost` guard now also **carrier-normalizes the address onto the
|
|
43
|
+
payload before save**, so the AIG contract is created with the canonical address. See the
|
|
44
|
+
[Whole Home Warranty per-address purchase guard](whole-home-warranty-purchase-guard.md).
|
|
45
|
+
|
|
38
46
|
## How it works
|
|
39
47
|
|
|
40
48
|
1. `postPost` runs after an entitlement is saved. It proceeds **only if** the payload has a
|
|
@@ -91,6 +99,10 @@ creation vs. cancellation. This is an accepted, documented limitation — not an
|
|
|
91
99
|
|
|
92
100
|
## Change history
|
|
93
101
|
|
|
102
|
+
- 2026-07-07 — `postPost` now also persists the validated service address
|
|
103
|
+
(`Entitlements.c_serviceAddressId`, `Addresses.isValidated=1`), wrapped so it never blocks the
|
|
104
|
+
AIG-contract/email flow; a new WH `prePost` guard carrier-normalizes the address onto the
|
|
105
|
+
payload before save (TRUE-79533). See the WH per-address purchase guard doc. (mhammontree)
|
|
94
106
|
- 2026-06-29 — Documented the silent-failure AIG warranty-contract interceptor and the
|
|
95
107
|
external log-scan monitor (`_Worker_Monitors_RateEntitlement`, TRUE-79129), including the
|
|
96
108
|
accepted detection limitation that auth-induced failures are not attributable (shared
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: "Rate Whole Home Warranty Per-Address Purchase Guard"
|
|
3
|
+
framework: "2.0"
|
|
4
|
+
repo: _underscore
|
|
5
|
+
project: _Underscore
|
|
6
|
+
client: rate
|
|
7
|
+
type: client-feature
|
|
8
|
+
status: active
|
|
9
|
+
updated: 2026-07-07
|
|
10
|
+
owners: [mhammontree]
|
|
11
|
+
files:
|
|
12
|
+
- _underscore/Model/Rate/Entitlement.php
|
|
13
|
+
- _underscore/Model/Client/Address.php
|
|
14
|
+
- dbchanges2/Client_Rate/2026-07-07a - WholeHomeWarrantyPerAddressGuard.sql
|
|
15
|
+
related:
|
|
16
|
+
- clients/rate/profile.md
|
|
17
|
+
- clients/rate/features/aig-contract-creation.md
|
|
18
|
+
- ../../../2.0/apps/_underscore/features/address-validation.md
|
|
19
|
+
---
|
|
20
|
+
|
|
21
|
+
## Summary
|
|
22
|
+
|
|
23
|
+
A customer may hold **one active Whole Home Warranty (WH) per validated address, globally**
|
|
24
|
+
(across all borrowers). This guard enforces that business rule at purchase time on the Rate
|
|
25
|
+
Entitlements route via a `prePost` interceptor on `_Model_Rate_Entitlement`
|
|
26
|
+
(`_underscore/Model/Rate/Entitlement.php`, Core.Records recordId **191**, registered PRE/POST).
|
|
27
|
+
|
|
28
|
+
For **WH purchases only** (WH is identified by the sale-item title containing `"warranty"`,
|
|
29
|
+
consistent with `resolvePurchaseProduct`), the guard:
|
|
30
|
+
|
|
31
|
+
1. **Requires a service address** from `$payload->contact->primaryContactAddress->address`.
|
|
32
|
+
2. **Hard-blocks** the purchase if
|
|
33
|
+
[`_Model_Client_Address::validateAddress`](../../../2.0/apps/_underscore/features/address-validation.md)
|
|
34
|
+
(USPS→FedEx→UPS waterfall) returns `success:false` — no entitlement is created; the error
|
|
35
|
+
surfaces to the frontend toaster.
|
|
36
|
+
3. **Adopts the carrier-normalized address** (`address1`/`address2`/`city`/`state`/`zipCode`)
|
|
37
|
+
and writes it back onto the payload, so both the saved address and the AIG contract use the
|
|
38
|
+
canonical form.
|
|
39
|
+
4. **Hard-blocks a second active WH at the same physical address globally** via
|
|
40
|
+
`hasActiveWarrantyAtAddress()`.
|
|
41
|
+
|
|
42
|
+
`postPost` then persists the validated service address after save (sets `Addresses.isValidated=1`
|
|
43
|
+
and `Entitlements.c_serviceAddressId`), wrapped so it never blocks the existing AIG-contract /
|
|
44
|
+
confirmation-email flow.
|
|
45
|
+
|
|
46
|
+
Ticket: TRUE-79533. Business rule owner: PM Paulina.
|
|
47
|
+
|
|
48
|
+
## Business rules (decided this session — Mark / Paulina)
|
|
49
|
+
|
|
50
|
+
- **Invalid address → HARD BLOCK.** An address that fails carrier validation blocks the
|
|
51
|
+
purchase entirely (no entitlement), surfaced as an API error to the frontend toaster.
|
|
52
|
+
- **Uniqueness is GLOBAL per address**, not per-borrower — a second active WH on the same
|
|
53
|
+
physical address is rejected regardless of which borrower buys it.
|
|
54
|
+
- **Address line2 distinguishes units.** A landlord's apartment units are distinct addresses:
|
|
55
|
+
2A / 2B / 2C = three separate WHs. Uniqueness keys on the full normalized address including
|
|
56
|
+
line2.
|
|
57
|
+
- **Persist the validated address ON the entitlement** (new `c_serviceAddressId` FK) rather
|
|
58
|
+
than deriving it from the fragile contact-primary-address path, and **backfill** existing WH
|
|
59
|
+
entitlements so the global rule covers production data already present.
|
|
60
|
+
|
|
61
|
+
## How it works
|
|
62
|
+
|
|
63
|
+
1. **`prePost` (PRE/POST interceptor).** Fires only for WH sale items (title LIKE
|
|
64
|
+
`%warranty%`).
|
|
65
|
+
2. Reads the service address off `$payload->contact->primaryContactAddress->address`; missing
|
|
66
|
+
address → block.
|
|
67
|
+
3. Calls `_Model_Client_Address::validateAddress`; `success:false` → block. On success, copies
|
|
68
|
+
the normalized `address1/address2/city/state/zipCode` back onto the payload.
|
|
69
|
+
4. `hasActiveWarrantyAtAddress()` joins `Entitlements` → `Items` (title LIKE `%warranty%`, with
|
|
70
|
+
WH `Items.id = 3` as an OR fallback) → **active** `Subscriptions` (`isActive = 1 AND
|
|
71
|
+
dateCancelled IS NULL`) → `Addresses` on the new `c_serviceAddressId` column. A match → block
|
|
72
|
+
the second active WH.
|
|
73
|
+
- **The dedup read disables the query cache** (`_Database::useQueryCache(false)`, restored in
|
|
74
|
+
a `finally`) so it sees rows committed by a just-prior purchase rather than a stale cached
|
|
75
|
+
result set.
|
|
76
|
+
5. **`postPost`.** After save, persists the validated service address: sets
|
|
77
|
+
`Addresses.isValidated = 1` and writes `Entitlements.c_serviceAddressId`. This block is
|
|
78
|
+
wrapped so a failure never interrupts the AIG-contract creation or the confirmation email
|
|
79
|
+
(see the [AIG contract creation doc](aig-contract-creation.md) — the same `postPost`).
|
|
80
|
+
6. All SQL uses `_Database::escape()` / int-casts — `_Query` has **no bind API** in this path.
|
|
81
|
+
|
|
82
|
+
## Migration (`dbchanges2/Client_Rate/2026-07-07a - WholeHomeWarrantyPerAddressGuard.sql`)
|
|
83
|
+
|
|
84
|
+
- Adds `Entitlements.c_serviceAddressId INT NULL` + an index.
|
|
85
|
+
- Guarded (`NOT EXISTS`) registration of `CustomRecordFields` for `c_serviceAddressId`
|
|
86
|
+
(recordId 191, type **NUMBER**).
|
|
87
|
+
- Guarded registration of the `ApiPayloadInterceptors` PRE/POST rows.
|
|
88
|
+
- A one-time, idempotent backfill of `c_serviceAddressId` for existing WH entitlements, sourced
|
|
89
|
+
from `Contacts` → `ContactAddresses` → `Addresses`.
|
|
90
|
+
|
|
91
|
+
## Rate data model (prod-verified 2026-07-07)
|
|
92
|
+
|
|
93
|
+
- **WH product** = `Items.id 3` / `partNumber 1429124` "Whole Home Warranty - Monthly". Tech
|
|
94
|
+
products are `Items.id` 1 and 2.
|
|
95
|
+
- **Active-subscription signal is on `Subscriptions`** (`isActive` tinyint default 1,
|
|
96
|
+
`dateCancelled`, `dateEnd`, `dtRequestToCancel`). `Entitlements` has **no `isActive`**.
|
|
97
|
+
- **Entitlements had no address linkage before this ticket.** `SalesOrders` has
|
|
98
|
+
`shipToAddressId`/`billToAddressId` but they are NULL for Rate. Before `c_serviceAddressId`,
|
|
99
|
+
the address was reachable only via `Entitlement.contactId` → `Contacts.primaryContactAddressId`
|
|
100
|
+
→ `ContactAddresses.addressId` → `Addresses`.
|
|
101
|
+
- **Borrower identity** = `Customers.c_borrowerId`.
|
|
102
|
+
- **Core.Records record IDs:** Addresses = 13, Entitlements = 191, Sales orders = 14,
|
|
103
|
+
Entitlement tickets = 212.
|
|
104
|
+
|
|
105
|
+
## Frontend contract (delivered by Tanner — TRUE-79825 / 79969 / 79905, toga2-view)
|
|
106
|
+
|
|
107
|
+
The frontend UI that this guard honors was delivered separately: `components/ValidateAddressModal`,
|
|
108
|
+
`hooks/useValidateAddressFormViewModel`, `pages/ZipValidation`. The FE calls
|
|
109
|
+
`GET /addresses/validateAddress` and reads **`data.addresses.validateAddress`** typed as
|
|
110
|
+
`{ success, error?, address1?, address2?, city?, state?, zipCode?, country? }`. The modal shows
|
|
111
|
+
suggested-vs-entered when `success && address1`, and a "couldn't verify" toaster when
|
|
112
|
+
`success === false`. See the shared
|
|
113
|
+
[address-validation feature](../../../2.0/apps/_underscore/features/address-validation.md) for the
|
|
114
|
+
endpoint mechanics.
|
|
115
|
+
|
|
116
|
+
## Gotchas / known issues
|
|
117
|
+
|
|
118
|
+
- **TOCTOU on the uniqueness check (accepted).** The check-then-persist dedup is **not**
|
|
119
|
+
lock-guarded. Two truly concurrent same-address WH purchases could both pass. Accepted for
|
|
120
|
+
sequential purchases; documented in code.
|
|
121
|
+
- **AIG carrier vs. AIG tenant.** "AIG" here is the warranty **carrier** behind Rate's Whole
|
|
122
|
+
Home Warranty — a different codepath from the separate `Client_Aig` / Staples Protection Plan
|
|
123
|
+
tenant. Do not conflate the two.
|
|
124
|
+
- **`ApiPayloadInterceptors` (Client_Rate) has no `phpMethod` column.** The interceptor method
|
|
125
|
+
is resolved **by convention** from `(prePostProcessing, httpMethod)`: `PRE+POST → prePost`,
|
|
126
|
+
`POST+POST → postPost` (matches the framework `[pre|post][HttpMethod]` convention).
|
|
127
|
+
- **`CustomRecordFields.type` enum supports only `STRING`/`NUMBER`** — `c_serviceAddressId` is
|
|
128
|
+
registered as `NUMBER`.
|
|
129
|
+
- **No `_Query` bind API here** — use `_Database::escape()` / int-casts for all interpolated
|
|
130
|
+
values.
|
|
131
|
+
|
|
132
|
+
## Change history
|
|
133
|
+
|
|
134
|
+
- 2026-07-07 — Built the WH per-address purchase guard (TRUE-79533): `prePost` on
|
|
135
|
+
`_Model_Rate_Entitlement` hard-blocks invalid addresses and second active WH at the same
|
|
136
|
+
normalized address (global), adopts the carrier-normalized address onto the payload, and
|
|
137
|
+
disables the query cache for the committed-read dedup; `postPost` persists the validated
|
|
138
|
+
service address (`Entitlements.c_serviceAddressId`, `Addresses.isValidated=1`) without blocking
|
|
139
|
+
the AIG/email flow. Migration adds `c_serviceAddressId` + index, registers the CustomRecordField
|
|
140
|
+
and PRE/POST interceptors, and backfills existing WH entitlements. (mhammontree)
|
package/package.json
CHANGED